1--- a/third-party/c/zbanks-alttp/ap_snes.h 2+++ b/third-party/c/zbanks-alttp/ap_snes.h 3@@ -155,6 +155,7 @@ 4 X(inventory_base, uint8_t, 0x7EF33F) \ 5 X(inventory_bombs, uint8_t, 0x7EF343) \ 6 X(inventory_hammer, uint8_t, 0x7EF34B) \ 7+ X(inventory_boots, uint8_t, 0x7EF355) \ 8 X(inventory_gloves, uint8_t, 0x7EF354) \ 9 X(inventory_sword, uint8_t, 0x7EF359) \ 10 X(inventory_quiver, uint8_t, 0x7EF377) \ 11--- a/third-party/c/zbanks-alttp/ap_map.c 12+++ b/third-party/c/zbanks-alttp/ap_map.c 13@@ -1156,6 +1156,22 @@ 14 if (*ap_ram.inventory_gloves >= 1) lift_mask |= TILE_ATTR_LFT1; 15 if (*ap_ram.inventory_gloves >= 2) lift_mask |= TILE_ATTR_LFT2; 16 if (*ap_ram.inventory_hammer >= 1) lift_mask |= TILE_ATTR_HMMR; 17+ // Carried patch (jev): fold TILE_ATTR_BONK (raw tile 0x57, 18+ // TileBehavior_BonkRocks - a rock/bramble row the disassembly's own 19+ // player.c:Link_BonkAndSmash only ever breaks while `link_is_running`) 20+ // into the SAME mask that already governs cost and the diagonal 21+ // "square with it" restriction for pots/rocks/hammer pegs below. 22+ // Folding it in here, rather than adding a parallel branch, is what 23+ // makes the existing corner logic forbid a diagonal approach to a 24+ // dash-only obstacle for free - the same reason a diagonal approach to 25+ // a liftable is already forbidden. `ap_goal_add` (ap_plan.c) already 26+ // requires REQUIREMENT_BOOTS for an EXPLORE goal whose own destination 27+ // tile is BONK; this is the missing other half - a path that merely 28+ // CROSSES a bonk row (Lost Woods 0x0a06, frame 92877 - Link had the 29+ // boots but only walked Up into the obstacle) was walled off outright, 30+ // with no route at all past it, goal or no goal. Evidence: 31+ // research/zbanks-alttp.md, "Dash as a path action". 32+ if (*ap_ram.inventory_boots) lift_mask |= TILE_ATTR_BONK; 33 34 static struct point_state buf[0x82 * 0x82]; 35 36@@ -1632,7 +1648,34 @@ 37 //XXX This is normally bad, but for now assume recoil 38 //assert_bp(!((next->xy.x <= screen->tl.x + 15) || (next->xy.y <= screen->tl.y + 15))); 39 //assert_bp(!((next->xy.x >= screen->br.x - 15) || (next->xy.y >= screen->br.y - 15))); 40- ap_targets[count++] = (struct ap_target) { .tl = next->xy }; 41+ struct ap_target dash_target = { .tl = next->xy }; 42+ // Carried patch (jev): a step onto a BONK tile (bonk rocks, 43+ // dash-only obstacles - see this function's own lift_mask 44+ // above) does not respond to ordinary walking: the real 45+ // game only breaks one while `link_is_running` 46+ // (zelda3 player.c's Link_BonkAndSmash). Give this ONE 47+ // waypoint the exact A-held-with-direction bits patch 0009 48+ // already gives SCRIPT_SEQUENCE's numpad dash characters, 49+ // so ap_follow_targets's existing joypad_mask handling (no 50+ // new per-frame logic) drives both the ~29-frame charge and 51+ // the dash itself. `next->from` is the predecessor (one 52+ // step closer to `start_xy`), so `next->xy - next->from->xy` 53+ // is the direction Link actually travels arriving here. 54+ if ((next->tile_attrs & TILE_ATTR_BONK) && *ap_ram.inventory_boots) { 55+ struct xy travel = XYOP2(next->xy, -, next->from->xy); 56+ const uint16_t dash_dir_mask = SNES_MASK(UP) | SNES_MASK(DOWN) | 57+ SNES_MASK(LEFT) | SNES_MASK(RIGHT); 58+ uint16_t dir_bit = 0; 59+ if (travel.x > 0 && travel.y == 0) dir_bit = SNES_MASK(RIGHT); 60+ else if (travel.x < 0 && travel.y == 0) dir_bit = SNES_MASK(LEFT); 61+ else if (travel.y > 0 && travel.x == 0) dir_bit = SNES_MASK(DOWN); 62+ else if (travel.y < 0 && travel.x == 0) dir_bit = SNES_MASK(UP); 63+ if (dir_bit != 0) { 64+ dash_target.joypad = dir_bit | SNES_MASK(A); 65+ dash_target.joypad_mask = dash_dir_mask | SNES_MASK(A); 66+ } 67+ } 68+ ap_targets[count++] = dash_target; 69 //printf("%zu: "PRIXY "\n", count, PRIXYF(ap_targets[count-1])); 70 } 71 }