diff --git a/crates/rustynes-apu/src/apu.rs b/crates/rustynes-apu/src/apu.rs index baa32e51..42fbebe4 100644 --- a/crates/rustynes-apu/src/apu.rs +++ b/crates/rustynes-apu/src/apu.rs @@ -176,7 +176,6 @@ pub struct Apu { /// is suppressed and this decrements; at 0 the arm fires. Default 0. // (W3-Stage-3: also dead under `mc-r1-dmc-delayed-4015`, which supersedes // the halt-subpos pre-arm with the emergent consume-edge arm.) - #[allow(dead_code)] pub(crate) subpos_arm_countdown: u8, /// Latches the one-byte looping edge where the next reload request is /// lost because it is raised too soon after a DMA get. Cleared when a @@ -326,14 +325,6 @@ pub const CHANNEL_MASK_ALL: u8 = 0x3F; pub const CHANNEL_GAIN_UNITY: [f32; 6] = [1.0; 6]; impl Apu { - const fn dmc_abort_delay_for(cycles_until_output: u16) -> Option { - match cycles_until_output { - 2 => Some(2), - 3 => Some(3), - _ => None, - } - } - /// New APU. #[must_use] pub fn new(region: Region, sample_rate: u32) -> Self { @@ -1939,33 +1930,6 @@ impl Apu { } } - // W3-Stage-3: under `mc-r1-dmc-delayed-4015` the explicit abort is - // emergent from the delayed-status service gate, so this floor scheduling - // compensation has no caller (superseded, retained for the flag-off path). - #[allow(dead_code)] - fn schedule_explicit_dmc_abort_if_needed(&mut self) { - if self.dmc.bits_remaining != 1 || self.dmc.sample_buffer.is_none() { - return; - } - // `first_apu_clock` is "CPU cycles until the next APU (DMC) clock". The - // DMC clocks on `apu_phase`-true, so `apu_phase ? 2 : 1` is correct. - let first_apu_clock = if self.apu_phase { 2 } else { 1 }; - let cycles_until_output = first_apu_clock + self.dmc.timer.saturating_mul(2); - if cycles_until_output == 1 { - self.pending_dmc_dma = true; - self.dmc_dma_is_load = false; - self.dmc_dma_short = false; - self.dmc_dma_addr = self.dmc.dma_addr(); - self.dmc_need_halt = true; - self.dmc_need_dummy_read = true; - self.pending_dmc_abort = true; - return; - } - if let Some(delay) = Self::dmc_abort_delay_for(cycles_until_output) { - self.dmc_abort_delay = delay; - } - } - /// CPU register read (only `$4015` is meaningful). Reading clears the /// frame IRQ flag. pub fn read_status(&mut self) -> u8 { diff --git a/crates/rustynes-core/src/bus.rs b/crates/rustynes-core/src/bus.rs index 52ad8cc3..410ece9f 100644 --- a/crates/rustynes-core/src/bus.rs +++ b/crates/rustynes-core/src/bus.rs @@ -326,7 +326,6 @@ pub struct LockstepBus { /// APU instance. pub(crate) apu: Apu, /// Cartridge metadata (kept for save-state and debugger). - #[allow(dead_code)] pub(crate) cart: Cartridge, /// Boxed mapper. pub(crate) mapper: Box, @@ -3219,76 +3218,6 @@ impl LockstepBus { self.last_nmi_level = level; } - /// Drain any cycles owed to the DMA controllers (OAM DMA + DMC DMA) - /// before completing a CPU access. - /// - /// Called from `cpu_read` and `cpu_write`. DMC DMA preempts OAM DMA per - /// nesdev: while OAM DMA is running and a DMC DMA also fires, the DMC - /// fetch happens "between the dummy and DMA reads" of the OAM DMA, - /// stalling the OAM transfer for 3-4 extra cycles. Our simpler model - /// services DMC DMA at the start of each cycle the bus controls; if - /// OAM DMA is in flight, the DMC DMA inserts itself between transfer - /// pairs. - // Under `mc-r1-full-cpu` the body reduces to the DMC-abort/idle handling; - // OAM moved to the CPU-driven `oam_dma_step`, so `&mut self`/`read_addr` are - // only lightly used here — silence the resulting lints under the flag. - #[allow( - clippy::unused_self, - clippy::needless_pass_by_ref_mut, - clippy::missing_const_for_fn - )] - fn drain_dma(&mut self, read_addr: Option) { - // Stage-D: under `mc-r1-full-cpu` the OAM DMA is CPU-driven (read1), so - // the legacy OAM block below (the only user of `read_addr` once the - // abort-cancel path owns aborts) is cfg'd out — silence the param. - let _ = read_addr; - // Sprint 3 iter 3 — under the `dmc-get-put-scheduler` - // feature, the abort is handled INSIDE `service_dmc_dma` / - // `service_dmc_dma_during_oam` (matching Mesen2's unified - // `RunDma` loop where the `processCycle` lambda checks - // `_abortDmcDma` per iteration). The pre-service abort - // call is preserved on the default-off path. - // accuracycoin-100 Phase 2: under `mc-r1-dmc-abort-cancel` the R1 - // read1/write1 path OWNS the abort (get-cycle 1-halt Y=1 / put-write - // cancel Y=0). Skip the legacy `drain_dma` service — `drain_dma(None)` - // runs every R1 `cpu_clock` cycle and would `complete_dmc_abort` the - // pending abort BEFORE the read1 hook can see it (the inert-as-placed - // bug). The legacy service below stays active for the default build. - // Stage-D (`mc-r1-full-cpu`): OAM DMA is CPU-driven in `read1` - // (`oam_dma_step`), NOT bus-side burst — leave `dma_pending` set for the - // read1 loop to consume; drain_dma does no OAM work under the flag. - } - - fn clock_oam_dma_cycle(&mut self, total: u32, alignment: u32) { - let consumed = total - self.dma_cycles_owed; // 0, 1, ... - if consumed < alignment { - // OAM DMA halt / alignment cycle — bus idle for the CPU, - // but the DMA controller owns it. Trace as DmaRead with - // the halted CPU read address so the trace shows what the - // open-bus latch ended up driving (Session-21). - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, self.dma_halt_addr, self.open_bus); - self.tick_one_cpu_cycle(); - self.dma_cycles_owed -= 1; - return; - } - let xfer_idx = consumed - alignment; // 0..512 - // Even xfer index: read; odd: write. - if xfer_idx & 1 == 0 { - let src_addr = - (u16::from(self.dma_page) << 8) | u16::try_from(xfer_idx >> 1).unwrap_or(0); - self.dma_byte = self.raw_oam_dma_read(src_addr); - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, src_addr, self.dma_byte); - } else { - self.oam_dma_put(); - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaWrite, 0x2004, self.dma_byte); - } - self.tick_one_cpu_cycle(); - self.dma_cycles_owed -= 1; - } - /// OAM-DMA source fetch (Session-26 / Sprint 2 iter 4). /// /// The 2A03 has three internal address buses (6502, OAM DMA, DMC @@ -3464,133 +3393,6 @@ impl LockstepBus { self.trace_bus_data = data; } - /// Service one DMC DMA transfer. - /// - /// Per nesdev §DMC DMA: - /// - Halt cycle (1 CPU cycle). - /// - Dummy cycle (1 CPU cycle). - /// - Optional alignment cycle if the DMA get would otherwise land on - /// a put cycle. - /// - One memory-read/get cycle. - /// - /// While CPU is halted, the previous read is logically repeated. For - /// `$4015` / `$4016` / `$4017` / `$2007` this has the documented - /// register-readout side-effect bug; PAL fixes it. - /// - /// v1.2 Sprint 3.2 — two implementations now coexist via the - /// `dmc-get-put-scheduler` cargo feature (ADR-0007). With the - /// flag OFF, the v1.0/v1.1 baseline ("phase-agnostic noop loop + - /// compensating delays") is preserved bit-identically — the four - /// delays `dmc_dma_short`, `dmc_dma_cooldown`, `dmc_abort_delay`, - /// `dmc_dma_delay` are still load-bearing. With the flag ON, the - /// new path uses Mesen2's get/put cycle alternation model - /// (`NesCpu.cpp:399-450`) — `dmc_need_halt` and - /// `dmc_need_dummy_read` on the APU are consumed cycle-by-cycle, - /// and the four compensating delays are NO-OPS under the new - /// model. The new path closes the cycle-2 implied-dummy-read - /// cascade that 6 prior single-delay tweaks could not. - // Phase B: the DMC burst is CPU-driven-interleaved under R1, so this - // bus-side burst is unused there (still used on the default path). - #[allow(dead_code)] - fn service_dmc_dma(&mut self, halted_addr: u16) { - if !self.apu.dmc_dma_pending() || self.in_dmc_dma { - return; - } - let addr = self.apu.dmc_dma_addr(); - let noop_cycles = if self.apu.dmc_dma_short() { 2 } else { 3 }; - self.in_dmc_dma = true; - self.capture_deferred_dma_replay(); - - for _ in 0..noop_cycles { - self.replay_dma_noop_read(halted_addr); - // Session-21: tag DMC halt/dummy/align cycles as DmaRead with - // the halted CPU address (which is what the open-bus latch - // sees on real silicon during those cycles). - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, halted_addr, self.open_bus); - self.tick_one_cpu_cycle(); - } - - // Perform the actual sample read/get and deliver back to the APU. - let byte = self.dmc_dma_read(addr, halted_addr); - if self.apu.dmc_dma_deliver_before_tick() { - self.apu.complete_dmc_dma_before_get_tick(byte); - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, addr, byte); - self.tick_one_cpu_cycle(); - } else { - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, addr, byte); - self.tick_one_cpu_cycle(); - self.apu.complete_dmc_dma(byte); - } - self.in_dmc_dma = false; - } - - #[allow(dead_code)] - fn service_dmc_abort(&mut self, halted_addr: u16) { - if !self.apu.dmc_abort_pending() || self.in_dmc_dma { - return; - } - self.in_dmc_dma = true; - self.replay_dma_noop_read(halted_addr); - // Session-21: abort halt cycle is observable from the bus as a - // DmaRead of the halted CPU address (open-bus driver retained). - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, halted_addr, self.open_bus); - self.tick_one_cpu_cycle(); - self.apu.complete_dmc_abort(); - self.in_dmc_dma = false; - } - - #[allow(dead_code)] - fn service_dmc_dma_during_oam(&mut self, total: u32, alignment: u32) { - if !self.apu.dmc_dma_pending() || self.in_dmc_dma { - return; - } - let addr = self.apu.dmc_dma_addr(); - let noop_cycles = if self.apu.dmc_dma_short() { 2 } else { 3 }; - let halted_addr = self.dma_halt_addr; - self.in_dmc_dma = true; - self.capture_deferred_dma_replay(); - - // DMC halt, dummy, and alignment no-op cycles overlap with OAM DMA. - // The 6502 core remains halted, but OAM can keep consuming its own - // read/write slots on those cycles. - for _ in 0..noop_cycles { - self.replay_dma_noop_read(halted_addr); - if self.dma_cycles_owed > 0 { - // clock_oam_dma_cycle owns its own trace tagging. - self.clock_oam_dma_cycle(total, alignment); - } else { - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, halted_addr, self.open_bus); - self.tick_one_cpu_cycle(); - } - } - - // The actual DMC get owns the memory read cycle. If OAM still has a - // transfer pending, this skips one OAM slot and forces the next OAM - // read to realign on a later get cycle. - let byte = self.dmc_dma_read(addr, halted_addr); - let deliver_before_tick = self.apu.dmc_dma_deliver_before_tick(); - if deliver_before_tick { - self.apu.complete_dmc_dma_before_get_tick(byte); - } - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, addr, byte); - self.tick_one_cpu_cycle(); - if self.dma_cycles_owed > 0 { - #[cfg(feature = "irq-timing-trace")] - self.set_trace_dma_access(BusAccess::DmaRead, halted_addr, self.open_bus); - self.tick_one_cpu_cycle(); - } - if !deliver_before_tick { - self.apu.complete_dmc_dma(byte); - } - self.in_dmc_dma = false; - } - const fn capture_deferred_dma_replay(&mut self) { self.deferred_dma_replay_addr = match self.open_bus { 0x02 => 0x2002, @@ -4249,8 +4051,6 @@ impl LockstepBus { impl Bus for LockstepBus { fn cpu_read(&mut self, addr: u16) -> u8 { - // Drain any pending DMA before doing the requested access. - self.drain_dma(Some(addr)); if self.deferred_dma_replay_addr != 0 && self.open_bus == (self.deferred_dma_replay_addr >> 8) as u8 { @@ -4318,7 +4118,6 @@ impl Bus for LockstepBus { } fn cpu_write(&mut self, addr: u16, value: u8) { - self.drain_dma(None); self.open_bus = value; // Mirror the CPU-initiated write onto the internal data bus. // Symmetric with `raw_cpu_read`'s mirror — DMC DMA does not @@ -4716,7 +4515,6 @@ impl Bus for LockstepBus { self.commit_controller_strobe(value); } } - self.drain_dma(None); self.cycle = self.cycle.wrapping_add(1); self.ppu.on_cpu_cycle(); // v2.8.0 Phase 4 — skip the virtual dispatch on boards whose diff --git a/crates/rustynes-frontend/src/audio.rs b/crates/rustynes-frontend/src/audio.rs index 560f6504..561d92e9 100644 --- a/crates/rustynes-frontend/src/audio.rs +++ b/crates/rustynes-frontend/src/audio.rs @@ -237,7 +237,6 @@ impl SampleQueue { /// v1.0.0 — the current master output gain. #[must_use] - #[allow(dead_code)] // read in tests + as a UI mirror. pub fn gain(&self) -> f32 { #[allow(clippy::cast_possible_truncation)] // low 32 bits hold the f32. f32::from_bits(self.inner.gain.load(Ordering::Relaxed) as u32) @@ -448,7 +447,6 @@ impl SampleQueue { } /// Number of buffered samples (racy snapshot; informational). - #[allow(dead_code)] // Used by tests + the Performance panel. pub fn len(&self) -> usize { let tail = self.inner.tail.load(Ordering::Acquire); let head = self.inner.head.load(Ordering::Acquire); @@ -456,21 +454,18 @@ impl SampleQueue { } /// True if the queue is empty. - #[allow(dead_code)] // Used by tests + the Performance panel. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Total samples dropped at the ring cap / by hard resync so far /// (overruns). v2.8.0 health counter for the Performance panel. - #[allow(dead_code)] // wasm builds render the panel without native audio. pub fn overrun_dropped(&self) -> u64 { self.inner.overrun_dropped.load(Ordering::Relaxed) } /// Total short callback fills (underruns) so far. v2.8.0 health counter /// for the Performance panel. - #[allow(dead_code)] // wasm builds render the panel without native audio. pub fn underruns(&self) -> u64 { self.inner.underruns.load(Ordering::Relaxed) } @@ -596,7 +591,6 @@ pub struct AudioOutput { pub sample_rate: u32, /// Number of channels (we render mono, but duplicate to fill stereo). /// Informational; the duplication happens inside the audio callback. - #[allow(dead_code)] pub channels: u16, /// Producer-side queue handle (push from the emulator thread). pub queue: SampleQueue, @@ -619,7 +613,6 @@ pub struct AudioOutput { impl AudioOutput { /// Open the default output device with the pre-v2.8.0 defaults (device /// default rate, 60 ms latency target, DRC on). Kept for tests. - #[allow(dead_code)] pub fn try_default() -> Result { Self::try_new(None, 60, true, None) } @@ -830,7 +823,6 @@ impl AudioOutput { } /// The latency target in samples (Performance panel readout). - #[allow(dead_code)] #[must_use] pub const fn latency_target_samples(&self) -> usize { self.latency_samples diff --git a/crates/rustynes-frontend/src/config.rs b/crates/rustynes-frontend/src/config.rs index c8ce40f8..9bb328dc 100644 --- a/crates/rustynes-frontend/src/config.rs +++ b/crates/rustynes-frontend/src/config.rs @@ -2104,7 +2104,6 @@ impl Config { /// # Errors /// /// Returns [`ConfigError`] on I/O or serialization failure. - #[allow(dead_code)] pub fn save(&self) -> Result<(), ConfigError> { let Some(path) = Self::default_path() else { return Ok(()); @@ -2117,7 +2116,6 @@ impl Config { /// # Errors /// /// Returns [`ConfigError`] on I/O or serialization failure. - #[allow(dead_code)] pub fn save_to(&self, path: &Path) -> Result<(), ConfigError> { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; diff --git a/crates/rustynes-frontend/src/debugger/mod.rs b/crates/rustynes-frontend/src/debugger/mod.rs index 0e0ad872..fd6ed8c1 100644 --- a/crates/rustynes-frontend/src/debugger/mod.rs +++ b/crates/rustynes-frontend/src/debugger/mod.rs @@ -834,14 +834,12 @@ pub struct DebuggerOverlay { /// v1.7.0 "Forge" beta.5 (#55) — the toolbar HUD that displayed this was /// removed; the status bar shows FPS from its own `ShellFrame`. The setter /// + field are retained for the stable public API (and future panels). - #[allow(dead_code)] fps: f32, /// v1.4.0 Sprint 4.2 — current TAS movie record/playback status, pushed by /// [`DebuggerOverlay::set_movie_status`] from the pacing loop. v1.7.0 /// "Forge" beta.5 (#55) — retained for the public API after the toolbar HUD /// that displayed it was removed (movie state is now in the menu + status /// bar). - #[allow(dead_code)] movie: MovieStatus, /// v2.7.1 — `RetroAchievements` badge-image cache (achievement icons in the /// panel rows + unlock toasts). Lazily created the first time a badge URL is diff --git a/crates/rustynes-frontend/src/gfx.rs b/crates/rustynes-frontend/src/gfx.rs index 3cff7ded..66e0dd65 100644 --- a/crates/rustynes-frontend/src/gfx.rs +++ b/crates/rustynes-frontend/src/gfx.rs @@ -930,7 +930,6 @@ impl Gfx { } /// Disable the NTSC filter (skip the post-pass). - #[allow(dead_code)] pub fn disable_ntsc(&mut self) { self.ntsc = None; } @@ -988,7 +987,6 @@ impl Gfx { } /// Disable the CRT filter (skip the post-pass). - #[allow(dead_code)] pub fn disable_crt(&mut self) { self.crt = None; } diff --git a/crates/rustynes-frontend/src/input.rs b/crates/rustynes-frontend/src/input.rs index 6f29e3a6..41200643 100644 --- a/crates/rustynes-frontend/src/input.rs +++ b/crates/rustynes-frontend/src/input.rs @@ -857,7 +857,6 @@ impl InputState { // Used by the unit tests; the binary always goes through // [`Self::from_config`] with config-loaded bindings. #[must_use] - #[allow(dead_code)] pub fn with_defaults() -> Self { Self::from_config(&InputConfig::default()) } diff --git a/crates/rustynes-frontend/src/save_state.rs b/crates/rustynes-frontend/src/save_state.rs index 24c226de..97d97a5c 100644 --- a/crates/rustynes-frontend/src/save_state.rs +++ b/crates/rustynes-frontend/src/save_state.rs @@ -115,11 +115,13 @@ pub fn load_from_slot( } /// `true` if a slot file exists. -// -// Sprint 5-3 will surface this in the egui modal ("recently used slots" -// indicator). We allow `dead_code` rather than wait to land it. +/// +/// Part of this module's public API, with no in-crate caller today: the +/// "recently used slots" indicator an early note anticipated was never built. +/// It needs no `#[allow(dead_code)]` and never did -- `pub` items in a lib +/// target are exempt from dead-code analysis. Exercised by +/// `slot_exists_returns_true_only_after_save`. #[must_use] -#[allow(dead_code)] pub fn slot_exists(data_dir: &Path, rom_sha256: &[u8; 32], slot: u8) -> bool { slot_path(data_dir, rom_sha256, slot).is_ok_and(|p| p.is_file()) } @@ -146,7 +148,6 @@ pub struct SlotMeta { /// # Errors /// /// Returns [`SaveError`] for an invalid slot index. -#[allow(dead_code)] // used by the native Save-States window only. pub fn slot_meta( data_dir: &Path, rom_sha256: &[u8; 32], diff --git a/crates/rustynes-frontend/src/save_states_ui.rs b/crates/rustynes-frontend/src/save_states_ui.rs index 2c7b18dd..1745b406 100644 --- a/crates/rustynes-frontend/src/save_states_ui.rs +++ b/crates/rustynes-frontend/src/save_states_ui.rs @@ -308,8 +308,11 @@ fn format_modified(modified: Option) -> String { } } -/// The data-dir slot path, re-exported for the app to log / inspect. -#[allow(dead_code)] +/// The data-dir slot path, for callers that need to log or inspect it. +/// +/// A thin `Option`-returning wrapper over `save_state::slot_path`. It has no +/// in-repo call site and is part of this module's public surface rather than an +/// unreachable internal, which is why it carries no dead-code attribute. pub fn slot_path_for(data_dir: &Path, rom_sha256: &[u8; 32], slot: u8) -> Option { save_state::slot_path(data_dir, rom_sha256, slot).ok() } diff --git a/crates/rustynes-ppu/src/ppu.rs b/crates/rustynes-ppu/src/ppu.rs index 3643a65c..4eb1b36e 100644 --- a/crates/rustynes-ppu/src/ppu.rs +++ b/crates/rustynes-ppu/src/ppu.rs @@ -591,7 +591,6 @@ pub struct Ppu { pub(crate) oam: Box<[u8]>, /// Secondary OAM: up to 8 sprites for the next scanline. Populated /// during sprite evaluation in Sprint 2-3. - #[allow(dead_code)] pub(crate) secondary_oam: [u8; 32], /// Palette RAM: 32 entries, 6-bit each (high 2 bits open-bus on read). pub(crate) palette_ram: [u8; 32],