Chapter 11: zbanks (headless), the bot with the lights off
The window (chapter 12) is for watching. This is for finding out. It is the zbanks/alttp C bot playing our console with no window, three to four times faster than a real SNES, writing down what it did: the proof that it plays, and the place to reproduce anything it does.
mkdir -p run/zbanks-c/run1 run/zbanks-c/states
BIN=$(nix develop -c buck2 build //apps/zbanks:zbanks --show-simple-output 2>/dev/null)
$BIN "roms/Legend of Zelda, The - A Link to the Past (USA).sfc" 30000 run/zbanks-c/run1 \
--states run/zbanks-c/states > run/zbanks-c/run1/bot.log 2> run/zbanks-c/run1/host.log
A run boots the patched cartridge (chapter 4's rando), restores a start
state if asked, starts the bot, and plays the number of frames given. Every
600 frames it writes the picture as a PNG and one line of progress.tsv:
mode, room, area, Link's position, hearts, sword, where the bot thinks Link
is, its info line and current task, pendants, crystals, bow, book, boots,
gloves, and goals given up and put back. The summary at the end (stderr)
lists the places seen, the traps and the goal list.
The first run needs a home. The bot's own ap_init loads a state called
home from the states directory: Link standing in his house at the start of
an open-mode game. Without one it starts at power-on, where it presses
nothing. Make it once per states directory:
$BIN "roms/Legend of Zelda, The - A Link to the Past (USA).sfc" 0 run/zbanks-c/home \
--states run/zbanks-c/states --make-home home
--make-home runs no bot: it creates save file 1 from power-on, applies the
Randomizer's open-mode preset to it, boots again and keeps Link in his house
as the named state.
Aside: how the map grows. Upstream's video ran from a map the bot had built over many runs. Every run here ends by writing the bot's own export of what it learned,
map_export.txt, and--import-map FILEhands one run's export to the next asmap.19.txt, which the bot imports on its first tick. Run on run, the known explore goals went 155, 200, 287, 367 (2026-09-21).
Try it. Make a home, then play 30,000 frames (about two and a half minutes; run it in the background) and read
run/zbanks-c/run1/progress.tsv. Add--jevunderop-env-run -- sh -c '…'to let Jev choose the goals, and compare.
For the people who maintain it
zbanks <rom.sfc> <frames> <out-dir> [--states DIR] [--start NAME] [--every N] [--import-map FILE] [--make-home NAME] [--jev] [--jev-dollars USD] [--no-retry] [--save-at FRAME]...
| Option | What |
|---|---|
--states DIR | Where named states live (default <rom>.states/ beside the ROM). It must already exist. The bot's ap_init loads home and hpegs from there. |
--start NAME | Restore that state before the bot starts. The bot's own ap_init then loads home over it whenever the directory has one, so to start somewhere else, make that state the home of a states directory of its own. |
--save-at FRAME | Repeatable: keep the machine after that frame as <out-dir>/at-FRAME.state. Copy it into a states directory as home to start another run exactly there (with Jev off, a run replays identically). |
--every N | A PNG (frame-NNNNNN.png) and a progress.tsv line every N frames (default 600). |
--import-map FILE | Copies FILE to <out-dir>/map.19.txt, which the bot imports on its first tick. |
--make-home NAME | Make the open-mode start state, as above, and run no bot. |
--no-retry | Goals the bot gives up on stay given up, as upstream's. By default the host puts them back when Link's possessions change; the summary counts both. |
--jev (or ZB_JEV=1) | Jev makes the goal choice (chapter 5). Needs the key. Every choice is a line of <out-dir>/jev.jsonl, and the summary gives questions and dollars per game-minute. |
--jev-dollars USD | Stop asking once this run has spent that much. |
ZB_HUMAN=first-last:PAD | Hold PAD (hex, Snes9x layout: 0x0400 down, 0x0040 X) as the human's pad on those frames. With X in it the bot steps aside, which tests what Link can physically do from a state. |
ZB_TRACE=first-last | Print the pad and a few bytes for each of those frames to stderr. |
A run does not stop the moment the bot gives up (ap_manual_mode: no goals
left). It runs the same stall detector and bounded recovery as the window
(chapter 4): every given-up goal is retried, with growing backoff and a cap
of tries without progress, and the run stops a minute after recovery itself
gives up, saying so.
The bot's stdout is its own log, redirect it. Its debug files (goals.txt,
map, map.pbm, full_map.dot, ...) land in <out-dir>. By convention runs
go under run/zbanks-c/. What the bot is and why the host is as it is:
research/zbanks-alttp.md.
In this folder
| Path | What |
|---|---|
| src/main.rs | The whole program: options, the play loop, PNGs and progress.tsv, --make-home, Jev, stall and recovery, the summary. |
| BUCK | The rust_binary, over console, alttp, zbanks, decisions, jev-http and png. |
← Previous: Chapter 10, headless/ · Up: apps · Next: Chapter 12, native/ →