diff --git a/crates/nesemu-desktop/src/main.rs b/crates/nesemu-desktop/src/main.rs index e1034d6..b7afa24 100644 --- a/crates/nesemu-desktop/src/main.rs +++ b/crates/nesemu-desktop/src/main.rs @@ -21,8 +21,6 @@ const TITLE: &str = "NES Emulator"; const SCALE: i32 = 3; const SAMPLE_RATE: u32 = 48_000; const AUDIO_RING_CAPACITY: usize = 4096; -const AUDIO_CALLBACK_FRAMES: u32 = 256; - fn main() { if std::env::var_os("GSK_RENDERER").is_none() { unsafe { @@ -161,7 +159,7 @@ fn build_ui(app: >k::Application, initial_rom: Option) { .expect("Failed to create Cairo surface"); // Fill background black - let _ = cr.set_source_rgb(0.0, 0.0, 0.0); + cr.set_source_rgb(0.0, 0.0, 0.0); let _ = cr.paint(); let sx = width as f64 / FRAME_WIDTH as f64; @@ -170,8 +168,8 @@ fn build_ui(app: >k::Application, initial_rom: Option) { let offset_x = (width as f64 - FRAME_WIDTH as f64 * scale) / 2.0; let offset_y = (height as f64 - FRAME_HEIGHT as f64 * scale) / 2.0; - let _ = cr.translate(offset_x, offset_y); - let _ = cr.scale(scale, scale); + cr.translate(offset_x, offset_y); + cr.scale(scale, scale); let _ = cr.set_source_surface(&surface, 0.0, 0.0); cr.source().set_filter(cairo::Filter::Nearest); let _ = cr.paint(); @@ -249,16 +247,16 @@ fn build_ui(app: >k::Application, initial_rom: Option) { let scheduler = Rc::clone(&scheduler); let sync_ui = Rc::clone(&sync_ui); chooser.connect_response(move |dialog, response| { - if response == gtk::ResponseType::Accept { - if let Some(path) = dialog.file().and_then(|f| f.path()) { - let mut app_state = desktop.borrow_mut(); - if let Err(err) = app_state.load_rom_from_path(&path) { - eprintln!("Failed to load ROM '{}': {err}", path.display()); - } else { - scheduler.borrow_mut().reset_timing(); - let name = rom_filename(&path); - sync_ui(&app_state, Some(&name)); - } + if response == gtk::ResponseType::Accept + && let Some(path) = dialog.file().and_then(|f| f.path()) + { + let mut app_state = desktop.borrow_mut(); + if let Err(err) = app_state.load_rom_from_path(&path) { + eprintln!("Failed to load ROM '{}': {err}", path.display()); + } else { + scheduler.borrow_mut().reset_timing(); + let name = rom_filename(&path); + sync_ui(&app_state, Some(&name)); } } }); @@ -370,18 +368,18 @@ fn build_ui(app: >k::Application, initial_rom: Option) { let sync_ui = Rc::clone(&sync_ui); let drop_target = gtk::DropTarget::new(gio::File::static_type(), gdk::DragAction::COPY); drop_target.connect_drop(move |_, value, _, _| { - if let Ok(file) = value.get::() { - if let Some(path) = file.path() { - let mut app_state = desktop.borrow_mut(); - if let Err(err) = app_state.load_rom_from_path(&path) { - eprintln!("Failed to load ROM '{}': {err}", path.display()); - return false; - } - scheduler.borrow_mut().reset_timing(); - let name = rom_filename(&path); - sync_ui(&app_state, Some(&name)); - return true; + if let Ok(file) = value.get::() + && let Some(path) = file.path() + { + let mut app_state = desktop.borrow_mut(); + if let Err(err) = app_state.load_rom_from_path(&path) { + eprintln!("Failed to load ROM '{}': {err}", path.display()); + return false; } + scheduler.borrow_mut().reset_timing(); + let name = rom_filename(&path); + sync_ui(&app_state, Some(&name)); + return true; } false }); @@ -716,7 +714,6 @@ impl DesktopApp { Err(err) => { eprintln!("Frame execution error: {err}"); self.state = EmulationState::Paused; - return; } } } diff --git a/docs/api_contract.md b/docs/api_contract.md index 8b157ff..5297468 100644 --- a/docs/api_contract.md +++ b/docs/api_contract.md @@ -35,8 +35,13 @@ The main public API is organized around these groups: - `CpuBus` - `CpuError` - `NativeBus` + - `Ppu` + - `Apu` + - `ApuStateTail` + - `ChannelOutputs` - High-level runtime: - `NesRuntime` + - `RuntimeError` - Host execution and lifecycle: - `RuntimeHostLoop` - `ClientRuntime` @@ -46,12 +51,23 @@ The main public API is organized around these groups: - `InputProvider` - `VideoOutput` - `AudioOutput` +- Null/stub implementations: + - `NullInput` + - `NullVideo` + - `NullAudio` +- Audio helpers: + - `AudioMixer` + - `RingBuffer` - Timing and pacing: - `FrameClock` - `FramePacer` - `PacingClock` - `NoopClock` - `VideoMode` +- Video constants: + - `FRAME_WIDTH` + - `FRAME_HEIGHT` + - `FRAME_RGBA_BYTES` - Input helpers: - `JoypadButton` - `JoypadButtons` @@ -59,6 +75,8 @@ The main public API is organized around these groups: - `JOYPAD_BUTTONS_COUNT` - `set_button_pressed` - `button_pressed` +- State versioning: + - `SAVE_STATE_VERSION` ## Supported Client Flow diff --git a/docs/architecture.md b/docs/architecture.md index a9d1244..5e562e4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,6 +41,10 @@ The workspace is split into four layers: - `src/runtime/audio.rs`: interim PCM synthesis from core state - `src/runtime/timing.rs`: frame pacing types and video timing - `src/runtime/types.rs`: public joypad-related types and helpers +- `src/runtime/constants.rs`: frame dimensions and video constants +- `src/runtime/error.rs`: `RuntimeError` type definitions +- `src/runtime/ring_buffer.rs`: lock-free ring buffer for audio sample transport +- `src/runtime/adapters.rs`: adapter bridge types (behind `adapter-api` feature) - `src/runtime/host/io.rs`: host IO traits and null implementations - `src/runtime/host/executor.rs`: per-frame execution unit - `src/runtime/host/clock.rs`: clock abstraction and pacing implementations diff --git a/src/native_core/mapper/mappers/mmc5.rs b/src/native_core/mapper/mappers/mmc5.rs index 257c126..cf12a70 100644 --- a/src/native_core/mapper/mappers/mmc5.rs +++ b/src/native_core/mapper/mappers/mmc5.rs @@ -530,11 +530,11 @@ impl Mapper for Mmc5 { cursor += 2; } } - if cursor + 1 <= data.len() { + if cursor < data.len() { self.irq_scanline_counter = data[cursor]; cursor += 1; } - if cursor + 1 <= data.len() { + if cursor < data.len() { self.sprite_8x16 = data[cursor] != 0; cursor += 1; }