Приёмка ур. 2–3: сквозь стену, клин в шве, бар под плитой, глухой страж
Четыре разбора с прогонов пользователя; три бага закрыты, четвёртый (BUG-SPIKE-1, пики) заведён с замером и гипотезой. BUG-JUMPWALL-1 (Critical) — недолетевший прыжок проходил СКВОЗЬ стену. check_collisions держал флаги перекрытия ОДНОГО ряда, а оригинал (seg004:0004) — трёх, и move_coll_to_prev (seg004:00DF) берёт «прошлые» флаги из нужного. Наш prev=3 («уже перекрывал») подавлял бамп ровно на кадре смены ряда, а в падении ряд меняется почти каждый кадр — переход 0→1 на стене приходился как раз на него. Порт трёх рядов дословно. Воспроизведено и закрыто на харнессе (новый набор t_wall: свип по 20 стартовым X, 4 давали проход сквозь кладку); 1723 трассы t_phys НЕ изменились — правка поведение-сохраняющая. Живьём подтвердил пользователь. BUG-GUARD-DEAF-1 (Major) — is_guard_notice не взводился НИГДЕ, поэтому неактивный страж не оборачивался на Кида за спиной никогда. Портированы все пять мест оригинала: опкод SOUND в play_seq (звуки 0..2), bumped_sound, мягкое/среднее приземление, обрушенная плита, щелчок кнопки. Ждёт игровой проверки боем в комнате 11 уровня 2. BUG-SEAM-WEDGE-1 — клин кладки в пустом (2,0). Сосед угла снизу-слева лежит в комнате по диагонали (room_BL); мы безусловно считали его стеной, оригинал (load_rowbelow, seg008:368) — только когда такой комнаты нет. Ряд «снизу» стал 11-байтным: [10] = тайл (0,9) диагональной комнаты. BUG-LOOSE-3 — чёрный бар под упавшей плитой-потолком. pop_ceil_bake_empty стирал полосу и восстанавливал только два тайла ряда −1, а в полосу лезет графика соседа слева и верхушки ряда 0. Теперь перерисовываются ряды −1 и 0, колонки col−1..col+1. Проверено попиксельной сверкой с эталонной перерисовкой: 0 различий. Плюс карта связности комнат уровней 1–3 (TASKS.md): на ур. 2 недостижимых нет, на ур. 3 это 23 и 24 — те же односторонние ссылки, что дали 13/18/24 на уровне 1, только комнаты полностью пустые. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -500,6 +500,8 @@ static void land(void)
|
||||
} else {
|
||||
seq = SEQ_17_SOFT_LAND;
|
||||
}
|
||||
/* seg005:185 — мягкое приземление слышно (звука нет, флаг есть). */
|
||||
is_guard_notice = 1;
|
||||
} else {
|
||||
uint8_t deadly = (uint8_t)(Kid.fall_y >= 33); /* СНЯТЬ до обнуления */
|
||||
pop_kid_hurt = 1;
|
||||
@@ -512,6 +514,7 @@ static void land(void)
|
||||
pop_kid_dead = 1;
|
||||
return;
|
||||
}
|
||||
is_guard_notice = 1; /* seg005:195 */
|
||||
seq = SEQ_20_MEDIUM_LAND;
|
||||
}
|
||||
Kid.fall_x = Kid.fall_y = 0;
|
||||
@@ -1123,9 +1126,13 @@ static void bumped_fall(void)
|
||||
if (Kid.action == ACT_FREEFALL) {
|
||||
Kid.fall_x = 0;
|
||||
} else {
|
||||
kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */
|
||||
kid_set_seq(SEQ_45_BUMPFALL);
|
||||
pop_kid_play();
|
||||
}
|
||||
/* bumped_sound (seg004:05F1) — ВНЕ ветвления, как в оригинале: удар о
|
||||
* стену слышен и в свободном падении. Звука у нас нет, но «Кид нашумел»
|
||||
* взводить обязаны — см. BUG-GUARD-DEAF-1. */
|
||||
is_guard_notice = 1;
|
||||
}
|
||||
|
||||
/* bumped_floor (seg004:053B): удар о стену, когда под персонажем ПОЛ.
|
||||
@@ -1173,8 +1180,9 @@ static void bumped_floor(void)
|
||||
(frame >= 102 && frame < 107))
|
||||
kid_set_seq(SEQ_46_HARDBUMP);
|
||||
else
|
||||
kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */
|
||||
kid_set_seq(SEQ_47_BUMP);
|
||||
pop_kid_play();
|
||||
is_guard_notice = 1; /* bumped_sound (seg004:05F1) */
|
||||
}
|
||||
|
||||
/* ---- check_collisions (seg004:0004) -------------------------------- *
|
||||
@@ -1195,13 +1203,22 @@ static void bumped_floor(void)
|
||||
* Тем же корнем лечится влёт в стену на длинном прыжке (комн. 6) и провал
|
||||
* сквозь пол после приземления у стены (комн. 5).
|
||||
*
|
||||
* ОТЛИЧИЕ ОТ ОРИГИНАЛА (сознательное): он держит флаги ТРЁХ рядов (curr/
|
||||
* above/below), чтобы при смене ряда «прошлые» флаги были настоящими. У
|
||||
* нас ряд один: сменился ряд — считаем, что персонаж уже перекрывал всё
|
||||
* (prev = 3), то есть на кадре смены ряда бампа нет, а на следующем он
|
||||
* посчитается честно. Три ряда — это втрое больше get_tile каждый кадр;
|
||||
* если где-то вылезет пропущенный бамп ровно на кадре приземления —
|
||||
* вернуться сюда. */
|
||||
* ТРИ РЯДА — как в оригинале (было: один, и это оказалось BUG-JUMPWALL-1).
|
||||
* Оригинал каждый кадр считает флаги для рядов row−1, row, row+1, а
|
||||
* move_coll_to_prev (seg004:00DF) в начале СЛЕДУЮЩЕГО кадра выбирает из них
|
||||
* тот, что соответствует ряду прошлого кадра. Так «прошлые» флаги остаются
|
||||
* настоящими и на кадре смены ряда.
|
||||
*
|
||||
* Наш прежний упрощённый вариант (prev = 3 = «уже перекрывал») подавлял бамп
|
||||
* ровно на этом кадре — а в падении ряд меняется чуть ли не каждый кадр, и
|
||||
* переход 0→1 на стене приходится как раз на него. Симптом: недолетевший
|
||||
* прыжок уходит СКВОЗЬ кладку по свободной траектории (ур. 3 комн. 14).
|
||||
* Цена трёх рядов — 3×14 get_tile на кадр вместо 14; замерено, что это
|
||||
* дешевле любого из обходных вариантов, а поведение совпадает с оригиналом.
|
||||
*
|
||||
* Ряды вне комнаты (−1 и 3) считаются штатно: get_tile сам резолвит ряд −1
|
||||
* через комнату сверху (g_above), а ряд 3 отдаёт WALL — то же «через край
|
||||
* не бампим», что и раньше. */
|
||||
/* Колонки считаем от −2 до 11, а не 0..9: колонки за швом — это тайлы
|
||||
* СОСЕДНЕЙ комнаты (get_tile резолвит их через g_lcol/g_rcol), и решётка
|
||||
* соседа обязана бампить так же, как своя (иначе Kid уходит сквозь
|
||||
@@ -1215,6 +1232,7 @@ static void bumped_floor(void)
|
||||
#define COLL_IDX(c) ((c) - COLL_C0)
|
||||
#define COLL_ROW_NONE ((int8_t)-128)
|
||||
static uint8_t coll_prev[COLL_N], coll_curr[COLL_N];
|
||||
static uint8_t coll_above[COLL_N], coll_below[COLL_N];
|
||||
static int8_t coll_prev_row = COLL_ROW_NONE; /* ряд, к которому относится coll_prev */
|
||||
/* «нет колонки»: НЕ −1, потому что −1/−2 — законные колонки шва. */
|
||||
#define BUMP_NONE ((int8_t)-128)
|
||||
@@ -1244,6 +1262,32 @@ static void set_char_collision(void)
|
||||
* колонки от col(char_x_left)−1 до col(char_x_right)+2 и оставляет прочие
|
||||
* как есть; мы считаем все четырнадцать — они дешёвые (индекс в массив +
|
||||
* switch), зато не остаётся протухших ячеек (FIX_COLL_FLAGS SDLPoP). */
|
||||
/* move_coll_to_prev (seg004:00DF): в prev кладём флаги ряда, в котором
|
||||
* персонаж был НА ПРОШЛОМ кадре. Массивы curr/above/below в этот момент
|
||||
* ещё хранят прошлый кадр, поэтому нужный ряд просто выбирается из них по
|
||||
* разнице рядов. Условия — дословно оригинальные, включая ±3: ряд может
|
||||
* «завернуться» при смене комнаты (2 -> 0 вниз, 0 -> 2 вверх), и тогда
|
||||
* геометрия колонок та же, что у текущего ряда.
|
||||
*
|
||||
* Первый кадр (и кадр после смены комнаты, pop_coll_invalidate) прошлого
|
||||
* ряда не имеет: prev = «уже перекрывал всё», бампа на нём нет. */
|
||||
static void move_coll_to_prev(int8_t row)
|
||||
{
|
||||
const uint8_t *src;
|
||||
int8_t i;
|
||||
if (coll_prev_row == COLL_ROW_NONE) {
|
||||
for (i = 0; i < COLL_N; i++) coll_prev[i] = 3;
|
||||
return;
|
||||
}
|
||||
if (row == coll_prev_row || row + 3 == coll_prev_row || row - 3 == coll_prev_row)
|
||||
src = coll_curr; /* ряд не менялся */
|
||||
else if (row + 1 == coll_prev_row || row - 2 == coll_prev_row)
|
||||
src = coll_above; /* поднялся на ряд */
|
||||
else
|
||||
src = coll_below; /* опустился на ряд */
|
||||
for (i = 0; i < COLL_N; i++) coll_prev[i] = src[i];
|
||||
}
|
||||
|
||||
static void get_row_collision_data(int8_t row, uint8_t *flags)
|
||||
{
|
||||
int8_t col;
|
||||
@@ -1271,14 +1315,13 @@ static void check_collisions(void)
|
||||
if (Kid.curr_row < 0 || Kid.curr_row > 2) { coll_prev_row = COLL_ROW_NONE; return; }
|
||||
|
||||
set_char_collision();
|
||||
/* move_coll_to_prev (seg004:00DF) в нашем однорядном варианте. */
|
||||
if (coll_prev_row == Kid.curr_row) {
|
||||
for (col = 0; col < COLL_N; col++) coll_prev[col] = coll_curr[col];
|
||||
} else {
|
||||
for (col = 0; col < COLL_N; col++) coll_prev[col] = 3; /* «уже перекрывал» */
|
||||
}
|
||||
move_coll_to_prev(Kid.curr_row);
|
||||
coll_prev_row = Kid.curr_row;
|
||||
get_row_collision_data(Kid.curr_row, coll_curr);
|
||||
/* Порядок важен: prev уже забран, теперь три ряда пересчитываются
|
||||
* НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). */
|
||||
get_row_collision_data(Kid.curr_row, coll_curr);
|
||||
get_row_collision_data((int8_t)(Kid.curr_row + 1), coll_below);
|
||||
get_row_collision_data((int8_t)(Kid.curr_row - 1), coll_above);
|
||||
/* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка,
|
||||
* в которой флаг перешёл 0→1. */
|
||||
for (col = COLL_C0 + COLL_N - 1; col >= COLL_C0; --col) {
|
||||
@@ -1418,10 +1461,13 @@ static void check_press(void)
|
||||
int8_t c = Kid.curr_col;
|
||||
if (g_above && g_link_u && c >= 0 && c <= 9 &&
|
||||
(g_above[c] & 0x1F) == TILE_LOOSE && !(g_above[c] & 0x20) &&
|
||||
(int8_t)pop_ceil_modif[c] <= 0)
|
||||
(int8_t)pop_ceil_modif[c] <= 0) {
|
||||
pop_ceil_modif[c] = 1; /* make_loose_fall(1) */
|
||||
is_guard_notice = 1; /* seg006:1734 */
|
||||
}
|
||||
} else if (get_tile_above_char() == TILE_LOOSE) {
|
||||
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
|
||||
is_guard_notice = 1; /* seg006:1734 */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1435,8 +1481,10 @@ static void check_press(void)
|
||||
uint8_t tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||
if (t == TILE_OPENER || t == TILE_CLOSER)
|
||||
pop_trigger_button(g_room, tp, t, pop_trob_modif(g_room)[tp]);
|
||||
else if (t == TILE_LOOSE)
|
||||
else if (t == TILE_LOOSE) {
|
||||
make_loose_fall(tp, 1);
|
||||
is_guard_notice = 1; /* seg006:1734 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user