# Integration Guide This guide shows how to embed `nesemu` into a host application or frontend. For the stable API boundary, see `api_contract.md`. For internal structure, see `architecture.md`. ## Choose An Integration Level Use the lowest level that matches your needs: - `Cpu6502` + `NativeBus` Use this if you need fine-grained stepping or low-level control. - `NesRuntime` Use this if you want frame-oriented execution, rendering helpers, and runtime state handling. - `RuntimeHostLoop` Use this if your host runs the emulator frame-by-frame and wants explicit input, video, audio, and pacing control. - `ClientRuntime` Use this if your app has running/paused/step states and needs lifecycle-oriented ticking. ## Minimal ROM Load ```rust use nesemu::{create_mapper, parse_rom, Cpu6502, NativeBus}; let rom_bytes = std::fs::read("game.nes")?; let rom = parse_rom(&rom_bytes)?; let mapper = create_mapper(rom)?; let mut bus = NativeBus::new(mapper); let mut cpu = Cpu6502::default(); cpu.reset(&mut bus); ``` ## Using `NesRuntime` ```rust use nesemu::{FRAME_RGBA_BYTES, NesRuntime}; let rom_bytes = std::fs::read("game.nes")?; let mut runtime = NesRuntime::from_rom_bytes(&rom_bytes)?; runtime.run_until_frame_complete()?; let mut frame = vec![0; FRAME_RGBA_BYTES]; runtime.render_frame_rgba(&mut frame)?; ``` Use `NesRuntime` when you want: - frame-based stepping instead of raw CPU control - framebuffer extraction - runtime-level save/load state helpers ## Using `RuntimeHostLoop` `RuntimeHostLoop` is the main integration point for hosts that want explicit control over frame execution. ```rust use nesemu::{ AudioOutput, HostConfig, InputProvider, JOYPAD_BUTTONS_COUNT, NesRuntime, RuntimeHostLoop, VideoOutput, }; struct Input; impl InputProvider for Input { fn poll_buttons(&mut self) -> [bool; JOYPAD_BUTTONS_COUNT] { [false; JOYPAD_BUTTONS_COUNT] } } struct Video; impl VideoOutput for Video { fn present_rgba(&mut self, _frame: &[u8], _width: usize, _height: usize) {} } struct Audio; impl AudioOutput for Audio { fn push_samples(&mut self, _samples: &[f32]) {} } let rom_bytes = std::fs::read("game.nes")?; let runtime = NesRuntime::from_rom_bytes(&rom_bytes)?; let mut host = RuntimeHostLoop::with_config(runtime, HostConfig::new(48_000, false)); let mut input = Input; let mut video = Video; let mut audio = Audio; let stats = host.run_frame_unpaced(&mut input, &mut video, &mut audio)?; let _ = stats; ``` Use `run_frame` for paced execution and `run_frame_unpaced` when the host controls timing externally. ## Using `ClientRuntime` `ClientRuntime` wraps the runtime with a simple running/paused/step lifecycle. ```rust use nesemu::{ AudioOutput, ClientRuntime, EmulationState, HostConfig, InputProvider, JOYPAD_BUTTONS_COUNT, NesRuntime, VideoOutput, }; struct Input; impl InputProvider for Input { fn poll_buttons(&mut self) -> [bool; JOYPAD_BUTTONS_COUNT] { [false; JOYPAD_BUTTONS_COUNT] } } struct Video; impl VideoOutput for Video { fn present_rgba(&mut self, _frame: &[u8], _width: usize, _height: usize) {} } struct Audio; impl AudioOutput for Audio { fn push_samples(&mut self, _samples: &[f32]) {} } let rom_bytes = std::fs::read("game.nes")?; let runtime = NesRuntime::from_rom_bytes(&rom_bytes)?; let mut client = ClientRuntime::with_config(runtime, HostConfig::new(48_000, true)); client.set_state(EmulationState::Running); let mut input = Input; let mut video = Video; let mut audio = Audio; let _ = client.tick(&mut input, &mut video, &mut audio)?; client.pause(); client.step_frame(&mut input, &mut video, &mut audio)?; ``` Use this wrapper when your UI loop naturally switches between running, paused, and manual stepping. ## Input Mapping Public helpers are available to avoid hard-coded button indices: - `JoypadButton` - `JOYPAD_BUTTON_ORDER` - `set_button_pressed` - `button_pressed` Public button order is: `[Up, Down, Left, Right, A, B, Start, Select]` ## Framebuffer And Audio - Video frames are exposed as RGBA8 - Frame size is `256x240` - Audio output is a stream of mixed mono `f32` samples - The runtime mixer is usable for host integration, but it is intentionally interim ## Save-State Use Use runtime-level state when you need host-visible frame metadata and input state preserved alongside low-level emulation state. Use bus-level state if you are integrating at the low-level core boundary. ## Optional Adapter Crates If you want backend-agnostic adapter traits and headless implementations: ```toml [dependencies] nesemu = { path = "../nesemu", features = ["adapter-api", "adapter-headless"] } ``` Then: ```rust #[cfg(feature = "adapter-api")] use nesemu::adapter_api::{AudioSink, InputSource, TimeSource, VideoSink}; ```