Зацеп за кромку нижнего ряда: do_fall пускает ряд до 3 + check_grab в midair
Уровень 7, комната 14: спуск с ряда 0 на ряд 2 через зацеп (повис на кромке кнопки (0,2), отпустил, взялся за (2,2)) не работал ни при какой фазе и задержке — Кид пролетал мимо и разгонялся до fall_y = 33, после чего окно зацепа закрыто уже по скорости. Корень: do_fall не давал curr_row выйти за 2, а check_grab целится в тайл ряда curr_row-1 — то есть ряд 2 своей комнаты становится целью только при curr_row == 3. В оригинале (seg005:0030) inc_curr_row безусловный, а get_tile для ряда 3 уходит по links.down (find_room_of_tile, seg006:005D). - pop_map.c do_fall: inc_curr_row теперь 2 -> 3; ветка «достиг y_land» гейтится по curr_row <= 2 (при ряде 3 ни in_wall, ни land звать нельзя — тайлов своей комнаты там нет, а до y_land[4] дело не доходит: комнату меняет check_leave_below на y >= 211). - pop_map.c check_action: восстановлена ветка ACT_MIDAIR (кадры 102..105, seg006:0619) — первые четыре кадра падения, где fall_y ещё не разогнан, зацеп не работал вовсе. Отсюда же «иногда цепляется, иногда нет». - tests-host/t_grab.c: регресс grab_below_room_edge_window_exists — сцена комнаты 14 + переход в 15 по pop_fell_out. До фикса ни одного зацепа, после — окно из 8 фаз X при задержках 0..5 кадров; проверяется и играбельность (зацеп при «отпустил и сразу зажал Shift»). - roomtest.c + pop_guard.h: чит-навигация ставит Кида на верхний ряд в комнате 14 уровня 7 (GRAB_DOWN_LEVEL/ROOM) — снизу этот спуск не проверить. Общее правило (снизу вверх) не тронуто: в верхнем ряду пола чаще нет и Кид проваливается сразу после телепорта. Проверено: tests-host 5/5 (55 в [grab]), size-check OK, живьём в MAME — Кид повис на кромке (2,2): frame=91 y=55 row=0 room=15. Доски: GRAB-BELOW-ROOM в BUGS_CLOSED, GRAB-KBD-TIMING в BUGS_OPEN (отложен пользователем до готовности всех уровней), план L7-FEATHER в TASKS_OPEN. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -149,11 +149,135 @@ TC_TEST(grab_same_for_all_key_patterns)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Спуск через НИЖНЮЮ КРОМКУ комнаты (BUG-GRAB-SEAM-DOWN) ---------- *
|
||||
* Уровень 7, комната 14 — по ней уровень и проходят: Кид на ряду 0 висит на
|
||||
* кромке кнопки (0,2), отпускает и в падении должен зацепиться за кромку
|
||||
* (2,2). Тайл-цель лежит В НИЖНЕМ ряду, то есть в момент зацепа персонаж
|
||||
* уже НИЖЕ комнаты — в оригинале это curr_row == 3, а get_tile для него
|
||||
* уходит по links.down в комнату снизу (find_room_of_tile, seg006:005D).
|
||||
*
|
||||
* Пока do_fall не пускал ряд дальше 2, check_grab на этом отрезке не звался
|
||||
* НИ РАЗУ: Кид пролетал мимо кромки, набирал fall_y > 32, и дальше окно
|
||||
* зацепа было закрыто уже по скорости — спуск становился непроходимым.
|
||||
* Тест держит именно это: окно существует и достижимо. */
|
||||
#define C 6 /* closer */
|
||||
#define O 15 /* opener */
|
||||
|
||||
static const uint8_t room14[30] = {
|
||||
C, F, O, E, E, E, E, E, F, F,
|
||||
W, W, W, E, E, E, E, E, W, W,
|
||||
F, F, P, E, E, E, E, E, W, W,
|
||||
};
|
||||
static const uint8_t room15[30] = {
|
||||
W, W, W, E, E, E, E, E, W, W,
|
||||
F, F, P, E, E, E, E, E, W, W,
|
||||
W, W, W, E, E, E, E, E, W, W,
|
||||
};
|
||||
/* Ряд 2 комнаты 14 = «ряд −1» комнаты 15 (pop_map_set_above): после перехода
|
||||
* зацеп продолжает целиться в ту же кромку, уже как в потолок. */
|
||||
static uint8_t above14[10];
|
||||
|
||||
#define ACT_MIDAIR 3
|
||||
#define ACT_FREEFALL 4
|
||||
|
||||
static uint8_t went_below; /* Kid уже пересёк нижнюю границу комнаты 14 */
|
||||
|
||||
/* Один кадр + переход в комнату снизу — ровно то, что делает главный цикл
|
||||
* roomtest.c по pop_fell_out (enter_room(down) + exit_room_timer). */
|
||||
static void step14(uint8_t keys)
|
||||
{
|
||||
sc_trace_clear(); /* трасса тут не нужна, важен исход */
|
||||
sc_run(keys, 1);
|
||||
if (pop_fell_out) {
|
||||
uint8_t i;
|
||||
pop_fell_out = 0;
|
||||
went_below = 1;
|
||||
for (i = 0; i < 10; i++) above14[i] = room14[20 + i];
|
||||
sc_room(room15, 15);
|
||||
pop_map_set_edges(0, 0, 14, 0); /* сверху — комната 14 */
|
||||
pop_map_set_above(above14);
|
||||
pop_leave_timer = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Исход попытки:
|
||||
* 'G' — повис на НИЖНЕЙ кромке (то, ради чего тест: ряд 2 комнаты 14);
|
||||
* 'U' — зацепился обратно за ту кромку, с которой сорвался (ряд 0) —
|
||||
* это другой, всегда работавший случай, и засчитывать его нельзя;
|
||||
* '.' — пролетел мимо; 'x' — так и не сорвался (проба не состоялась). */
|
||||
static char probe_down(uint8_t x, uint8_t delay)
|
||||
{
|
||||
uint8_t i;
|
||||
sc_room(room14, 14);
|
||||
pop_map_set_edges(0, 0, 0, 15); /* links комнаты 14: снизу — 15 */
|
||||
pop_map_set_above(0);
|
||||
pop_leave_timer = 0;
|
||||
pop_fell_out = 0;
|
||||
went_below = 0;
|
||||
/* Лицом ВЛЕВО у правой кромки кнопки: обрыв позади — это и есть поза
|
||||
* спуска (pop_down_action, seg005:482). */
|
||||
sc_kid_at_x(2, 0, x, -1);
|
||||
|
||||
/* ↓ + Shift — спуск с уступа (pop_down_action, seq_68) и вис на кромке.
|
||||
* Shift держим, иначе Кид сорвётся, не успев повиснуть. */
|
||||
for (i = 0; i < 30; i++) {
|
||||
step14(SC_D | SC_SHIFT);
|
||||
if (Kid.action == ACT_HANG_CLIMB || Kid.action == ACT_HANG_STRAIGHT) break;
|
||||
}
|
||||
if (Kid.action != ACT_HANG_CLIMB && Kid.action != ACT_HANG_STRAIGHT) return 'x';
|
||||
/* отпустить Shift — сорваться с виса (hang_fall, seg005) */
|
||||
for (i = 0; i < 12; i++) {
|
||||
step14(0);
|
||||
if (Kid.action == ACT_MIDAIR || Kid.action == ACT_FREEFALL) break;
|
||||
}
|
||||
if (Kid.action != ACT_MIDAIR && Kid.action != ACT_FREEFALL) return 'x';
|
||||
|
||||
for (i = 0; i < delay; i++) step14(0); /* пауза перед Shift */
|
||||
for (i = 0; i < 30; i++) {
|
||||
step14(SC_SHIFT);
|
||||
if (Kid.action == ACT_HANG_CLIMB || Kid.action == ACT_HANG_STRAIGHT) {
|
||||
if (!went_below) return 'U';
|
||||
/* За ЧТО взялся: после перехода вниз ряд 0 = кромка ряда 2
|
||||
* комнаты 14 (наша цель), ряд 2 = кромка ряда 1 комнаты 15
|
||||
* (этажом ниже — этот случай работал и раньше). */
|
||||
return Kid.curr_row == 0 ? 'G' : 'g';
|
||||
}
|
||||
}
|
||||
return '.';
|
||||
}
|
||||
|
||||
#define XD_LO 90
|
||||
#define XD_HI 113
|
||||
|
||||
#define XD_DELAY_MAX 8
|
||||
|
||||
TC_TEST(grab_below_room_edge_window_exists)
|
||||
{
|
||||
uint8_t x, d, n = 0, n0 = 0;
|
||||
for (x = XD_LO; x <= XD_HI; x++)
|
||||
for (d = 0; d <= XD_DELAY_MAX; d++)
|
||||
if (probe_down(x, d) == 'G') { n++; if (!d) n0++; }
|
||||
TC_TRUE(n > 0);
|
||||
/* И играбельность: зацеп обязан удаваться при самом естественном вводе —
|
||||
* отпустил вис и сразу зажал Shift (d = 0). Окно, доступное только с
|
||||
* точной задержкой, для игрока почти равно отсутствующему. */
|
||||
TC_TRUE(n0 > 0);
|
||||
if (!n) { /* карта исходов: строка = задержка Shift */
|
||||
uint8_t i;
|
||||
for (i = 0; i <= XD_DELAY_MAX; i++) {
|
||||
put('d'); put((char)('0' + i)); put(':'); put(' ');
|
||||
for (x = XD_LO; x <= XD_HI; x++) put(probe_down(x, i));
|
||||
put('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
sc_init();
|
||||
TC_RUN(grab_window_exists);
|
||||
TC_RUN(grab_window_reachable_by_safe_step);
|
||||
TC_RUN(grab_same_for_all_key_patterns);
|
||||
TC_RUN(grab_below_room_edge_window_exists);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user