1# Chapter 2: console, a Super Nintendo you can keep in a variable 2 3Most projects that let a program play a game run an emulator as a separate 4process and poke at it from outside: a Lua script inside it, a socket, a 5screenshot to read pixels off. This one does the opposite. The emulator core 6is a library, and `Console` is a value: 7 8```rust 9let mut snes = Console::boot(rom_bytes, Saves::default())?; 10snes.set_button(SnesButton::Start, true); 11snes.run_frame()?; 12snes.frame // the picture the PPU just finished: size and RGBA pixels 13snes.samples // stereo audio produced during that frame 14snes.saves // cartridge SRAM, keyed by extension, held in memory 15snes.wram() // all 128 KiB of work RAM; wram()[0x10] is $7E0010 16snes.wram_mut() // the same, writable, between frames 17 18let machine = snes.snapshot()?; // the whole machine, between frames 19snes.restore(&machine)?; // and back; refused for another core or cartridge 20snes.power_cycle(); // off and on again, battery save kept 21``` 22 23Everything else in this repository is built on those few lines. The game 24state, the bot, Jev's questions and the on-screen controller all come from 25the same machine the picture comes from, in the same process, with no 26copying through a socket. 27 28> **Aside: a frame, not a step.** A real SNES runs its CPU, its picture unit 29> and its sound chip on their own clocks, and the core emulates all three 30> cycle by cycle. `run_frame` keeps ticking until the core says a frame was 31> rendered, so one call is one sixtieth of a second of game, never one CPU 32> instruction. NTSC is actually 60.0988 frames a second, which is why 33> `target_fps()` exists: a host paces on it, never on its monitor. 34 35The core underneath, jgenesis' `snes-core`, is written to be driven by a 36frontend: every tick it is handed somewhere to draw, somewhere to put sound, 37somewhere to keep saves and something to ask about the controller. This 38crate supplies all four as plain in-memory values (`Frame`, `Samples`, 39`Saves`, the pad), which is what lets it run the same natively and, one day, 40in a browser. Where the picture goes, where the sound plays and where a save 41is kept are the app's decisions, made after the frame. 42 43**A snapshot is the machine, not something like it.** It starts with a 44header naming the core's save-state version and the CRC-32 of the cartridge 45image, so a snapshot from another build of the core or another ROM is 46refused rather than half-loaded. There are two encodings: `snapshot` (compact 47varint integers, the format of every state kept on disk) and 48`snapshot_fixed` (every integer at full width, so the same field sits at the 49same offset every time, which is what lets chapter 6 store keyframes as 50small deltas). The two do not read each other. 51 52> **Try it.** `apps/headless` (chapter 10) boots your cartridge, runs it, 53> then takes a snapshot, runs 300 frames, goes back, runs the same 300 54> again, and asserts the two futures are the same picture and the same work 55> RAM: 56> 57> nix develop -c buck2 run //apps/headless:headless -- \ 58> "roms/Legend of Zelda, The - A Link to the Past (USA).sfc" 1500 run/out.ppm 59> 60> Look for `snapshot … bytes, identical after 300 frames`. 61 62## For the people who maintain it 63 64### In this folder 65 66| Path | What | 67| --- | --- | 68| [src/lib.rs](src/lib.rs) | `Console` (boot, `run_frame`, `target_fps`, buttons, `power_cycle`, both snapshot formats, `wram`/`wram_mut`), the in-memory `Frame`, `Samples` and `Saves`, sound-device resampling (`audio_output`, `audio_queued`), and `SnapshotError`. | 69| [BUCK](BUCK) | The `rust_library`, over `//third-party/rust:snes-core`, `snes-config` and `jgenesis-common`. No test target: `apps/headless` is its test. | 70 71`wram()` and `wram_mut()` exist only because this repository carries two 72small patches on the vendored core, which upstream keeps private (chapter 7319). 74 75← Previous: [Chapter 1, packages/](../) · Up: [packages](../) · Next: [Chapter 3, alttp/](../alttp/) →