1# jevsnes: a guide in thirty chapters 2 3**Jev plays Zelda.** Or, to be exact about it: a bot plays *The Legend of 4Zelda: A Link to the Past*, and every time it has to decide where to go 5next, it asks Jev. 6 7Jev is TypeSafe AI's "System One" model. It does not write, and you cannot 8ask it to explain itself. You hand it a question whose answers are already 9written down (yes or no; one of these six; somewhere on this scale), and it 10tells you how likely each answer is, with probabilities you can trust. The 11three kinds of question are called `Noul`, `Choice` and `Score`. A Link to 12the Past turns out to be full of `Choice`s: the chest, the north door, or 13the pot by the wall? 14 15This repository is the whole apparatus: a Super Nintendo, the game running 16on it, a bot that plays the game, the place where Jev is asked, a window to 17watch it all in, and a recorder that can rewind any of it. 18 19> **Try it.** There is no live site for this one; the game runs in a window 20> on a desktop. But the thinking parts are tested without a window, a ROM or 21> a network. After the setup below: 22> 23> nix develop -c buck2 test //packages/alttp:test //packages/zbanks:test 24> 25> runs the tests that read Hyrule out of work RAM and the ones that decide 26> when the bot has stopped playing. 27 28## The idea in three sentences 29 301. **The SNES is a library, not a program beside us.** The emulator core 31 ([jgenesis](https://github.com/jsgroth/jgenesis)' `snes-core`) is linked 32 into the same process, so the game's RAM, its picture and its controller 33 are ordinary Rust values we can read and write between frames 34 (chapter 2). 352. **Somebody else's bot plays.** [zbanks/alttp](https://github.com/zbanks/alttp) 36 is a C bot from 2020 that reads SNES memory and, in its author's words, 37 "clears Eastern Palace" in a Randomizer seed. We compile its own sources, 38 carry nineteen recorded patches to it, and host it the way its patched 39 Snes9x did (chapters 4, 17 and 18). 403. **Jev makes the bot's one real decision.** The bot picks its next goal by 41 lowest score. A carried patch lets the host make that pick instead, and 42 `packages/decisions` turns the offered goals into sentences and asks Jev 43 one `Choice` (chapter 5). With Jev off, the bot is upstream's, byte for 44 byte. 45 46> **Aside: why not let Jev drive the controller?** That was the first 47> attempt. A Rust "brain" asked Jev which way to walk, and it did not get 48> far: once Link stood still for four minutes while Jev gave the right 49> answer over and over, and once a soldier nearby made it ask seventeen 50> questions in seven seconds and trip the rate limiter (both are kept in 51> chapter 22). On 2026-09-21 it was deleted in favour of a bot that already 52> knew how to walk, and Jev moved up a level, to the question a bot is 53> actually bad at: *what is worth doing next?* 54 55How far that gets, measured headless on 2026-09-21 (run `j10` in 56[research/zbanks-alttp.md](research/zbanks-alttp.md), "How far it plays"): 57the sword by frame 2,719, the bow from the Eastern Palace's big chest, the 58Armos Knights beaten, the Pendant of Courage, and Sahasrahla's Pegasus Boots 59by frame 62,499. Jev was asked 70 questions on the way, for $0.0017 in all. 60 61## The whole story in one picture 62 63What happens in one emulated frame of the window, about sixty times a 64second: 65 66```mermaid 67sequenceDiagram 68 participant W as Window (apps/native) 69 participant B as Bot host (packages/zbanks) 70 participant C as C bot (third-party/c) 71 participant D as Decisions (packages/decisions) 72 participant J as Jev 73 participant S as SNES (packages/console) 74 W->>B: tick(your pad) 75 B->>C: ap_tick(frame, pad) 76 opt the bot is choosing its next goal 77 C->>B: the goals within reach, cheapest first 78 B->>D: which one? 79 D->>J: one Choice, the goals as sentences 80 J-->>D: a probability for each 81 D-->>C: the pick (or upstream's own, if Jev is off or out) 82 end 83 C-->>B: the pad to hold 84 B-->>W: the pad 85 W->>S: run_frame() 86 S-->>W: picture, sound, work RAM 87 W->>W: panels, replay recording, MCP answers 88``` 89 90In words: the window hands the bot the human's pad; the bot thinks and hands 91back a pad of its own; the console runs one frame with it; and everything we 92want to watch (what the bot says, what Jev was asked, the game state in 93words) is read out of the same machine and drawn *beside* the picture, 94never over it. 95 96## How to read this 97 98Every folder in this repository is a chapter, and each ends with a link to 99the next. Read them in order and you will know how all of it works; jump in 100anywhere and the chapter says what it is and what is beside it. Each starts 101with the idea, in plain words, and ends with what a maintainer needs (files, 102invariants, commands). The `CLAUDE.md` beside each README is what an AI agent 103working there is told on top of the chapter. 104 105| Chapter | Folder | What you learn | 106| --- | --- | --- | 107| 1 | [packages/](packages/) | The libraries, and the line between the portable ones and the rest. | 108| 2 | [packages/console/](packages/console/) | A Super Nintendo you can keep in a variable, and copy. | 109| 3 | [packages/alttp/](packages/alttp/) | Reading Hyrule out of 128 KiB of work RAM. | 110| 4 | [packages/zbanks/](packages/zbanks/) | Hosting a C bot: its memory, its pad, its stalls. | 111| 5 | [packages/decisions/](packages/decisions/) | The one place Jev is asked anything, and what happens when it is not. | 112| 6 | [packages/replay/](packages/replay/) | A VCR for a machine: record, seek, branch. | 113| 7 | [packages/panels/](packages/panels/) | Everything drawn beside the picture. | 114| 8 | [packages/mcp/](packages/mcp/) | A window that answers an agent's questions. | 115| 9 | [apps/](apps/) | The programs, and which libraries each one is. | 116| 10 | [apps/headless/](apps/headless/) | Does the game even boot? | 117| 11 | [apps/zbanks/](apps/zbanks/) | The bot playing with the lights off. | 118| 12 | [apps/native/](apps/native/) | The window. | 119| 13 | [apps/jevprobe/](apps/jevprobe/) | One question to Jev, out loud. | 120| 14 | [apps/replay-probe/](apps/replay-probe/) | Proving the same thing happens twice. | 121| 15 | [apps/hotdemo/](apps/hotdemo/) | A program that changes its mind while running. | 122| 16 | [third-party/](third-party/) | Other people's code, and how it is kept honest. | 123| 17 | [third-party/c/](third-party/c/) | The bot's own source, built as upstream built it. | 124| 18 | [third-party/c/patches/](third-party/c/patches/) | Every change made to the bot, and why each one had to be. | 125| 19 | [third-party/rust/](third-party/rust/) | How Rust crates reach the build. | 126| 20 | [third-party/rust/fixups/](third-party/rust/fixups/) | The answers the crate converter cannot work out. | 127| 21 | [third-party/rust/top/](third-party/rust/top/) | An empty file with a job. | 128| 22 | [research/](research/) | The source-cited notes the code was built from. | 129| 23 | [research/fixtures/](research/fixtures/) | Evidence kept in a jar. | 130| 24 | [tools/](tools/) | Programs about the build, not the game. | 131| 25 | [tools/mcp/](tools/mcp/) | How a client reaches the window in milliseconds. | 132| 26 | [tools/watch/](tools/watch/) | One line of status, for a watchdog. | 133| 27 | [tools/hotpatch/](tools/hotpatch/) | Editing a running program without restarting it. | 134| 28 | [nix/](nix/) | The Mesen launcher. | 135| 29 | [toolchains/](toolchains/) | Which compiler, and why always `-O3`. | 136| 30 | [platforms/](platforms/) | Where build actions run, and an optional cache. | 137 138## If you know web frameworks 139 140None of this is a website, but most of its parts have a counterpart you 141already know. 142 143| Here | What it is | In web terms | 144| --- | --- | --- | 145| Rust crate | One library or program, with its own dependencies. | An npm package. | 146| buck2 | The build system: every crate, the C bot and the generated third-party graph are targets in one graph, built and cached by content. | Nx or Turborepo, but it also compiles the C. | 147| `BUCK` file | A folder's build targets. | A `project.json`, or the build half of a `package.json`. | 148| reindeer | Turns a `Cargo.toml` of third-party crates into buck2 targets. | Nothing quite; imagine generating your bundler config from `package-lock.json`. | 149| Nix devshell | Every tool at an exact version, from `nix develop`. | `package.json` plus a Node version manager, for every tool and not only Node. | 150| egui | An immediate-mode UI: the whole window is redrawn from the app's state every frame. | A React render function, called sixty times a second, with no virtual DOM. | 151| A snapshot | The entire machine, serialised: restore it and you are back on that frame. | The Redux store, if it held the CPU too. | 152| MCP server | Tools an AI agent can call on the running window: read RAM, take a picture, press buttons. | A dev-tools API, like the one React DevTools talks to. | 153| The C bot | Native code linked into the program. | A node-gyp native addon. | 154 155## Running it yourself 156 157The repository is served over git by the lmjtfy site, with the jevcrates 158submodule it uses: 159 160 git clone --recurse-submodules https://lmjtfy.fun/jevsnes.git 161 cd jevsnes 162 nix develop # or let direnv do it: .envrc says `use flake` 163 164Everything the build needs is in the devshell ([flake.nix](flake.nix) says 165what each tool is for); nothing is assumed from the host. 166 167> **Aside: one input is private.** The devshell's `buck2` comes from the 168> owner's private package set (`nix-pkgs`, fetched over SSH), because the 169> buck2 in nixpkgs drops its forkserver under test load (fixed upstream and 170> released 2026-09-15). Without access to that repository, `nix develop` 171> stops at fetching it. Every other input is public. 172 173### The cartridge 174 175ROMs are not in git and never will be. Dump your own cartridge (any SNES 176cartridge dumper makes a headerless `.sfc`) and put it in `roms/`, which is 177gitignored. The window, the headless bot and the probes all target the USA 178release, the one the community disassembly and RAM maps describe: 179 180| SHA-1 | File | 181| --- | --- | 182| `6d4f10a8b10e10dbe624cb23cf03b88bb8252973` | `roms/Legend of Zelda, The - A Link to the Past (USA).sfc` | 183| `7c073a222569b9b8e8ca5fcb5dfec3b5e31da895` | `roms/Legend of Zelda, The - A Link to the Past (Europe).sfc` | 184 185`sha1sum` your dump against the table before blaming anything else. 186 187### Building and running 188 189 nix develop -c buck2 build //... 190 nix develop -c buck2 test //... 191 nix develop -c buck2 run //apps/headless:headless -- \ 192 "roms/Legend of Zelda, The - A Link to the Past (USA).sfc" 1500 run/out.ppm 193 194The first build compiles a few hundred crates, a C bot and a cycle-accurate 195SNES, all at `-O3` (chapter 29 says why there is no debug build). The window 196itself is chapter 12. 197 198### The Jev key 199 200Jev needs a TypeSafe API key in `TYPESAFE_API_KEY`, in the environment of the 201one process that asks. The owner's machine puts it there with `op-env-run`, a 202host wrapper (not part of this repository) that puts it in one child 203process's environment and nowhere else: 204 205 op-env-run -- nix develop -c buck2 run //apps/jevprobe:jevprobe 206 207Anywhere else, get a key from TypeSafe (<https://docs.typesafe.ai>) and give 208it to that one command's environment the same way, rather than exporting it 209into your shell. 210 211Without a key, everything plays exactly the same; Jev is simply off, and the 212window's Jev panel says so. 213 214### Mesen, the second opinion 215 216The devshell also carries [Mesen 2](https://github.com/SourMesen/Mesen2), a 217known-good emulator to check our core against: the same ROM, the same RAM 218addresses, a picture to compare. Jev never plays in it. `Mesen` on the 219devshell's `PATH` is a wrapper (chapter 28). 220 221 Mesen "roms/Legend of Zelda, The - A Link to the Past (USA).sfc" 222 Mesen --testRunner --enableStdout --timeout=60 script.lua "roms/….sfc" 223 224The second form is headless, driven by a Lua script, and exits with the code 225the script passes to `emu.stop`. [research/mesen-automation.md](research/mesen-automation.md) 226is the manual. 227 228## What gets no chapter 229 230Some folders are not part of the story: `buck-out/` and `target/` (build 231output), `run/` (pids, logs, captured frames, recordings: everything a run 232writes), `roms/` (your cartridge and its saves) and `.claude/` (an agent's 233local settings). All of them are gitignored or untracked, so a clone does not 234have them until something makes them. Upstream code vendored under 235`third-party/` gets no chapters of ours either; chapter 16 explains why. 236 237## Files at the top 238 239| File | What | 240| --- | --- | 241| [flake.nix](flake.nix) | The devshell: Rust from fenix (with the wasm32 target), buck2, reindeer, clang and lld, Mesen, ffmpeg, gdb, and the two variables that reach the GPU under WSL. | 242| [flake.lock](flake.lock) | The exact revision of every Nix input. | 243| [.buckconfig](.buckconfig) | buck2's cells, the bundled prelude, the execution platform and the thread count. | 244| [.buckroot](.buckroot) | Empty; marks the buck2 project root. | 245| [.envrc](.envrc) | direnv: `use flake`. | 246| [.gitmodules](.gitmodules) | The jevcrates submodule, by a relative URL. | 247| [.gitignore](.gitignore) | Build output, `run/`, ROMs and saves, snapshots. | 248| [.mcp.json](.mcp.json) | Tells an MCP client (Claude Code) to start `tools/mcp/launch.sh` (chapter 25). | 249| [CLAUDE.md](CLAUDE.md) | What an agent working anywhere here must not break. | 250 251Next: [Chapter 1, packages/](packages/) →