jevsnes.git / packages / console / README.md
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/) →