Skip to content

Commit 8cf0972

Browse files
doublegateclaude
andauthored
chore(v2.0.1): remove dead mc-r1-dmc-abort-probe flag + measure-first perf pass (#237)
Closes the v2.0.1 "housekeeping" workstream #225 deferred: removes the dead `mc-r1-dmc-abort-probe` diagnostic feature flag in full, runs a measure-first performance pass (adopting nothing — discipline), and reconciles the affected docs. Behavior-neutral: the default build is byte-identical and AccuracyCoin holds 139/141 (nestest 0-diff). Removed — `mc-r1-dmc-abort-probe` - Deleted the 3 `[features]` decls (`rustynes-apu`, `rustynes-core`, `rustynes-test-harness` Cargo.toml) + the two forwarding chains. - `rustynes-apu/src/apu.rs`: removed the ~210-line gated `abort_probe` module (atomic counters, `pack_dmc`, snapshot/log helpers) and all 13 `#[cfg]` increment/dump sites, preserving the real behavior each was interleaved with (`cannot_run_dmc_dma = 2`, `dmc_edge_arm_suppress`, `pending_dmc_abort`, `dmc_abort_delay`, the `$4015` handler, the explicit-abort scheduler). - `rustynes-apu/src/lib.rs`: removed the gated `pub use apu::abort_probe`. - `rustynes-core/src/bus.rs`: removed the 3 gated log hooks. - `scan_dma_abort.rs` retained (only its probe-only reporting block removed; the no-features RAM-scan fast-path has standalone value). - No `#[cfg(not)]` counterparts existed, so the default build is byte-identical. Kept — `mmc3-m2-phase-irq` (conservative) Unlike the pure-diagnostic DMC probe, it gates real alternate MMC3-IRQ-timing behavior (with `#[cfg(not)]` shipped-path counterparts) and is still referenced by the open R1/R2 residual (ADR 0002, by-design-deferred, not closed). Perf — measure-first, nothing adopted `cargo bench -p rustynes-core` `full_frame` medians: `nes_run_frame_nestest` 3.77 ms (4.42x NTSC realtime), `flowing_palette` 2.26 ms (7.37x) — within noise of the documented R1 baseline. No micro-opt cleared the >3%-Criterion + byte-identical bar (v1.7.0 H7 discipline), so none adopted. Recorded in `docs/performance.md`. Docs — STATUS (probe removal + remaining default-off flags), ADR 0030 (dated update: both bounded octal-latch attempts reach only 139/141 flag-on), CHANGELOG `[Unreleased]`, `docs/performance.md`, and the stale `docs/release-notes/v2.0.1.md` kept-flags list. Review: adopted both gemini brace-cleanups (apu.rs, bus.rs) + the copilot release-notes-consistency fix; all 3 threads resolved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 519bdd5 commit 8cf0972

12 files changed

Lines changed: 113 additions & 454 deletions

File tree

‎CHANGELOG.md‎

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

18-
*Nothing yet — the next planned milestone is **v2.0.1** (the first release of the
19-
Android mobile-finalization re-port train onto the v2.0.0 core; see
20-
`to-dos/ROADMAP.md`).*
18+
### Removed
19+
20+
- **Dead `mc-r1-dmc-abort-probe` diagnostic feature (v2.0.1 housekeeping).** The
21+
default-off DMC-DMA-abort scheduling probe left over from the v2.0.0 Timebase
22+
R-1 work is removed in full: the `[features]` declarations in `rustynes-apu`,
23+
`rustynes-core`, and `rustynes-test-harness`; the gated `abort_probe` pub-static
24+
atomic-counter module and its `lib.rs` re-export; every `#[cfg(feature =
25+
"mc-r1-dmc-abort-probe")]` increment/dump site in `rustynes-apu/src/apu.rs` and
26+
`rustynes-core/src/bus.rs`; and the probe-only reporting block in the
27+
`scan_dma_abort` diagnostic bin (the bin itself is retained — its no-features
28+
RAM-scan fast-path has standalone value). The flag gated **only** diagnostic
29+
counters and CSV dumps, never shipped behaviour, so the default build is
30+
byte-identical — **AccuracyCoin holds 139/141, nestest 0-diff**. The
31+
`mmc3-m2-phase-irq` experiment was evaluated for the same treatment and
32+
deliberately **kept**: it gates real alternate MMC3-IRQ-timing behaviour (with
33+
`#[cfg(not)]` shipped-path counterparts) and remains tied to the still-open
34+
R1/R2 residual investigation (ADR 0002, by-design-deferred, not closed).
35+
36+
### Changed
37+
38+
- **`docs/performance.md`** gains a v2.0.1 `full_frame` measure-first entry:
39+
the end-to-end headless frame cost is re-measured and confirmed far under the
40+
16.639 ms NTSC budget and against the ≤2 ms/frame target; per the standing
41+
>3%-Criterion-stable + byte-identical bar (v1.7.0 H7 precedent), **no
42+
optimization was adopted**.
43+
- **`docs/adr/0030`** records that both bounded v2.0.1 AccuracyCoin ALE/hybrid
44+
attempts — Option 2 (`mc-ppu-bus-addr-hybrid`, PR #234) and Option 1 (the
45+
2-cycle-ALE draft, PR #236) — reached only 139/141 flag-on, empirically
46+
confirming the fix needs a dedicated Timebase-scale campaign, not a bounded
47+
fork. **139/141 is the honest v2.0.1 baseline.**
48+
- **`docs/STATUS.md`** feature-flags section updated: the DMC-abort-probe removal
49+
is now recorded as done, and the new default-off v2.0.1 experiments
50+
(`mc-ppu-bus-addr-hybrid`, ADR 0030; `mmc3-m2-phase-irq`, ADR 0002) are listed
51+
among the genuinely-remaining flags.
2152

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

‎crates/rustynes-apu/Cargo.toml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ workspace = true
2323
# would then quietly opt every consumer in to `std`, masking no_std regressions
2424
# from cross-target CI gates that build with `--no-default-features`.
2525
std = []
26-
# v2.0 R-1 core C-1 DIAGNOSTIC: gated pub-static atomic counters that pin
27-
# where the explicit-DMC-abort scheduling diverges under R1. Default-off.
28-
mc-r1-dmc-abort-probe = []
2926

3027
[dependencies]
3128
bitflags.workspace = true

‎crates/rustynes-apu/src/apu.rs‎

Lines changed: 1 addition & 322 deletions
Large diffs are not rendered by default.

‎crates/rustynes-apu/src/lib.rs‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ mod snapshot;
4949
mod triangle;
5050

5151
pub use apu::{Apu, ApuBus, CHANNEL_GAIN_UNITY, CHANNEL_MASK_ALL};
52-
// v2.0 R-1 core C-1 diagnostic — re-export the gated abort-scheduling probe
53-
// counters so the harness can read `rustynes_core::rustynes_apu::abort_probe`.
54-
#[cfg(feature = "mc-r1-dmc-abort-probe")]
55-
pub use apu::abort_probe;
5652
pub use blip::{BlipBuf, CPU_HZ_NTSC, CPU_HZ_PAL};
5753
pub use dmc::Dmc;
5854
pub use dmc::REENABLE_BUMP;

‎crates/rustynes-core/Cargo.toml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ ppu-state-trace = ["std", "rustynes-ppu/ppu-state-trace"]
4242
# `scripts/mesen2_cpu_boot_trace.lua` for the Mesen2 reference
4343
# generator.
4444
cpu-boot-trace = ["std"]
45-
# v2.0 R-1 core C-1 diagnostic — forward to rustynes-apu. Default-off.
46-
mc-r1-dmc-abort-probe = ["rustynes-apu/mc-r1-dmc-abort-probe"]
4745
# v2.0.1 (ADR 0030) — EXPERIMENTAL persistent-PPU-bus-address model (Option 2). Forward
4846
# to rustynes-ppu. Default-off; the shipped build is byte-identical without it.
4947
mc-ppu-bus-addr-hybrid = ["rustynes-ppu/mc-ppu-bus-addr-hybrid"]

‎crates/rustynes-core/src/bus.rs‎

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3350,12 +3350,6 @@ impl LockstepBus {
33503350
/// where 6502 core address bits 15..=5 remain from the halted CPU read
33513351
/// while DMA supplies address bits 4..=0.
33523352
fn dmc_dma_read(&mut self, addr: u16, halted_addr: u16) -> u8 {
3353-
#[cfg(feature = "mc-r1-dmc-abort-probe")]
3354-
{
3355-
#[allow(clippy::cast_possible_truncation)]
3356-
let c = self.cycle as u32;
3357-
rustynes_apu::abort_probe::log_get(c, addr);
3358-
}
33593353
let sample = self.raw_cpu_read(addr);
33603354
if matches!(self.apu_region(), ApuRegion::Pal) || (halted_addr & 0xFFE0) != 0x4000 {
33613355
return sample;
@@ -3704,12 +3698,6 @@ impl LockstepBus {
37043698
};
37053699
self.last_read_addr = addr;
37063700
self.open_bus = v;
3707-
#[cfg(feature = "mc-r1-dmc-abort-probe")]
3708-
if addr == 0x4000 && (48_000_000..=48_700_000).contains(&self.cycle) {
3709-
#[allow(clippy::cast_possible_truncation)]
3710-
let c = self.cycle as u32;
3711-
rustynes_apu::abort_probe::log_4000_read(c, v);
3712-
}
37133701
// Mirror the read onto the internal data bus, but ONLY when
37143702
// this is a CPU-initiated access. DMC DMA fetches drive
37153703
// only the EXTERNAL (`open_bus`) bus per nesdev's two-bus
@@ -4025,15 +4013,7 @@ impl Bus for LockstepBus {
40254013
0x0000..=0x1FFF => self.ram[(addr & 0x07FF) as usize] = value,
40264014
0x2000..=0x3FFF => self.ppu_register_write(addr, value),
40274015
REG_OAM_DMA => self.dma_pending = Some(value),
4028-
0x4000..=0x4013 | 0x4015 | 0x4017 => {
4029-
#[cfg(feature = "mc-r1-dmc-abort-probe")]
4030-
if addr == 0x4010 {
4031-
#[allow(clippy::cast_possible_truncation)]
4032-
let c = self.cycle as u32;
4033-
rustynes_apu::abort_probe::log_4010(c, value);
4034-
}
4035-
self.apu.write_register(addr, value);
4036-
}
4016+
0x4000..=0x4013 | 0x4015 | 0x4017 => self.apu.write_register(addr, value),
40374017
0x4016 => {
40384018
// Session-24 / Phase 3 (Controller Strobing): the
40394019
// controllers' OUT pins are only updated at the start

‎crates/rustynes-test-harness/Cargo.toml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ ppu-state-trace = ["rustynes-core/ppu-state-trace"]
3939
# The export is output-only, so AccuracyCoin / nestest / blargg / kevtris must
4040
# be identical with this on. Off by default (the CI gate stays the stock build).
4141
hd-pack = ["rustynes-core/hd-pack"]
42-
# v2.0 R-1 core C-1 diagnostic — forward to rustynes-core. Default-off.
43-
mc-r1-dmc-abort-probe = ["rustynes-core/mc-r1-dmc-abort-probe"]
4442
# v2.0.1 (ADR 0030) — EXPERIMENTAL persistent-PPU-bus-address model (Option 2). Forward
4543
# to rustynes-core so the AccuracyCoin battery can run with the flag on.
4644
mc-ppu-bus-addr-hybrid = ["rustynes-core/mc-ppu-bus-addr-hybrid"]

‎crates/rustynes-test-harness/src/bin/scan_dma_abort.rs‎

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -192,99 +192,6 @@ fn main() -> ExitCode {
192192
}
193193
println!();
194194

195-
#[cfg(feature = "mc-r1-dmc-abort-probe")]
196-
{
197-
use core::sync::atomic::Ordering::Relaxed;
198-
use std::fmt::Write as _;
199-
200-
// Φ-2 hang diagnostic: how many DMAs hit the >60-step escape (GET never
201-
// fired = wedge), and the put_cycle parity at the hang.
202-
println!(
203-
" Φ-2 HANG: count={} put_cycle_at_hang={} | arms={} get_total={}",
204-
rustynes_core::rustynes_apu::abort_probe::HANG_COUNT.load(Relaxed),
205-
rustynes_core::rustynes_apu::abort_probe::HANG_PUT.load(Relaxed),
206-
rustynes_core::rustynes_apu::abort_probe::ARM_COUNT.load(Relaxed),
207-
rustynes_core::rustynes_apu::abort_probe::GET_TOTAL.load(Relaxed),
208-
);
209-
210-
use rustynes_core::rustynes_apu::abort_probe;
211-
// W3-Stage-4: how often the consume-edge-quantization suppression
212-
// latched (expected: ONLY the Implicit $540 X=8/9 restart races).
213-
println!(
214-
" EDGE_SUPPRESS_SET = {}",
215-
abort_probe::EDGE_SUPPRESS_SET.load(Relaxed),
216-
);
217-
// final lever #1: dump the boundary-cycle DMC state captures.
218-
let nd = (abort_probe::SUBPOS_DIAG_IDX.load(Relaxed) as usize).min(63);
219-
if nd > 0 {
220-
println!(" SUBPOS_DIAG blocks (prev2/prev1/cur per block), n={nd}:");
221-
for i in 0..nd {
222-
let p = abort_probe::SUBPOS_DIAG[i].load(Relaxed);
223-
let apu_phase = p & 1;
224-
let needs = (p >> 1) & 1;
225-
let buf = (p >> 2) & 1;
226-
let cannot = (p >> 4) & 0xFF;
227-
let bytes = (p >> 12) & 0xFFF;
228-
let bits = (p >> 24) & 0xFF;
229-
let timer = (p >> 32) & 0xFFFF;
230-
let tag = match i % 3 {
231-
0 => "prev2",
232-
1 => "prev1",
233-
_ => "CUR ",
234-
};
235-
println!(
236-
" [{i:>2}] {tag} apu_phase={apu_phase} timer={timer} bits={bits} bytes={bytes} buf={buf} needs_dma={needs} cannot_run={cannot}"
237-
);
238-
}
239-
}
240-
println!("\n--- explicit-DMC-abort scheduling probe (C-1 diagnostic) ---");
241-
for (label, v) in abort_probe::snapshot() {
242-
println!(" {label:<26} {v}");
243-
}
244-
// Φ-1: RustyNES $4010=$4E -> first $FFC0 GET offsets (TriCNES = 48).
245-
let no = (abort_probe::FFC0_OFF_IDX.load(Relaxed) as usize).min(64);
246-
let offs: Vec<u32> = (0..no)
247-
.map(|i| abort_probe::FFC0_OFF[i].load(Relaxed))
248-
.collect();
249-
println!(" Φ-1 $4E->$FFC0-GET offsets (n={no}, TriCNES=48): {offs:?}");
250-
// Φ-1 period: $FFC0 GET-to-GET (TriCNES CheckDMATiming region = 576).
251-
let np = (abort_probe::FFC0_PERIOD_IDX.load(Relaxed) as usize).min(128);
252-
let pers: Vec<u32> = (0..np)
253-
.map(|i| abort_probe::FFC0_PERIOD[i].load(Relaxed))
254-
.collect();
255-
println!(" Φ-1 $FFC0 reload PERIOD (n={np}, TriCNES=576): {pers:?}");
256-
// Φ-2: dump all $4000 reads (cycle,value) to CSV for the setup-window diff.
257-
let r4 = (abort_probe::R4000_IDX.load(Relaxed) as usize).min(4096);
258-
if let Ok(out) = std::env::var("RUSTYNES_R4000_LOG") {
259-
if !out.is_empty() {
260-
let mut s = String::from("cycle,val\n");
261-
for i in 0..r4 {
262-
let p = abort_probe::R4000_LOG[i].load(Relaxed);
263-
let _ = writeln!(s, "{},{:02X}", (p & 0xFFFF_FFFF) as u32, (p >> 32) as u8);
264-
}
265-
std::fs::write(&out, &s).expect("write r4000");
266-
println!(" Φ-2 $4000 reads: {r4} -> {out}");
267-
}
268-
}
269-
// Dump the per-$4015-write event log to CSV for trajectory analysis.
270-
let n = (abort_probe::EVLOG_IDX.load(Relaxed) as usize).min(abort_probe::EVLOG_CAP);
271-
let mut csv = String::from("idx,cpu_cycle,enable,bytes_remaining,timer\n");
272-
for i in 0..n {
273-
let cyc = abort_probe::EVLOG_CYCLE[i].load(Relaxed);
274-
let meta = abort_probe::EVLOG_META[i].load(Relaxed);
275-
let enable = (meta >> 31) & 1;
276-
let bytes = (meta >> 16) & 0x7FFF;
277-
let timer = meta & 0xFFFF;
278-
let _ = writeln!(csv, "{i},{cyc},{enable},{bytes},{timer}");
279-
}
280-
let out = std::env::var("RUSTYNES_ABORT_EVLOG").unwrap_or_default();
281-
if !out.is_empty() {
282-
std::fs::write(&out, &csv).expect("write evlog");
283-
println!(" evlog: {n} events -> {out}");
284-
}
285-
println!();
286-
}
287-
288195
println!(
289196
"CheckDMATiming Y (normal DMA, must == 4 to pass the abort gate): {}",
290197
check_dma_timing_y.map_or_else(|| "(unset)".to_string(), |y| format!("{y}"))

‎docs/STATUS.md‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,12 @@ pirate carts, niche boards) is documented in `docs/compatibility.md`.
903903

904904
## Feature flags
905905

906-
> **v2.0.1 update:** the entire `mc-r1-*` / `mc-ppu-*` family + `cpu-{stack,implied}-dummy-reads`
906+
> **v2.0.1 update:** the last diagnostic hold-over from the v2.0.0 Timebase work —
907+
> the DMC-DMA-abort probe `mc-r1-dmc-abort-probe` — has now been **removed** (it
908+
> gated only default-off pub-static atomic counters and a diagnostic bin block, no
909+
> shipped behaviour; the default build is byte-identical, AccuracyCoin unchanged).
910+
> Earlier in the v2.0.0 collapse, most of the `mc-r1-*` / `mc-ppu-*` family +
911+
> `cpu-{stack,implied}-dummy-reads` were already retired or unconditionalised:
907912
>
908913
> - the PPU-accuracy trio (`ppu-oam-data-bus`, `ppu-sprite-shifter-counter`,
909914
> `ppu-2002-read-end-flags`) + `accuracycoin-sprite-eval-base-from-oamaddr` are
@@ -913,8 +918,13 @@ pirate carts, niche boards) is documented in `docs/compatibility.md`.
913918
> `mc-r1-coldboot-ppuoffset`, `mc-r1-bp-ppu-offset`, `dmc-get-put-scheduler`) were
914919
> deleted. The rows below for any removed flag are **historical** (v2.0.0-era). The
915920
> flags that REMAIN: `std`, `serde`, `test-roms`, `commercial-roms`, `mapper-audio`,
916-
> `wasm-winit`/`wasm-canvas`, and the diagnostics `irq-timing-trace`,
917-
> `ppu-state-trace`, `cpu-boot-trace`, `cpu-instr-cycle-trace`, `mc-r1-dmc-abort-probe`.
921+
> `wasm-winit`/`wasm-canvas`, the diagnostics `irq-timing-trace`, `ppu-state-trace`,
922+
> `cpu-boot-trace`, `cpu-instr-cycle-trace`, and two default-off v2.0.1 experiments:
923+
> `mc-ppu-bus-addr-hybrid` (v2.0.1, ADR 0030 — the EXPERIMENTAL persistent-PPU-bus-
924+
> address model for the AccuracyCoin ALE-read pair; shipped build byte-identical
925+
> without it) and `mmc3-m2-phase-irq` (the still-open R1/R2 MMC3-IRQ-timing residual
926+
> experiment per ADR 0002 — retained because it gates real alternate behaviour and
927+
> the residual is by-design-deferred, not closed).
918928
919929
| Flag | Crate(s) | Default | Purpose |
920930
|------|----------|---------|---------|

‎docs/adr/0030-accuracycoin-ale-read-hybrid-addresses-octal-latch.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,31 @@ long-term branch — regardless of whether Option 2 succeeds** (per the maintain
181181
AccuracyCoin PPU tests"). Sequencing: **Option 2 (PR B3) first** — Hybrid Addresses before
182182
ALE+Read — then the **Option 1 branch** as the enduring physically-correct model. This ADR
183183
is updated (or superseded) when either track lands and the figure returns to 141/141.
184+
185+
## Update — 2026-07-08 (both bounded v2.0.1 attempts made; neither converged)
186+
187+
Both v2.0.1 tracks laid out in the Decision were attempted under a bounded effort, and
188+
**neither converged** — the honest **139/141 (98.58%)** figure stands as the v2.0.1 baseline:
189+
190+
- **Option 2 — Mesen2 persistent-bus-address model.** Landed as **PR #234** behind the
191+
default-off feature flag `mc-ppu-bus-addr-hybrid` (`rustynes-ppu` → forwarded through
192+
`rustynes-core` and `rustynes-test-harness`). It adds the persistent `bus_addr` register
193+
and the render-time `$2006`/`$2007` reroute, but flag-**on** it still reaches only **139/141**
194+
(both "ALE + Read" `$0491` and "Hybrid Addresses" `$0492` continue to fail): the implicit
195+
bus-address retention does not reproduce the sub-cycle octal-latch sequence the two tests
196+
require without also disturbing the currently-passing `$2007 Stress` path.
197+
- **Option 1 — 2-cycle-ALE fetch refactor.** Attempted as **draft PR #236** on branch
198+
`feat/v2.0.1-option1-2cycle-ale`. The physically-correct two-dot bus model likewise reaches
199+
only **139/141** flag-on within the bounded attempt — the fetch-cadence recalibration needed
200+
to light exactly the right pixels without shifting sprite-zero / A12 / MMC3 timing is not
201+
achievable as a bounded fork.
202+
203+
In **both** cases the flag-**OFF** (shipped) build is **byte-identical** to the v2.0.1
204+
AccuracyCoin re-sync baseline — **139/141, nestest 0-diff, AccuracyCoin otherwise 100%** —
205+
so the non-converging experiments never touch the shipped core. This empirically **confirms
206+
this ADR's central thesis**: the fix is not reachable by a bounded fork of either shape; it
207+
needs a **dedicated Timebase-scale campaign** that models the per-cycle PPU bus *inside* the
208+
one-clock scheduler (ADR 0029), calibrated against the vendored `ref-proj/Mesen2` per-cycle
209+
bus-stream cross-diff oracle and gated on the full regression battery. Until that campaign is
210+
scheduled, **139/141 is the honest v2.0.1 baseline** and both flags remain default-off
211+
experiments. The two draft branches are retained as the starting point for that campaign.

0 commit comments

Comments
 (0)