jevsnes.git / third-party / c / README.md

Chapter 17: c, the bot's own source, built as upstream built it

zbanks/alttp lives here at upstream commit bcb2537 ("Hammer/sword/lift obstacles in path in ap_follow_targets", upstream's last, 2020-02-06), vendored with git read-tree --prefix and kept byte-identical to upstream. Its README credits the reverse engineering it stands on: MathOnNapkins' Zelda 3 RAM/ROM/SRAM documentation and annotated disassembly, and wiiqwertyuiop's annotated disassembly.

Its pieces, roughly: alttp.c is ap_tick, the per-frame entry point; ap_snes.c/ap_snes.h read the game out of memory (sprites, tiles, rooms); ap_map.c builds a map of everything seen and pathfinds across it with A*; ap_plan.c holds the goals and tasks and picks what to do next; ap_req.c and ap_item.c track what Link has and what each goal requires. The rest are small data structures: a pointer map (pm), a min-heap priority queue (pq, the pathfinder's frontier), bitmask helpers (bm), and a bitmask for bipartite matching (lb).

How it is built. Not with its own Makefile, but the Makefile's flags are copied into BUCK, with a comment on each change:

flowchart LR
  src["zbanks-alttp/ (upstream, untouched)"] --> patched[":patched (a genrule)"]
  patches["patches/*.patch"] --> patched
  patched --> lib["//third-party/c:zbanks-alttp (cxx_library)"]
  lib --> shim["packages/zbanks: shim/host.c and the Rust host"]

:patched copies upstream's sources and applies every patch in patches/, in name order, with patch --batch --forward -p4. A patch that no longer applies fails the build, so a refresh can never silently lose one.

The few flags a different compiler and host force:

  • -fcommon: ap_snes.h defines bool ap_manual_mode; and every .c includes it. The gcc of upstream's era merged such definitions; clang 11+ and gcc 10+ refuse to link them.
  • No -Werror: upstream was warning-clean on its compiler; clang 21 warns about things gcc of 2020 did not, and a warning is not a behaviour change.
  • -Dfopen=zb_fopen and -Drename=zb_rename: the bot writes its debug files with bare relative names, into whatever directory the process runs in, which for us is the repository root. These send them through the host instead, into run/....
  • -Dap_goal_add=zb_goal_add, on ap_map.c only: every goal the bot makes is made in ap_map.c, so this routes them through the host, which declines the ones the save says are done and calls the real ap_goal_add (in ap_plan.c, compiled without the rename) for the rest.

Aside: the debugger that isn't there. The bot was written to run under gdb. Its assert_bp is an int3 breakpoint instruction, and the author pressed continue. Outside a debugger the kernel turns int3 into SIGTRAP, which kills the process. The host's shim installs a SIGTRAP handler that counts the trap and returns, which is exactly gdb's continue. The Bot panel shows the count. It fires tens of times a run, always at the same place.

Try it. nix develop -c buck2 build //third-party/c:zbanks-alttp builds the bot with every patch applied. nix develop -c buck2 build //third-party/c:patched stops after patching, if you want to read the sources as compiled.

For the people who maintain it

In this folder

PathWhat
patches/Chapter 18: the carried patches, the only changes to the bot's own code.
BUCK:patched (upstream's sources with patches/ applied) and :zbanks-alttp, the cxx_library, with every flag change commented.
README.mdThis chapter.
CLAUDE.mdHow to make a patch and refresh from upstream, for agents.
zbanks-alttp/Upstream's tree, byte for byte. No chapter: a refresh replaces the directory wholesale, and its README is upstream's.

The host the bot plugs into is packages/zbanks (chapter 4). How it differs from upstream's patched Snes9x, and why: research/zbanks-alttp.md.

← Previous: Chapter 16, third-party/ · Up: third-party · Next: Chapter 18, patches/ →