jevsnes: a guide in thirty chapters
Jev plays Zelda. Or, to be exact about it: a bot plays The Legend of Zelda: A Link to the Past, and every time it has to decide where to go next, it asks Jev.
Jev is TypeSafe AI's "System One" model. It does not write, and you cannot
ask it to explain itself. You hand it a question whose answers are already
written down (yes or no; one of these six; somewhere on this scale), and it
tells you how likely each answer is, with probabilities you can trust. The
three kinds of question are called Noul, Choice and Score. A Link to
the Past turns out to be full of Choices: the chest, the north door, or
the pot by the wall?
This repository is the whole apparatus: a Super Nintendo, the game running on it, a bot that plays the game, the place where Jev is asked, a window to watch it all in, and a recorder that can rewind any of it.
Try it. There is no live site for this one; the game runs in a window on a desktop. But the thinking parts are tested without a window, a ROM or a network. After the setup below:
nix develop -c buck2 test //packages/alttp:test //packages/zbanks:testruns the tests that read Hyrule out of work RAM and the ones that decide when the bot has stopped playing.
The idea in three sentences
- The SNES is a library, not a program beside us. The emulator core
(jgenesis'
snes-core) is linked into the same process, so the game's RAM, its picture and its controller are ordinary Rust values we can read and write between frames (chapter 2). - Somebody else's bot plays. zbanks/alttp is a C bot from 2020 that reads SNES memory and, in its author's words, "clears Eastern Palace" in a Randomizer seed. We compile its own sources, carry nineteen recorded patches to it, and host it the way its patched Snes9x did (chapters 4, 17 and 18).
- Jev makes the bot's one real decision. The bot picks its next goal by
lowest score. A carried patch lets the host make that pick instead, and
packages/decisionsturns the offered goals into sentences and asks Jev oneChoice(chapter 5). With Jev off, the bot is upstream's, byte for byte.
Aside: why not let Jev drive the controller? That was the first attempt. A Rust "brain" asked Jev which way to walk, and it did not get far: once Link stood still for four minutes while Jev gave the right answer over and over, and once a soldier nearby made it ask seventeen questions in seven seconds and trip the rate limiter (both are kept in chapter 22). On 2026-09-21 it was deleted in favour of a bot that already knew how to walk, and Jev moved up a level, to the question a bot is actually bad at: what is worth doing next?
How far that gets, measured headless on 2026-09-21 (run j10 in
research/zbanks-alttp.md, "How far it plays"):
the sword by frame 2,719, the bow from the Eastern Palace's big chest, the
Armos Knights beaten, the Pendant of Courage, and Sahasrahla's Pegasus Boots
by frame 62,499. Jev was asked 70 questions on the way, for $0.0017 in all.
The whole story in one picture
What happens in one emulated frame of the window, about sixty times a second:
sequenceDiagram
participant W as Window (apps/native)
participant B as Bot host (packages/zbanks)
participant C as C bot (third-party/c)
participant D as Decisions (packages/decisions)
participant J as Jev
participant S as SNES (packages/console)
W->>B: tick(your pad)
B->>C: ap_tick(frame, pad)
opt the bot is choosing its next goal
C->>B: the goals within reach, cheapest first
B->>D: which one?
D->>J: one Choice, the goals as sentences
J-->>D: a probability for each
D-->>C: the pick (or upstream's own, if Jev is off or out)
end
C-->>B: the pad to hold
B-->>W: the pad
W->>S: run_frame()
S-->>W: picture, sound, work RAM
W->>W: panels, replay recording, MCP answers
In words: the window hands the bot the human's pad; the bot thinks and hands back a pad of its own; the console runs one frame with it; and everything we want to watch (what the bot says, what Jev was asked, the game state in words) is read out of the same machine and drawn beside the picture, never over it.
How to read this
Every folder in this repository is a chapter, and each ends with a link to
the next. Read them in order and you will know how all of it works; jump in
anywhere and the chapter says what it is and what is beside it. Each starts
with the idea, in plain words, and ends with what a maintainer needs (files,
invariants, commands). The CLAUDE.md beside each README is what an AI agent
working there is told on top of the chapter.
| Chapter | Folder | What you learn |
|---|---|---|
| 1 | packages/ | The libraries, and the line between the portable ones and the rest. |
| 2 | packages/console/ | A Super Nintendo you can keep in a variable, and copy. |
| 3 | packages/alttp/ | Reading Hyrule out of 128 KiB of work RAM. |
| 4 | packages/zbanks/ | Hosting a C bot: its memory, its pad, its stalls. |
| 5 | packages/decisions/ | The one place Jev is asked anything, and what happens when it is not. |
| 6 | packages/replay/ | A VCR for a machine: record, seek, branch. |
| 7 | packages/panels/ | Everything drawn beside the picture. |
| 8 | packages/mcp/ | A window that answers an agent's questions. |
| 9 | apps/ | The programs, and which libraries each one is. |
| 10 | apps/headless/ | Does the game even boot? |
| 11 | apps/zbanks/ | The bot playing with the lights off. |
| 12 | apps/native/ | The window. |
| 13 | apps/jevprobe/ | One question to Jev, out loud. |
| 14 | apps/replay-probe/ | Proving the same thing happens twice. |
| 15 | apps/hotdemo/ | A program that changes its mind while running. |
| 16 | third-party/ | Other people's code, and how it is kept honest. |
| 17 | third-party/c/ | The bot's own source, built as upstream built it. |
| 18 | third-party/c/patches/ | Every change made to the bot, and why each one had to be. |
| 19 | third-party/rust/ | How Rust crates reach the build. |
| 20 | third-party/rust/fixups/ | The answers the crate converter cannot work out. |
| 21 | third-party/rust/top/ | An empty file with a job. |
| 22 | research/ | The source-cited notes the code was built from. |
| 23 | research/fixtures/ | Evidence kept in a jar. |
| 24 | tools/ | Programs about the build, not the game. |
| 25 | tools/mcp/ | How a client reaches the window in milliseconds. |
| 26 | tools/watch/ | One line of status, for a watchdog. |
| 27 | tools/hotpatch/ | Editing a running program without restarting it. |
| 28 | nix/ | The Mesen launcher. |
| 29 | toolchains/ | Which compiler, and why always -O3. |
| 30 | platforms/ | Where build actions run, and an optional cache. |
If you know web frameworks
None of this is a website, but most of its parts have a counterpart you already know.
| Here | What it is | In web terms |
|---|---|---|
| Rust crate | One library or program, with its own dependencies. | An npm package. |
| 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. |
BUCK file | A folder's build targets. | A project.json, or the build half of a package.json. |
| reindeer | Turns a Cargo.toml of third-party crates into buck2 targets. | Nothing quite; imagine generating your bundler config from package-lock.json. |
| 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. |
| 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. |
| A snapshot | The entire machine, serialised: restore it and you are back on that frame. | The Redux store, if it held the CPU too. |
| 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. |
| The C bot | Native code linked into the program. | A node-gyp native addon. |
Running it yourself
The repository is served over git by the lmjtfy site, with the jevcrates submodule it uses:
git clone --recurse-submodules https://lmjtfy.fun/jevsnes.git
cd jevsnes
nix develop # or let direnv do it: .envrc says `use flake`
Everything the build needs is in the devshell (flake.nix says what each tool is for); nothing is assumed from the host.
Aside: one input is private. The devshell's
buck2comes from the owner's private package set (nix-pkgs, fetched over SSH), because the buck2 in nixpkgs drops its forkserver under test load (fixed upstream and released 2026-09-15). Without access to that repository,nix developstops at fetching it. Every other input is public.
The cartridge
ROMs are not in git and never will be. Dump your own cartridge (any SNES
cartridge dumper makes a headerless .sfc) and put it in roms/, which is
gitignored. The window, the headless bot and the probes all target the USA
release, the one the community disassembly and RAM maps describe:
| SHA-1 | File |
|---|---|
6d4f10a8b10e10dbe624cb23cf03b88bb8252973 | roms/Legend of Zelda, The - A Link to the Past (USA).sfc |
7c073a222569b9b8e8ca5fcb5dfec3b5e31da895 | roms/Legend of Zelda, The - A Link to the Past (Europe).sfc |
sha1sum your dump against the table before blaming anything else.
Building and running
nix develop -c buck2 build //...
nix develop -c buck2 test //...
nix develop -c buck2 run //apps/headless:headless -- \
"roms/Legend of Zelda, The - A Link to the Past (USA).sfc" 1500 run/out.ppm
The first build compiles a few hundred crates, a C bot and a cycle-accurate
SNES, all at -O3 (chapter 29 says why there is no debug build). The window
itself is chapter 12.
The Jev key
Jev needs a TypeSafe API key in TYPESAFE_API_KEY, in the environment of the
one process that asks. The owner's machine puts it there with op-env-run, a
host wrapper (not part of this repository) that puts it in one child
process's environment and nowhere else:
op-env-run -- nix develop -c buck2 run //apps/jevprobe:jevprobe
Anywhere else, get a key from TypeSafe (https://docs.typesafe.ai) and give it to that one command's environment the same way, rather than exporting it into your shell.
Without a key, everything plays exactly the same; Jev is simply off, and the window's Jev panel says so.
Mesen, the second opinion
The devshell also carries Mesen 2, a
known-good emulator to check our core against: the same ROM, the same RAM
addresses, a picture to compare. Jev never plays in it. Mesen on the
devshell's PATH is a wrapper (chapter 28).
Mesen "roms/Legend of Zelda, The - A Link to the Past (USA).sfc"
Mesen --testRunner --enableStdout --timeout=60 script.lua "roms/….sfc"
The second form is headless, driven by a Lua script, and exits with the code
the script passes to emu.stop. research/mesen-automation.md
is the manual.
What gets no chapter
Some folders are not part of the story: buck-out/ and target/ (build
output), run/ (pids, logs, captured frames, recordings: everything a run
writes), roms/ (your cartridge and its saves) and .claude/ (an agent's
local settings). All of them are gitignored or untracked, so a clone does not
have them until something makes them. Upstream code vendored under
third-party/ gets no chapters of ours either; chapter 16 explains why.
Files at the top
| File | What |
|---|---|
| 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. |
| flake.lock | The exact revision of every Nix input. |
| .buckconfig | buck2's cells, the bundled prelude, the execution platform and the thread count. |
| .buckroot | Empty; marks the buck2 project root. |
| .envrc | direnv: use flake. |
| .gitmodules | The jevcrates submodule, by a relative URL. |
| .gitignore | Build output, run/, ROMs and saves, snapshots. |
| .mcp.json | Tells an MCP client (Claude Code) to start tools/mcp/launch.sh (chapter 25). |
| CLAUDE.md | What an agent working anywhere here must not break. |
Next: Chapter 1, packages/ →