For agents, on top of README.md, which they read first.

  • Never edit the bot. A behaviour the host needs is a change here, in shim/host.c, or a compiler flag in ../../third-party/c/BUCK - with the evidence in ../../research/zbanks-alttp.md. Only a change no host can make from outside becomes a carried patch, by the rules in ../../third-party/c/CLAUDE.md.
  • A new rando::PATCHES row changes the cartridge CRC: every home state made before it is refused. Remake them (apps/zbanks --make-home, the window's roms/<rom>.states/ included).
  • One bot per process. Its state is C globals; Bot::start panics on a second call. Do not try to run two for comparison in one process.
  • The pointers base() returns are cached by the bot for ever (ap_snes.c:60-61). The WRAM mirror and ROM copy are leaked on purpose and must never move or be freed; everything reaches the console by copying through them in tick, load and save.
  • A new ROM table read by the bot is a Japanese address. Find the table's bytes in ~/src/github.com/spannerisms/jpdasm, locate them in the USA ROM, check the whole table matches, and add a JP_TO_US row.
  • rando::patch_rom changes the cartridge's CRC, which every snapshot records: states for a bot must be made from the patched image.
  • Not wasm: the C and the signal handler make it native-only, and src/outcomes.rs writes goal_outcomes.jsonl. States still come through the States trait, never std::fs in lib.rs.
  • The given-up watch (zb_watch_goals) reads goals after they leave the list. That is safe only because upstream never frees one: ap_goal_fail and ap_goal_complete both just LL_EXTRACT (ap_plan.c). A refresh from upstream that starts freeing goals turns this into a use-after-free; check those two functions on every refresh.
  • Permafailed vs completed is attempts > 3, upstream's own threshold in ap_goal_fail. Three things test it in the shim and the crate now: the given-up watch above, zb_completed_count (recovery::Recovery's progress signal), and the outcome ring zb_note_outcome fills (Outcome::completed). If a carried patch or a refresh changes that threshold, change all of them.
  • src/stall.rs and src/recovery.rs are shared by the window and headless. They depend on //packages/alttp:alttp (for stall.rs's alttp::Place) but nothing app-specific - no window, no MCP, no filesystem. apps/native used to keep its own copy of stall.rs; it now does use zbanks::stall;, so there is one definition of "stalled" for both apps/native and apps/zbanks, not two that can drift.
  • A goal's identity string is duplicated by convention in three places: decisions::goal_choice::identity, shim/host.c's zb_note_outcome, and what outcomes.rs stores ("{kind}|{node}|{screen}"). This crate cannot depend on decisions (the dependency runs the other way). Change all three together or a restart's outcomes stop matching the goals offered.