jevsnes.git / apps / zbanks

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 FILE hands one run's export to the next as map.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 --jev under op-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]...

OptionWhat
--states DIRWhere 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 NAMERestore 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 FRAMERepeatable: 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 NA PNG (frame-NNNNNN.png) and a progress.tsv line every N frames (default 600).
--import-map FILECopies FILE to <out-dir>/map.19.txt, which the bot imports on its first tick.
--make-home NAMEMake the open-mode start state, as above, and run no bot.
--no-retryGoals 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 USDStop asking once this run has spent that much.
ZB_HUMAN=first-last:PADHold 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-lastPrint 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

PathWhat
src/main.rsThe whole program: options, the play loop, PNGs and progress.tsv, --make-home, Jev, stall and recovery, the summary.
BUCKThe rust_binary, over console, alttp, zbanks, decisions, jev-http and png.

← Previous: Chapter 10, headless/ · Up: apps · Next: Chapter 12, native/ →