RustyNES v0.8.6 - Sub-Cycle Accuracy Improvements (M11 S3-S5)
Hardware accuracy milestone - Implements DMC DMA cycle stealing, open bus behavior, and per-cycle mapper clocking. M11 83% complete.
Highlights
- DMC DMA Cycle Stealing: CPU stalls 4 cycles per DMC sample fetch (matches hardware)
- Open Bus Behavior: Tracks data bus state for unmapped memory reads
- Controller Open Bus: Bits 5-7 properly mixed from open bus with controller data
- Per-Cycle Mapper Clocking: Mapper IRQs now trigger at cycle-precise timing
What's Changed
DMC DMA Cycle Stealing
The DMC audio channel now properly steals CPU cycles when fetching samples:
// When DMC needs a sample, CPU stalls for 4 cycles
// This affects timing-sensitive code that runs during audio playback
bus.dmc_stall_cycles = 4;Games that rely on precise timing during DMC playback will now behave correctly.
Open Bus Behavior
Implemented last_bus_value tracking for hardware-accurate unmapped memory reads:
// All memory operations now track the bus state
fn read(&mut self, addr: u16) -> u8 {
let value = self.internal_read(addr);
self.last_bus_value = value; // Track for open bus
value
}
fn write(&mut self, addr: u16, value: u8) {
self.last_bus_value = value; // Track for open bus
self.internal_write(addr, value);
}Reads from unmapped addresses return the last value on the data bus, matching NES hardware behavior.
Controller Open Bus
Controller reads now properly mix open bus bits with controller data:
// Controller returns: bits 0-4 from controller, bits 5-7 from open bus
let controller_bits = self.controller.read() & 0x1F;
let open_bus_bits = self.last_bus_value & 0xE0;
controller_bits | open_bus_bitsThis fixes games that use bits 5-7 of controller port reads for copy protection or other purposes.
Per-Cycle Mapper Clocking
Mappers are now clocked on every CPU cycle via on_cpu_cycle():
fn on_cpu_cycle(&mut self) {
// Clock mapper for cycle-accurate IRQ timing
self.bus.mapper.clock(1);
// Step PPU 3 dots
self.bus.ppu.step();
self.bus.ppu.step();
self.bus.ppu.step();
}This improves IRQ timing precision for mappers like MMC3 that use scanline counters.
Quality Metrics
- Tests: 522+ passing (0 failures, 1 ignored doctest)
- Blargg Pass Rate: 100% (90/90 tests)
- New Tests: 2 open bus behavior tests added
- M11 Progress: Sprints 3-5 complete (83%)
M11 Sub-Cycle Accuracy Milestone
| Sprint | Focus | Status |
|---|---|---|
| S1 | CPU cycle callback infrastructure | Complete |
| S2 | PPU synchronization per memory access | Complete |
| S3 | APU/DMC cycle stealing | Complete |
| S4 | Open bus behavior | Complete |
| S5 | Mapper cycle clocking | Complete |
| S6 | Final validation | Pending |
Technical Specifications
| Feature | Implementation |
|---|---|
| DMC DMA Stall | 4 cycles per sample fetch |
| Open Bus Decay | Instant (no capacitance simulation) |
| Controller Open Bus | Bits 5-7 from last bus value |
| Mapper Clock | 1 tick per CPU cycle |
Installation
Pre-built Binaries
Download the appropriate binary for your platform from the Assets section below.
From Source
git clone https://github.com/doublegate/RustyNES.git
cd RustyNES
cargo build --releaseUpgrading from v0.8.5
Recommended update - Significant hardware accuracy improvements. Drop-in replacement with no configuration changes required.
Full Changelog: v0.8.5...v0.8.6