1@README.md 2 3How to make one, and the rules every patch obeys, are in `../CLAUDE.md`. A 4new patch gets a row in `README.md`'s table and an entry below, in the same 5commit as the patch. 6 7## The carried patches, in full 8 9- `0001-follow-targets-on-intra-room-stairs.patch` (alttp.c) - bcb2537's 10 early return for `$B0 != 0` also caught the intra-room stairs, which 11 count their own steps in `$B0`; the bot never consumed the targets it 12 climbed past and walked back down. Upstream's video (commit 7add0e6) 13 predates that line. Evidence: `../../../research/zbanks-alttp.md`. 14- `0002-goal-choice-hook.patch` (ap_plan.c) - `ap_goal_choose_hook`, a 15 function pointer `ap_goal_evaluate` calls with upstream's own pick (the 16 lowest score), that score and `ap_goal_score`, and whose return it 17 pursues. NULL by default, which is upstream's behaviour exactly 18 (measured: a 12,000-frame run identical, line for line, with and 19 without it). It is a patch and not a shim wrapper because the choice is 20 inside a `static` function between two statements; the shim 21 (`../../../packages/zbanks/shim/host.c`, `zb_set_goal_chooser`) installs it. 22- `0003-anti-fairy-circle.patch` (ap_snes.h, ap_map.c, ap_plan.c) - 23 Eastern Palace `$B8`, where the big key is: the switch that brings out 24 its chest is under a pot an anti-fairy circle (sprite `$82`, HP 255) 25 orbits until the room's other enemies are dead. Upstream calls the 26 circle a plain enemy, so paths ran through it and Link was pinned by 27 knockback for 19,000 frames (run `s2a`). Four parts: `$82` is 28 invulnerable and blocking, and the anti-fairies it breaks into (`$15`) 29 invulnerable (the game does not count them for a clear room either); 30 kill-all skips invulnerable sprites; a `SCRIPT_KILLALL` entry "EP clear 31 big key room" in upstream's own per-room script table, as it has for the 32 castle's key guard; and kill-all no longer fails its goal the first 33 frame an enemy has no path to it (tries the others, waits 8 frames, gives 34 up after 32 misses) - three such frames permafailed the script in run 35 `b4`. No host fix: the room is the same in vanilla and the Randomizer, 36 and the only thing a host could change is the room itself (delete the 37 enemies), which is changing the game, not hosting it. Upstream never 38 needed `$B8` (its seed's big key was elsewhere; its video never enters 39 it). Evidence: `../../../research/zbanks-alttp.md`, "Eastern Palace `$B8`". 40- `0004-bow-shot-survives-follow-targets.patch` (ap_map.c) - bcb2537 41 (upstream's last commit, 2020-02-06) added an `else { JOYPAD_CLEAR(A); 42 JOYPAD_CLEAR(B); JOYPAD_CLEAR(Y); }` to `ap_follow_targets`. `ap_tick` 43 zeroes the pad every frame, so the only Y that line can clear is one the 44 caller set just before - `SCRIPT_KILLALL`'s bow shot at a VBOW sprite 45 (ap_plan.c) - and it cancelled that shot on the frame it was pressed. 46 Upstream killed the Armos Knights at 8f13023 (2020-01-02), before the line 47 existed. The patch drops only the Y. Measured from `j9`'s last frame, Jev 48 off: unpatched (`a0`) 12,000 frames, no arrow, five knights at 48 HP; 49 patched (`a1`) all dead and the pendant held up by frame 3,500. No host 50 fix: the host never sees the Y, which is set and cleared inside one 51 `ap_tick`. Evidence: `../../../research/zbanks-alttp.md`, "The Armos Knights". 52- `0005-green-pendant-is-bit-4.patch` (ap_req.c) - `REQUIREMENT_GREEN_PENDANT` 53 tested `$7EF374 & 0x01`, the red pendant. The green one (Courage) is 54 `0x04`: vanilla Sahasrahla tests it (usdasm bank_05.asm:20560-20562), so 55 does the Randomizer (z3randomizer dialog.asm:355, sram.asm:139 "- - - - - 56 g b r"). The Eastern Palace gives green, so Sahasrahla's NPC goal (the 57 only user of the requirement, ap_plan.c:135) stayed unsatisfiable and the 58 boots never came (run `o2`, 83,580 frames, manual mode). No host fix: the 59 bot reads the byte itself. 60- `0006-walk-up-to-a-blocking-npc.patch` (ap_map.c, ap_plan.c) - a 61 talking NPC that is `BLKF` (Sahasrahla, `0x16.0`) could not be reached: 62 `ap_pathfind_local` makes a blocking sprite's hitbox plus one 8-pixel cell 63 impassable, a sprite node's box is the sprite's own box, and that margin 64 rings the box ("A* failed, min heuristic: 2", run `o3` at 59,353, him 65 standing there). Three parts, each needed in turn (runs `o3c`): the 66 sprite being walked to does not block the search for it; Link, whom 67 `XYLINKIN` can never fit inside a solid NPC's box, has arrived at a 68 talking sprite once he stops within 8 pixels of it (`TALK_NPC` then 69 presses up and A, as upstream wrote it); and a sprite whose margin Link 70 already stands in blocks only its own cells, or every search from there 71 fails and every goal is unsatisfiable. Upstream's own earlier answer, a 72 TALK node placed below the sprite, is still there commented out (072b195). 73 **Both exemptions are gated on `SPRITE_ATTR_TALK`.** The first version 74 compared a blocking sprite's hitbox against the CURRENT pathfind's 75 destination box with no check that the sprite was the thing being walked 76 to, so any blocking sprite near an unrelated destination went transparent 77 to the search while staying solid to the engine - room `$0012`'s own 78 `0x73` sprite did exactly this to the Hyrule Castle secret passage's 79 door, freezing every fresh run there. Evidence: "The castle passage door". 80- `0007-talk-to-an-npc-until-it-gives.patch` (ap_plan.c) - `TALK_NPC` 81 took any nonzero `$02D8` as the NPC's gift, but `$02D8` keeps the last 82 item Link was given (the stale value behind run `o1`'s abort at uncle); 83 now only a new, nonzero one counts (the game writes 0 as the NPC starts 84 giving). While the NPC's text box is up the task does not time out 85 (Sahasrahla's text outlasts 64 frames). And when the talk is over Link 86 steps down off the NPC for 20 frames: talking pressed up into it, and the 87 next path started with a sidestep along its solid body that went nowhere. 88- `0008-text-box-gets-a-in-any-state.patch` (alttp.c) - `ap_tick` plans, 89 and so mashes dialog (`ap_task_evaluate`), only while Link is on the 90 ground. After the boots, the A closing Sahasrahla's text starts a dash 91 (`$5D = 0x11`, `kPlayerState_StartDash` in zelda3 player.h:21 - the 92 bot's enum calls `0x11` `FALLING_LEDGE`), and the box stayed open for 93 good. Now an open text box (module `$0E`, not the menu) gets A in any 94 state. 95- `0009-pegasus-boots-dash.patch` (ap_map.c) - `SCRIPT_SEQUENCE` gains four 96 characters (`8`/`2`/`4`/`6`, numpad directions), each a target that holds 97 A WITH its direction for one 16px step; repeat one to cover the ~29-frame 98 charge and the travel. Upstream never wrote a dash anywhere in its 99 history. `ap_follow_targets`'s per-tile clear of A is skipped only when 100 the current target's mask holds A together with a direction, so every 101 existing script is untouched. Built, proven to compile and to replay the 102 early game unchanged. 103- `0010-book-of-mudora-goal.patch` (ap_map.c, ap_plan.c) - an `ap_scripts[]` 104 entry for the Library's shelf (room `$0107`, entrance `$49`), its approach 105 point derived from ROM data (the entrance table, the sprite's own 106 room-relative bytes cross-checked against two others in the same room) 107 rather than a live walk-in, and `REQUIREMENT_BOOTS` added in 108 `ap_goal_add` by the script's name ("Library Book of Mudora", the same 109 pattern as Sahasrahla's pendant check). Proven headless to attach to the 110 right screen and to gate correctly (`unsat` without boots, `limit`/in 111 range with them); NOT yet proven to land the item. Evidence: "The Book of 112 Mudora". 113- `0011-shopkeeper-blocks-in-room-0123.patch` (ap_snes.h) - the per-room 114 override for sprite type `0xBB`/subtype `0x0200` in dungeon room `$0123` 115 dropped `SPRITE_ATTR_BLKF` while adding `NODE`, unlike every other 116 `TALK|NODE` override (Sahasrahla keeps BLKF; uncle correctly has none - 117 he is lying down). Without BLKF the pathfinder never saw this sprite as 118 an obstacle, so A* routed straight through its real hitbox and the 119 engine's own collision refused it - Link pinned one pixel from the 120 sprite, pressing Down forever at door `D 0x80` ("Stuck Link Detected! 121 5a88,2447 en route to 5a78,24c0"). No host fix: it is upstream's own 122 static table. Evidence: "The door D 0x80 stall in room $0123". 123- `0012-dam-push-block-not-a-hammer-peg.patch` (ap_map.c) - raw tile 124 `0x27` is `TILE_ATTR_HMMR` (a hammer peg) in `ap_snes.h`'s flat, 125 room-agnostic `ap_tile_attrs[]` - upstream's own comment there already 126 flags the ambiguity ("was also fence; remapped to 0x01"). Dungeon room 127 `$010B` (the Dam's block-puzzle room) reuses the same tile id for its 128 grey push blocks, so `ap_pathfind_local` scored it "liftable" (cost 20, 129 not a wall) and `ap_follow_targets` swung a hammer at it - Link stood 130 there mashing Y forever, one pixel from a solid block, while ordinary 131 directional walking against it still ran the game's own push mechanic 132 partway (confirmed live: `push_timer` counting down with Link's own x,y 133 frozen). Fix: a new `ap_tile_attrs_here(raw_tile)` helper returns 0 for 134 tile `0x27` specifically in room `$010B`, leaving every other room's 135 `0x27` untouched; wired into both call sites. No host fix: it is the same 136 static-table-lacks-room-context class as `0011`. Evidence: "The Dam 137 Chest push block". 138- `0013-big-chest-needs-the-big-key.patch` (ap_plan.c) - `ap_goal_score`'s 139 `GOAL_CHEST` case checked only whether the chest was already opened, 140 never whether it could legally be opened: `ap_node_islocked`'s own 141 `DOOR_ATTR_BKEY` big-key check (ap_map.c) only ever fires for locked 142 DOORS, never `NODE_CHEST`. The bot walked straight up to a keyless big 143 chest (Eastern Palace's) every time and got "Eh? It's locked! If you 144 had the Big Key…" - a wasted, repeating attempt that fed a ping-pong 145 with an unrelated failing goal across the whole map. Fix: when 146 `goal->node->chest_type == 1` (upstream's own ROM-derived "is this 147 THE big chest" flag), the goal is `GOAL_SCORE_UNSATISFIABLE` until 148 `*ap_ram.sram_dungeon_bigkeys` has that dungeon's bit - the same bit 149 formula `ap_node_islocked`'s `NODE_KEYBLOCK` branch already uses 150 (`node->screen->dungeon_id`, not the global "current dungeon", so it 151 stays correct for a chest in a dungeon Link is not currently in). An 152 unsatisfiable goal is silently skipped by `ap_goal_evaluate` forever - 153 it never becomes active, never fails, and never enters the given-up/ 154 retry cycle, which is what actually stops the ping-pong (not a 155 retry-suppression mechanism layered on top). Evidence: "The Eastern 156 Palace big chest". 157- `0014-door-with-no-vestibule-in-room-0089.patch` (ap_map.c) - every 158 door's approach point is offset 24px past its own tile, assuming a 159 little floor stands between "in front of it" and the transition. Room 160 `$0089`'s own north door (`tile_attr 0x4b`) has none: crossing its tile 161 is instant, so the generic offset places the goal on the far side of a 162 line Link's own room can never reach - `unsatisfiable` forever, the 163 resume-time stall this was built to fix. Fix: 0px offset for this one 164 door only (room `$0089`, `tile_attr 0x4b`), landing on the door's own 165 tile, which `ap_pathfind_local` already treats as passable within 3 166 grid-cells of Link's start - the same exemption every other door's 167 EXPLORE goal already depends on to be reachable at all. Two other 168 theories (a missing cross-screen adjacency link; a mis-detected door 169 direction) were checked with headless instrumentation and evidence and 170 ruled out before this one. Proven: a 20,000-frame headless run from the 171 exact reproduced stall walks through the door and keeps playing across 172 seven more rooms. Evidence: "A door with no vestibule ($0089)". 173- `0015-never-push-a-block-into-a-wall.patch` (ap_map.c) - `0012`'s fix is 174 room-scoped (`dungeon_room == 0x010B`), and every OTHER room that reuses a 175 tile id for a push block the static `ap_tile_attrs[]` cannot see room 176 context for will stall the identical way until someone finds it by hand - 177 the same shape `0014`'s generalization closed for door offsets. No static 178 table generalizes this: the SNES's own per-room tile reinterpretation 179 means a raw id alone never says "push block here." The general signal is 180 dynamic instead - `push_timer` ($7E0371) engaging while Link's position 181 does not move, observed by the pre-existing generic stuck detector 182 (`stationary_link_count >= 128` in `ap_follow_targets`). Fix: a small 183 per-screen memo (`ap_jam_screen`/`ap_jam_cells[8]`/`ap_jam_count`, file 184 statics, cleared the instant the active screen changes - correct, not 185 conservative, because a room's pushed-block state resets only on a real 186 screen transition). The stuck detector calls `ap_jam_note` when it fires 187 with `push_timer != 0`; `ap_pathfind_local`'s costing loop consults 188 `ap_jam_is` as its very first branch, ahead of the destination-bbox and 189 `0x1C` cases, forcing a wall cost. Proven organically: `0012` disabled for 190 one diagnostic build (`dungeon_room == 0x010B` → `0xFFFF`, reverted after), 191 a fresh `--make-home` run reproduced the exact historical Dam stall at 192 frame 3408 and `0015` alone caught it, correctly re-learned it on a second 193 room visit at frame ~10129, and stayed silent for the whole run once the 194 real `0012` was restored alongside it. Evidence: "Never push a block into 195 a wall". 196- `0016-hc-routines-already-done-in-open-mode.patch` (ap_plan.c) - two 197 checks in `ap_goal_score`'s `GOAL_SCRIPT` case. Every `"HC "`-prefixed 198 script (killing a castle guard/miniboss) exists only to progress the 199 vanilla rescue sequence, which the open-mode preset 200 (`zbanks::rando::PRESET`) marks done from frame 0 201 (`sram_progress2 & 0x04`) - gated `unsatisfiable` on that bit, by name 202 prefix so a currently-commented-out HC script inherits it too. Separately, 203 a GENERAL check for every routine: if its own screen has a `NODE_CHEST` 204 and every one already shows its `sram_room_state` bit (the identical read 205 `GOAL_CHEST`'s own scoring uses), the routine is `GOAL_SCORE_COMPLETE` - 206 Jev was offered "Blind's House Block Puzzle" at 79% with that room's 207 chests already open. A screen with no chest (AgTower, Kak well) is 208 untouched. The HC gate is proven live-fire (`unsat` from the first scoring 209 pass); the chest check is code-reviewed only (reuses `GOAL_CHEST`'s 210 already-proven formula) since every fixture on disk completes the 211 script's task before its chest opens - see the next entry. Evidence: "A 212 routine already done, or already claimed, is never offered again". 213- **A `SCRIPT_SEQUENCE` task finishing is not the same as its goal being 214 reached**, and treating them as the same is a live bug, not yet fixed: 215 "Dam Chest Block Puzzle" is `ap_goal_complete`d the instant its hardcoded 216 movement string ends, whether or not the chest the script exists to open 217 actually opened. The wrong push sequence for this cartridge (see 218 `research/zbanks-alttp.md`'s Dam Chest section) still reports success and 219 the bot wanders off - looks-fine failure, the exact shape 220 `unrepresentable-over-documented` warns about. Do not "fix" this by 221 special-casing Dam; the general rule is that a `GOAL_SCRIPT`'s completion 222 must be judged by the SAME state check its own scoring uses (the chest 223 check above, or an equivalent), not by the task queue draining. 224- `0030-hammer-peg-indoors-general.patch` (ap_map.c, jev item 1) - 225 generalizes `0012`: at frame 88684 in Sahasrahla's hut, the pad showed 226 Y+Right with the hammer equipped while the active task was `LIFT_POT` - 227 Link swung the hammer at bare floor instead of walking to the pot. 228 `ap_tile_attrs_here` (introduced by `0012`, room-scoped to `$010B`) still 229 let `ap_follow_targets` mash Y at any OTHER room's reuse of raw tile 230 `0x27` (`ap_snes.h`'s only `TILE_ATTR_HMMR` entry). The real game never 231 treats `0x27` as a hammer peg indoors at all: zelda3's 232 `HandleItemTileAction_Dungeon` (dungeon.c:81dabb) only ever recognizes a 233 peg through the "replacement object" family (raw attribute `0x70-0x7F`, 234 a slot index into `dung_replacement_tile_state[]`, written `0x4040` by 235 `RoomDraw_HammerPegSingle`) - `0x27` indoors is always a plain solid 236 tile reused per-tileset (a push block's floor in `$010B`, this hut's 237 floor here). Fix: `ap_tile_attrs_here` now strips `TILE_ATTR_HMMR` 238 whenever `*ap_ram.in_building` is set, unconditionally - no room number 239 anywhere, and for `$010B` specifically this is exactly equivalent to 240 `0012`'s `return 0` (raw `0x27`'s only bit is HMMR, so stripping it 241 zeroes the same way). Outdoors is untouched: there, raw `0x27` 242 (`TileBehavior_Hookshottables`) genuinely is a hammer/hookshot peg, and 243 `ap_map_attr_from_ram`'s own fence carve-out already covers that 244 ambiguity. Proven: a fresh 100,000-frame headless regression (canonical 245 `--make-home`, Jev off) reaches the same healthy shape as prior runs 246 (PICKUP 16/25, CHEST 13/20, EXPLORE 99/155, only 2 stuck-link events, 247 neither hammer-related) with no new jams or regressions. No host fix: 248 the flat table's raw-id reuse is the SNES's own per-tileset 249 reinterpretation, the same class `0011`/`0012`/`0015` already 250 established a host cannot see from outside. Evidence: "The hammer swings 251 at bare floor". 252- `0031-dash-as-path-action.patch` (ap_snes.h, ap_map.c) - adds 253 `inventory_boots` (`$7EF355`) to the bot's RAM table, folds 254 `TILE_ATTR_BONK` (raw tile `0x57`, bonk rocks, which the game breaks only 255 while `link_is_running`) into `ap_pathfind_local`'s lift mask once the 256 boots are held (so the existing corner logic forbids a diagonal approach 257 for free), and gives the one waypoint that steps onto a BONK tile 0009's 258 A-held-with-direction bits, so `ap_follow_targets` drives the charge and 259 the dash with no new per-frame logic. Found at Lost Woods `0x0a06`, frame 260 92877: Link had the boots but only walked Up into the obstacle. The patch 261 cites a research section, "Dash as a path action", that 262 `research/zbanks-alttp.md` does not have yet. 263- `0032-glove-gating-general.patch` (ap_plan.c) - the glove requirement on 264 a liftable's goal comes from its tile's own `TILE_ATTR_LFT1`/`LFT2` bits 265 (`$52`/`$55` Power Glove, `$53`/`$56` Titan's Mitt; `alttp-ram-map.md`, 266 "Liftables, named and gated"), not `node->tile_attr == 0x55`, which only 267 ever gated the big grey rock. `REQUIREMENT_GLOVES_2` existed in 268 `ap_req.h` with no caller. The patch cites a research section, "Glove 269 gating", that `research/zbanks-alttp.md` does not have yet. 270- Proving a patch that changes pathing: every run diverges from the first 271 frame, so the A/B that isolates one place is a replay with the patch 272 gated on `ap_frame` (`--save-at` to find the frame) - a throwaway build, 273 never committed. See `../../../research/zbanks-alttp.md`, "The boots".