1diff --git a/third-party/c/zbanks-alttp/ap_map.c b/third-party/c/zbanks-alttp/ap_map.c 2index e481fe75..a1b2c3d4 100644 3--- a/third-party/c/zbanks-alttp/ap_map.c 4+++ b/third-party/c/zbanks-alttp/ap_map.c 5@@ -27,6 +27,66 @@ 6 static uint8_t ap_target_sprite_index = 0; 7 static struct ap_screen * ap_target_screen; 8 9+// Carried patch (jev): a general "never push a block into a wall" rule. 10+// Patches 0011/0012 each hardcoded one room where a static tile id/sprite 11+// meant something upstream's flat, room-agnostic tables could not say - 12+// but the underlying SNES tile-id space is reused per room BY DESIGN (the 13+// same raw id means a fence in one room, a hammer peg in another, a push 14+// block in a third - ap_snes.h's own comment on 0x27 already admits this), 15+// so no static table, however room-scoped, generalizes: the next 16+// undiscovered reuse would need its own new patch and its own new room 17+// check, forever. The general signal is not the tile id at all - it is 18+// what the GAME does when Link tries to interact with it: `push_timer` 19+// engages (a real push/pull/lift attempt in progress) while Link's own 20+// position does not move. That is true for every kind of jam, in every 21+// room, without knowing in advance which tile ids or rooms can produce 22+// one. `ap_follow_targets` already detects "stuck" generically 23+// (`stationary_link_count >= 128`, pre-existing); this patch only teaches 24+// that existing detector to REMEMBER the jam, scoped to the current 25+// screen (cleared the moment the active screen changes - a room's pushed 26+// blocks reset only on a real screen transition, so that is exactly when 27+// stale jam knowledge must be forgotten too), and to feed it back into 28+// `ap_pathfind_local`'s own costing as a wall. Evidence and proof: 29+// research/zbanks-alttp.md, "Never push a block into a wall (general 30+// rule)". 31+#define AP_JAM_MAX 8 32+static struct ap_screen * ap_jam_screen = NULL; 33+static struct xy ap_jam_cells[AP_JAM_MAX]; 34+static int ap_jam_count = 0; 35+ 36+static void 37+ap_jam_sync(struct ap_screen * screen) 38+{ 39+ if (screen != ap_jam_screen) { 40+ ap_jam_screen = screen; 41+ ap_jam_count = 0; 42+ } 43+} 44+ 45+static void 46+ap_jam_note(struct ap_screen * screen, struct xy xy) 47+{ 48+ ap_jam_sync(screen); 49+ for (int i = 0; i < ap_jam_count; i++) { 50+ if (XYEQ(ap_jam_cells[i], xy)) return; 51+ } 52+ if (ap_jam_count < AP_JAM_MAX) { 53+ ap_jam_cells[ap_jam_count++] = xy; 54+ LOGB("Jammed cell noted: " PRIXY " in screen %s - treating it as a wall until the room resets", 55+ PRIXYF(xy), screen ? screen->name : "(null)"); 56+ } 57+} 58+ 59+static bool 60+ap_jam_is(struct ap_screen * screen, struct xy xy) 61+{ 62+ ap_jam_sync(screen); 63+ for (int i = 0; i < ap_jam_count; i++) { 64+ if (XYEQ(ap_jam_cells[i], xy)) return true; 65+ } 66+ return false; 67+} 68+ 69 const char * const ap_node_type_names[] = { 70 #define X(type) [CONCAT(NODE_, type)] = #type, 71 NODE_TYPE_LIST 72@@ -725,6 +785,9 @@ 73 if (stationary_link_count >= 128) { 74 LOGB("Stuck Link Detected! " PRIXY " en route to " PRIXY, 75 PRIXYF(link), PRIXYF(ap_targets[0].tl)); 76+ if (*ap_ram.push_timer != 0) { 77+ ap_jam_note(map_screens[XYMAPSCREEN(link)], ap_targets[0].tl); 78+ } 79 ap_target_timeout = 0; 80 stationary_link_count = 0; 81 } 82@@ -1099,7 +1162,15 @@ 83 uint8_t ledge_mask = 0; 84 uint8_t ledge_dir = DIR_NONE; 85 //if (tile & (TILE_ATTR_WALK | TILE_ATTR_DOOR)) { 86- if (tile & (TILE_ATTR_WALK)) { 87+ if (ap_jam_is(screen, mapxy)) { 88+ // A cell proven immovable ahead of time (see ap_jam_note) 89+ // is a wall no matter what the static tile table or the 90+ // current destination bbox says - ahead of even the 91+ // destination-override and 0x1C special cases below, so 92+ // a jammed cell can never be reclassified as free just 93+ // because a goal's own bbox happens to cover it. 94+ cost = (1 << 20); 95+ } else if (tile & (TILE_ATTR_WALK)) { 96 cost = 0; 97 } else if ((tile & TILE_ATTR_DOOR) && XYL1DIST(XY(x, y), src) <= 3) { 98 cost = 0;