jevsnes.git / third-party / c / patches / 0015-never-push-a-block-into-a-wall.patch
0015-never-push-a-block-into-a-wall.patch98 lines · 4.2 KB · raw
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;