jevsnes.git / third-party / c / patches / CLAUDE.md
CLAUDE.mdpreviewCLAUDE.mdsource273 lines · 18.4 KB · raw
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".