Some checks failed
CI / rust (push) Has been cancelled
Full NES emulation: CPU, PPU, APU, 47 mappers, iNES/NES 2.0 parsing. GTK4 desktop client with HeaderBar, pixel-perfect Cairo rendering, drag-and-drop ROM loading, and keyboard shortcuts. 187 tests covering core emulation, mappers, and runtime.
108 lines
4.5 KiB
Markdown
108 lines
4.5 KiB
Markdown
# Architecture
|
|
|
|
This document describes how the workspace is organized internally.
|
|
|
|
Use `../README.md` for project overview, `integration.md` for host integration, and `api_contract.md` for supported public surface.
|
|
|
|
## Workspace Layout
|
|
|
|
- `nesemu`: reusable emulation library and host-facing runtime wrappers
|
|
- `crates/nesemu-adapter-api`: backend-agnostic adapter traits
|
|
- `crates/nesemu-adapter-headless`: headless/null adapter implementations
|
|
- `crates/nesemu-desktop`: GTK4 desktop frontend that consumes the root crate
|
|
|
|
## High-Level Layers
|
|
|
|
The workspace is split into four layers:
|
|
|
|
1. `native_core`
|
|
Owns emulation correctness and hardware-facing behavior.
|
|
2. `runtime`
|
|
Wraps the core with host-oriented execution, pacing, lifecycle control, and save-state helpers.
|
|
3. adapter crates
|
|
Define integration edges without coupling the core to a concrete backend.
|
|
4. desktop frontend
|
|
Serves as a consumer and manual test harness, not as part of the library contract.
|
|
|
|
## Core Module Boundaries
|
|
|
|
- `src/native_core/cpu`: 6502 execution, addressing helpers, opcode dispatch
|
|
- `src/native_core/ppu`: rendering pipeline, VRAM/OAM/register behavior
|
|
- `src/native_core/apu`: timing, channel state, and audio-facing hardware state
|
|
- `src/native_core/bus`: component wiring and device-visible read/write semantics
|
|
- `src/native_core/mapper`: cartridge mapper abstraction and concrete implementations
|
|
- `src/native_core/ines`: iNES parsing and ROM metadata
|
|
- `src/native_core/state_io`: shared state decoding helpers
|
|
|
|
## Runtime Module Boundaries
|
|
|
|
- `src/runtime/core.rs`: `NesRuntime` orchestration around CPU + bus
|
|
- `src/runtime/state.rs`: runtime save/load state format
|
|
- `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/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
|
|
- `src/runtime/host/loop_runner.rs`: host loop wrapper for frame-based execution
|
|
- `src/runtime/host/session.rs`: app lifecycle wrapper for running, pausing, and stepping
|
|
|
|
## Data Flow
|
|
|
|
At a high level, the runtime stack looks like this:
|
|
|
|
1. ROM bytes are parsed into cartridge metadata and ROM contents.
|
|
2. A mapper is created from the ROM description.
|
|
3. `NativeBus` wires CPU, PPU, APU, mapper, and input-visible state together.
|
|
4. `Cpu6502` executes against the bus.
|
|
5. `NesRuntime` wraps the core to provide frame-level execution, rendering, and save-state helpers.
|
|
6. `RuntimeHostLoop` and `ClientRuntime` adapt the runtime to host application control flow.
|
|
|
|
## Public Surface Strategy
|
|
|
|
The root crate re-exports the integration-critical API so external users do not need to depend on the internal module layout.
|
|
|
|
The design intent is:
|
|
|
|
- external clients use root re-exports and `runtime`
|
|
- advanced clients may use `native_core`
|
|
- internal module paths are free to evolve faster than the root surface
|
|
|
|
## Host Responsibilities
|
|
|
|
The library intentionally leaves these concerns to the host application:
|
|
|
|
- windowing and presentation backend
|
|
- audio device/output backend
|
|
- platform input mapping
|
|
- ROM file I/O
|
|
- persistent state storage
|
|
|
|
## Input, Video, and Audio Contracts
|
|
|
|
- `JoypadButtons` are exposed in the public order `[Up, Down, Left, Right, A, B, Start, Select]`
|
|
- `InputProvider` polls the current button state from the host
|
|
- `VideoOutput` receives RGBA frames
|
|
- `AudioOutput` receives mixed mono samples
|
|
- port 2 is currently treated as disconnected in the exposed core API
|
|
|
|
## Save-State Design
|
|
|
|
- `NativeBus::save_state` and `NativeBus::load_state` persist low-level emulator state
|
|
- `NesRuntime` extends that state with runtime metadata such as frame number and active buttons
|
|
- save-state payloads are versioned for crate-internal use, not for long-term external compatibility
|
|
|
|
## Testing Layout
|
|
|
|
- CPU tests are grouped by behavior, interrupts, and invariants
|
|
- mapper tests are grouped by mapper family and property-style checks
|
|
- runtime tests cover frame execution, pacing, state roundtrips, and lifecycle control
|
|
- `tests/public_api.rs` exercises the supported public flow as a black-box consumer
|
|
|
|
## Constraints And Tradeoffs
|
|
|
|
- no platform backend is bundled beyond the GTK desktop example
|
|
- audio mixing in `runtime/audio.rs` is intentionally interim
|
|
- optional adapter crates are thin integration layers, not mandatory parts of the core runtime
|
|
- compatibility promises are defined in `docs/api_contract.md`, not by internal module visibility
|