Уровни 12 и 13: тень, Джафар, падающие плиты + разбор слоёв

Уровень 12 (тень) — порт seg002/seg006:
- check_shadow: подъём тени в комнате 15 по условию «меч подобран»,
  init_shad_12, вход падением (seq 7);
- autocontrol_shadow_level12: ждать Кида, бой, сближение, СЛИЯНИЕ;
- общий урон (ранил тень — ранил себя) и check_killed_shadow
  (убил тень — убил себя);
- таймер вспышки слияния 42 -> -1, sword_disappears при уходе из
  комнаты 18, появление плит в комнатах 2/13 после слияния.

Переход 12 -> 13 бесшовный: двери у уровня нет, он кончается фактом
попадания в комнату 23 (tbl_seamless_exit); флаг pop_seamless не даёт
сбросить HP.  Чит навигации по комнатам этот триггер придерживает —
иначе комнату 23 двенадцатого уровня не посмотреть в принципе.

Уровень 13 (Джафар): guard_notice_timer (фора после встречи),
on_guard_killed + Jaffar_exit, check_fall_flo (гряда плит сверху) и три
исключения loose-механики.  Плюс спрайты визиря VIZIER.DAT — без них он
рисовался обычным стражем; заодно цвет из данных комнаты применяется
только к обычному стражу, как в оригинале.

Падающие плиты — семь дефектов, найденных прогонами:
- MOB_MAX 4 -> 14: check_fall_flo роняет шесть плит разом, лишние
  терялись без щебня;
- одиночные сигналы посадки/провала больше не затираются в кадре;
- честный loose_fall: сбитая плита рождается в том же кадре, от места
  удара, с половинной скоростью;
- при снятии плиты метится и сосед справа (висел передний торец);
- heal и отрисовка кусков разнесены на два прохода;
- куски рисуются ПОСЛЕ фона, порядок между собой — по убыванию y
  (compare_curr_objs для пары 0x80 сортирует наоборот);
- wall_pattern убран из ceil_over_kid_tile: у оригинала узор из
  draw_tile_bottom идёт в фон, а не в передний слой.

Хост-тесты: два новых набора, t_shadow (45 проверок) и t_jaffar (44).
TK_ROOMS 8 -> 24 — спецсобытия адресуют комнаты по реальным номерам.

Осознанные расхождения (docs/impl_diff.md): нет мигания Кида спрайтами
тени при слиянии, чит навигации не запускает бесшовный переход, чит K
убивает через штатный путь смерти.

Открытым остаётся разбор слоёв: MOB-CLIP-RIGHT, MID-OVERLAY-LAYER,
GUARD-FALLOUT-VICTORY, BG-ONCE (BUGS_OPEN.md / TASKS_OPEN.md).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 18:21:52 +03:00
parent 716f6f9273
commit 67d62e3e8b
37 changed files with 3169 additions and 79 deletions
+336 -16
View File
@@ -236,6 +236,16 @@ void pop_kid_hp_reset(void) __banked
pop_kid_dead = 0;
}
/* Бесшовный переход (seamless, seg003:158): уровень стартует, НЕ трогая HP —
* Кид уносит на следующий и потолок, и полученный урон, потому что для него
* это не «новый уровень», а продолжение того же бега. Всё остальное, что
* делает pop_kid_hp_reset, нужно и здесь: дельта кадра и признак смерти. */
void pop_kid_hp_carry(void) __banked
{
hitp_delta = 0;
pop_kid_dead = 0;
}
/* init_game (seg003:0043): начало ИГРЫ, а не уровня — стартовое HP. */
void pop_kid_hp_newgame(void) __banked { hitp_beg_lev = POP_START_HITP; }
@@ -249,25 +259,39 @@ static void pop_kid_add_life(void)
hitp_delta = (int8_t)(hitp_max - hitp_curr);
}
/* Тот же add_life наружу: слияние с тенью (уровень 12) даёт ровно ту же
* прибавку, что большая склянка, а живёт ИИ тени в другом банке. */
void pop_add_life(void) __banked { pop_kid_add_life(); }
/* Урон — через общий pop_take_hp (резидент pop_guard.c): физика работает
* над Char, значит и HP обязаны убавляться у ТОГО, кто сейчас в Char. Своя
* копия take_hp здесь была Kid-only и правила hitp_curr мимо дельты. */
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down) __banked
/* Перечитать fg соседей из уровня, НЕ трогая историю коллизий. Отдельно от
* pop_map_set_edges, потому что нужен и посреди жизни в комнате: тайл соседа
* может измениться, пока тот не отрисован (плита, брошенная Кидом на
* полпути, доваливается заочно — animate_loose в pop_trob.c). g_lcol/g_rcol
* до этого были снимком НА МОМЕНТ ВХОДА, и коллизия у шва продолжала считать
* провалившуюся плиту полом. */
void pop_map_refresh_edges(void) __banked
{
uint8_t i;
if (!g_link_l && !g_link_r) return;
pop_level_access_begin();
for (i = 0; i < 30; i++) {
if (g_link_l) g_lcol[i] = pop_level_tile_raw(g_link_l, i);
if (g_link_r) g_rcol[i] = pop_level_tile_raw(g_link_r, i);
}
pop_level_access_end();
}
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down) __banked
{
g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down;
/* fg соседей — прямо из уровня, одной скобкой доступа на обе комнаты.
* Держать ещё одну копию в приложении незачем: карта — единственный её
* потребитель (отрисовке швов хватает кромочных колонок pop_room_load). */
if (left || right) {
pop_level_access_begin();
for (i = 0; i < 30; i++) {
if (left) g_lcol[i] = pop_level_tile_raw(left, i);
if (right) g_rcol[i] = pop_level_tile_raw(right, i);
}
pop_level_access_end();
}
pop_map_refresh_edges();
/* смена комнаты — колонки перепроецированы, прошлые флаги перекрытия
* недействительны (в оригинале prev_coll_room != curr_row_coll_room и
* бампа на кадре входа нет). БОКОВОЙ переход это отменяет: вызывающий
@@ -1349,13 +1373,52 @@ uint8_t pop_get_item_action(void) __banked
}
}
/* Спецсобытие уровня 12 «появление плит» (seg006:1063, ветка внутри
* check_on_floor). После слияния с тенью (pop_united_shadow == 1) в
* верхнем ряду комнат 2 и 13 пропасти становятся проходимыми: персонаж,
* который ДОЛЖЕН БЫЛ провалиться, вместо этого достраивает под собой пол.
* Плита появляется ровно под ним, шаг за шагом — это не «включили этаж», а
* дорожка, возникающая под ногами.
*
* Возврат 1 = «пол появился, падать не надо». Перерисовываем сам тайл и
* соседа справа на ОБЕИХ страницах — тем же приёмом, что pop_skel_wake_tile
* (у оригинала это пара set_redraw_full/set_wipe на curr_tilepos и +1).
*
* Комната берётся РЕЗОЛВНУТОЙ (tile_room_of): персонаж может стоять в шве,
* и тогда пол обязан появиться в соседней комнате, как и у оригинала —
* там get_tile переставляет curr_room/curr_tilepos до присваивания. */
#define HIDDEN_FLOOR_ROOM_A 2
#define HIDDEN_FLOOR_ROOM_B 13
#define HIDDEN_FLOOR_COL_B 6 /* в комнате 13 — только правая половина */
static uint8_t floors_appear(void)
{
uint8_t room, tp;
if (pop_current_level != SHADOW_FIGHT_LEVEL) return 0;
if (pop_united_shadow >= 0) return 0; /* только ПОСЛЕ слияния */
if (Char.curr_row != 0) return 0;
if (Char.room != HIDDEN_FLOOR_ROOM_A &&
!(Char.room == HIDDEN_FLOOR_ROOM_B && Char.curr_col >= HIDDEN_FLOOR_COL_B))
return 0;
room = tile_room_of(Char.curr_col, Char.curr_row, &tp);
if (!room) return 0;
pop_level_set_tile(room, tp, TILE_FLOOR);
if (room == g_room) {
g_fg[tp] = TILE_FLOOR;
pop_set_redraw(tp, POP_RD_FLOOR, 2);
if ((tp % 10) < 9)
pop_set_redraw((uint8_t)(tp + 1), POP_RD_FLOOR, 2);
}
return 1;
}
/* ---- Диспетчер ----------------------------------------------------- */
static void check_on_floor(void)
{
if (kid_cur_flags() & FRAME_NEEDS_FLOOR) {
if (get_tile_at_char() == TILE_WALL)
in_wall(); /* забежал в стену -> выровнять */
if (!tile_is_floor(g_curr_tile))
if (!tile_is_floor(g_curr_tile) && !floors_appear())
start_fall();
}
}
@@ -1466,6 +1529,40 @@ static void bumped_floor(void)
is_guard_notice = 1; /* bumped_sound (seg004:05F1) */
}
/* bump_into_opponent (seg003:08AA). Зовётся из play_kid_frame (seg000:1238)
* между set_char_collision и check_collisions, и ТОЛЬКО у Кида — у стража
* этой ветки нет (см. комментарий к guard_phys ниже).
*
* ЗАЧЕМ. Это единственная защита оригинала от «вошёл в комнату — сразу
* труп». Стража при входе никуда не отбрасывает: enter_guard (seg002:0112)
* ставит его ровно в его тайл из данных уровня, и Кид вполне может войти
* вплотную. Спасает то, что БЕЗОРУЖНЫЙ Кид, столкнувшись с вооружённым
* соперником лицом к лицу, не получает укол, а ОТСКАКИВАЕТ (seq_47).
* Остальные три страховки у нас уже есть: Кид сам достаёт меч
* (control_standing, pop_ctrl.c), страж поднимается в стойке покоя с мечом
* в ножнах (pop_guard_cold.c) и не колет ближе 12 (guards.c).
*
* Расхождений с оригиналом нет. Опциональные фиксы SDLPoP
* (fix_painless_fall_on_guard — падение на стража с высоты перестаёт быть
* безболезненным; fix_jumping_over_guard — нельзя перепрыгнуть) намеренно НЕ
* портированы: базовое поведение движка 1989 года. */
static void bump_into_opponent(void)
{
int16_t distance;
if (can_guard_see_kid < 2) return;
if (Char.sword != 0) return; /* Кид уже в боевой стойке */
if (Opp.sword == 0) return; /* у соперника меч в ножнах */
if (Opp.action >= 2) return; /* только stand / run_jump */
if (Char.direction == Opp.direction) return; /* должны быть лицом к лицу */
distance = pop_char_opp_dist();
if (distance < 0) distance = -distance;
if (distance > 15) return;
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
Char.fall_y = 0;
pop_char_set_seq(SEQ_47_BUMP);
play_seq();
}
/* ---- check_collisions (seg004:0004) -------------------------------- *
* ПОЛНЫЙ порт: флаги перекрытия считаются для ВСЕХ колонок ряда, а колонка
* удара выбирается обходом 9→0 по переходу флага 0→1.
@@ -2112,6 +2209,12 @@ static void check_press(void)
static void do_knock(int tile_row)
{
int col, pos;
/* СПЕЦКЕЙС УРОВНЯ 13 (loose_make_shake, seg007:949): сотрясение НЕ
* трясёт плиты вовсе. Причина та же, что у первого спецкейса: тряска
* ставит фазу 0x80, а на этом уровне у плит уже лежит СВОЙ отложенный
* старт (0xF0..0xFF), и приземление Кида сбросило бы его на 0x80 —
* гряда посыпалась бы разом вместо вразнобой. */
if (pop_current_level == POP_LOOSE_TILES_LEVEL) return;
if (tile_row == -1) { /* ряд 2 комнаты сверху — плиты-потолки */
if (!g_above || !g_link_u) return;
for (col = 0; col < 10; col++)
@@ -2139,18 +2242,120 @@ void pop_check_knock(void) __banked
}
}
/* Сдать НЕЗАКОНЧЕННЫЕ loose уходящей комнаты в персистентное хранилище —
* звать ПЕРЕД сменой комнаты, пока g_room/g_fg/g_link_u ещё старые.
*
* pop_loose_modif[30] — фаза тайлов ТЕКУЩЕЙ комнаты, и раньше смена комнаты
* её просто теряла: плита, по которой Кид пробежал и ушёл вправо, оставалась
* целой (уровень 10, комната 2, тайл 2,9). В оригинале терять нечего —
* там loose это trob в общем списке, а фаза лежит в curr_room_modif, то есть
* в самой таблице уровня. Порт того же: фаза уезжает в room_modif (он
* персистентен per-room), а досчитывает её animate_loose в pop_trob.c.
*
* Пишем фазу ВСЕМ loose-тайлам, включая нулевые: room_modif — источник
* истины, а pop_loose_modif лишь его копия на время пребывания в комнате,
* и оставить там прошлое значение значило бы «оживить» уже отдрожавшую
* плиту при следующем входе. trob заводим только на незаконченные. */
void pop_loose_leave_room(void) __banked
{
uint8_t *mod;
int i;
if (!g_fg || !g_room) return;
mod = pop_trob_modif(g_room);
if (mod) {
for (i = 0; i < 30; i++) {
if ((g_fg[i] & 0x1F) != TILE_LOOSE) continue;
mod[i] = pop_loose_modif[i];
if (pop_loose_modif[i]) pop_add_trob(g_room, (uint8_t)i, 0);
}
}
/* Плиты-ПОТОЛКИ — ряд 2 комнаты сверху, tilepos 20+col. */
if (g_above && g_link_u) {
mod = pop_trob_modif(g_link_u);
if (mod) {
for (i = 0; i < 10; i++) {
if ((g_above[i] & 0x1F) != TILE_LOOSE) continue;
mod[20 + i] = pop_ceil_modif[i];
if (pop_ceil_modif[i])
pop_add_trob(g_link_u, (uint8_t)(20 + i), 0);
}
}
}
}
/* Решётка ворот должна идти ПОВЕРХ персонажа? Порт первого условия
* draw_tile_fore (seg008:0D15):
*
* if (tile_left == tiles_4_gate && Kid.curr_row == drawn_row &&
* Kid.curr_col == drawn_col - 1 && Kid.room != room_R)
* draw_gate_fore();
*
* То есть: рисуя тайл (row, col), оригинал кладёт бары ворот в FOREtable —
* поверх всего, что уже нарисовано, — если тайл СЛЕВА это ворота и персонаж
* стоит ИМЕННО НА НИХ. Графика ворот принадлежит правому краю своего тайла
* и рисуется в проходе СЛЕДУЮЩЕЙ колонки, отсюда сдвиг на единицу.
*
* Живёт в карте, а не в слое фона, по двум причинам: спрашивает она про
* ТАЙЛ и позицию персонажа (это знание карты), и её линкует хост-харнесс —
* решение проверяется тестом без эмулятора (t_char: gate_fore_over_char).
* Слою фона остаётся только рисование (draw_gate_fore в pop_bg.c).
*
* РАСХОЖДЕНИЕ С ОРИГИНАЛОМ (осознанное, см. docs/impl_diff.md): оригинал
* жёстко спрашивает про Kid, потому что foretable у него ОДНА на проход
* тайлов; у нас fore-проход идёт по персонажу, поэтому спрашиваем про того,
* кого рисуем. Наш вариант — надмножество: страж, стоящий в проёме ворот,
* у нас тоже уходит ЗА решётку, а в оригинале остался бы перед ней. */
uint8_t pop_gate_over_char(const pop_char_t *ch) __banked
{
if (!ch || ch->room != g_room) return 0;
if (ch->curr_row < 0 || ch->curr_row > 2) return 0;
/* Колонка 9: бары легли бы в колонку 10, то есть в СОСЕДНЮЮ комнату —
* у оригинала это отсекает `Kid.room != room_R`. */
if (ch->curr_col < 0 || ch->curr_col > 8) return 0;
return (uint8_t)(get_tile(ch->curr_col, ch->curr_row) == TILE_GATE);
}
/* СТАРТ/РЕСТАРТ уровня: забыть фазы, НЕ сдавая их в room_modif — в
* оригинале всё состояние сносит сам load_level (seg003:57). Без этого
* pop_loose_leave_room у первого же enter_room занесла бы в только что
* восстановленный уровень дрожь из прошлой жизни: pop_trob_reset уже
* отработал, room_modif свежий, а pop_loose_modif — ещё старый. */
void pop_loose_forget(void) __banked
{
int i;
for (i = 0; i < 30; i++) pop_loose_modif[i] = 0;
for (i = 0; i < 10; i++) pop_ceil_modif[i] = 0;
}
/* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest
* индексируются позицией тайла и не должны утечь из старой комнаты в новую.
* Уже ПАДАЮЩИЕ куски при этом не трогаем — они привязаны к своей комнате и
* должны долететь (do_mobs, seg007:1063; см. pop_loose_mob_room_changed). */
* должны долететь (do_mobs, seg007:1063; см. pop_loose_mob_room_changed).
*
* Вместо нулей забираем фазу НОВОЙ комнаты из room_modif — обратная
* половина pop_loose_leave_room: расшатанная и брошенная плита, к которой
* Кид вернулся, продолжает дрожать с той же фазы, а не с покоя. trob,
* который крутил её заочно, снимет сам animate_loose (room == cur_room). */
void pop_loose_reset(void) __banked
{
uint8_t *mod;
int i;
mod = g_room ? pop_trob_modif(g_room) : 0;
for (i = 0; i < 30; i++) {
pop_loose_modif[i] = 0;
pop_loose_modif[i] = (mod && g_fg && (g_fg[i] & 0x1F) == TILE_LOOSE)
? mod[i] : 0;
}
mod = (g_above && g_link_u) ? pop_trob_modif(g_link_u) : 0;
for (i = 0; i < 10; i++) {
pop_ceil_modif[i] = 0;
pop_ceil_modif[i] = (mod && (g_above[i] & 0x1F) == TILE_LOOSE)
? mod[20 + i] : 0;
/* ...и СНЯТЬ заочный trob: плиту-потолок отсюда снова ведёт
* pop_loose_tick. Без этого её крутили бы ДВОЕ — trob по
* room_modif и мы по pop_ceil_modif, — и она проваливалась бы
* дважды (animate_loose снимает сам только trob'ы cur_room, а
* комната сверху — не cur_room). */
if (pop_ceil_modif[i]) pop_add_trob(g_link_u, (uint8_t)(20 + i), -1);
}
pop_ceil_fell = 0;
pop_debris_at = 0;
@@ -2158,6 +2363,82 @@ void pop_loose_reset(void) __banked
pop_loose_mob_room_changed();
}
/* remove_loose (seg007:0EB8) для ОДНОГО тайла, наружу — ради честного
* loose_fall: падающий кусок сбивает плиту НЕМЕДЛЕННО, в том же кадре
* (pop_room.c, mob_break_loose). Здесь, а не в pop_room, потому что тут
* живут и рабочая копия тайлов комнаты (g_fg), и фазы плит.
*
* Перерисовываем ДВЕ клетки — саму и соседа справа. Передний торец пола
* заезжает в соседний тайл (правая грань соседа всегда в backtable), и без
* второй пометки от исчезнувшей плиты оставался висеть торец: тайл пустой,
* а его передняя грань нарисована. Тем же приёмом, что pop_skel_wake_tile
* и что set_redraw_full+set_wipe на curr_tilepos и curr_tilepos+1 в
* оригинале. */
void pop_loose_remove(uint8_t room, uint8_t tilepos) __banked
{
uint8_t *mod = pop_trob_modif(room);
if (tilepos >= 30) return;
if (room == g_room) {
g_fg[tilepos] = TILE_EMPTY;
pop_loose_modif[tilepos] = 0;
pop_set_redraw(tilepos, POP_RD_LOOSE_GONE, 2);
if ((tilepos % 10) < 9)
pop_set_redraw((uint8_t)(tilepos + 1), POP_RD_FLOOR, 2);
} else {
/* Комната не на экране, но её кромочная колонка видна в шве — те же
* снимки, что обновляет animate_loose. */
pop_neigh_dirty = 1;
}
/* Модификатор пустой клетки = ТИП УРОВНЯ (по нему draw_tile_right
* выбирает blueline у дыры), тайл в странице уровня — сразу. */
if (mod) mod[tilepos] = pop_palace;
pop_level_set_tile(room, tilepos, TILE_EMPTY);
}
/* check_fall_flo (seg000:1317) — СПЕЦСОБЫТИЕ уровня 13: гряда плит над
* комнатой сыплется на Кида сама, вразнобой.
*
* Как это устроено в оригинале: при входе в комнату 23 или 16 шести плитам
* ряда 2 комнаты СВЕРХУ раздаётся ОТРИЦАТЕЛЬНЫЙ модификатор −(rnd & 15).
* Отрицательное значение — это старший бит, то есть для animate_loose оно
* выглядит как «тряска», а на уровне 13 тряска намеренно не гасится
* (спецкейс в pop_loose_tick). Фаза считает вверх, доходит до нуля и
* дальше идёт обычным отсчётом до провала — получается разная задержка у
* каждой плиты.
*
* Плиты ряда 2 верхней комнаты у нас — это pop_ceil_modif (плиты-потолки),
* так что раздаём фазу именно туда.
*
* ОДНО отличие от оригинала, вынужденное представлением: у нас фаза 0 = «не
* анимируется», плита с такой фазой в цикле пропускается вовсе. Значит
* счёт, дойдя до нуля, оборвался бы — поэтому, во-первых, тик перескакивает
* ноль (см. pop_loose_tick), а во-вторых, стартовое значение берётся на
* единицу «отрицательнее» (−n−1): вместе это даёт ровно ту же суммарную
* задержку n + 11 кадров, что и у оригинала.
*
* Звать при входе в комнату, после pop_loose_reset (тот забирает фазы из
* room_modif) — как check_the_end зовёт его после start_chompers. */
static pop_rnd_t loose_seed; /* свой домен последовательности, см. pop_geom.h */
void pop_check_fall_flo(void) __banked
{
uint8_t col;
if (pop_current_level != POP_LOOSE_TILES_LEVEL) return;
if (g_room != POP_LOOSE_TILES_ROOM1 && g_room != POP_LOOSE_TILES_ROOM2) return;
if (!g_above || !g_link_u) return;
for (col = POP_LOOSE_TILES_FIRST - 20; col <= POP_LOOSE_TILES_LAST - 20; col++) {
uint8_t t = g_above[col];
/* Те же отсевы, что в make_loose_fall: не loose и «solid» loose
* пропускаются молча (в комнате 1 тайл 24 пустой — диапазон 22..27
* намеренно надмножество), уже идущий отсчёт не перебиваем. */
if ((t & 0x1F) != TILE_LOOSE || (t & 0x20)) continue;
if ((int8_t)pop_ceil_modif[col] > 0) continue;
pop_ceil_modif[col] =
(uint8_t)(-(int8_t)(pop_prandom(&loose_seed, 255) & 0x0F) - 1);
pop_set_redraw_above(col, POP_RDA_CEIL, 1);
}
}
/* fell_on_your_head (seg007:1205): плита прилетела Киду по голове. В беге
* (кадры 5..14) не бьёт (кроме уровня 13 — его тут нет); в висе/подъёме тоже
* (action >= hang_climb, кроме turn). Иначе −1 HP: смерть → seq_22 (crushed),
@@ -2169,7 +2450,10 @@ void pop_loose_reset(void) __banked
static void fell_on_your_head(void)
{
uint8_t frame = Char.frame, action = Char.action;
if ((frame < 5 || frame >= 15) &&
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:1218): там плита бьёт и В БЕГЕ. На прочих
* уровнях бегущего она пропускает — иначе гряда падающих плит, сквозь
* которую и надо пробегать, не представляла бы никакой угрозы. */
if ((pop_current_level == POP_LOOSE_TILES_LEVEL || frame < 5 || frame >= 15) &&
(action < ACT_HANGCLIMB || action == ACT_TURN)) {
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
if (pop_take_hp(1)) { /* «брызги» даст pop_do_delta_hp */
@@ -2221,8 +2505,22 @@ void pop_loose_tick(void) __banked
for (pos = 0; pos < 30; pos++) {
if (pop_loose_modif[pos] != 0) {
uint8_t m = ++pop_loose_modif[pos];
/* Отложенный старт уровня 13 досчитал до нуля. Ноль у нас
* означает «не анимируется», и плита застряла бы навсегда —
* перескакиваем на 1. Стартовое значение выбрано с учётом
* этого перескока (см. pop_check_fall_flo). */
if (m == 0) m = pop_loose_modif[pos] = 1;
if (m & 0x80) { /* тряска (do_knock) */
if (m >= 0x84) { /* конец тряски: сброс + покой */
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:823): фазу со старшим битом НЕ
* гасим и не трясём. На нём check_fall_flo раздаёт плитам
* ОТРИЦАТЕЛЬНЫЙ старт (0xF0..0xFF) как отложенный таймер, и
* обычная ветка «тряска кончилась на 0x84» убила бы его в
* первом же кадре — плита не упала бы никогда. Пропущенная
* инкрементом фаза сама дойдёт до 0x00, там старший бит
* снимется, и дальше пойдёт обычный отсчёт до провала. */
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
/* ждём молча */
} else if (m >= 0x84) { /* конец тряски: сброс + покой */
pop_loose_modif[pos] = 0;
/* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
* правая грань loose живёт в соседе). ОБЕ страницы —
@@ -2231,6 +2529,13 @@ void pop_loose_tick(void) __banked
} else {
pop_set_redraw(pos, POP_RD_LOOSE, 1); /* дрожащий кадр */
}
} else if (m >= 11 && pop_loose_fell) {
/* Сигнал прошлого провала ещё не разобран главным циклом (он
* ОДИН на кадр) — придержим плиту кадр, откатив фазу к 10.
* Иначе из двух плит, провалившихся в одном кадре, дыру
* запекала бы только вторая, а первая оставалась нарисованной
* целой (уровень 13: check_fall_flo роняет гряду разом). */
pop_loose_modif[pos] = 10;
} else if (m >= 11) { /* loose_floor_delay — падение */
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
* Debris появляется ТОЛЬКО там, где приземлился падающий
@@ -2244,6 +2549,11 @@ void pop_loose_tick(void) __banked
pop_trob_modif(g_room)[pos] = pop_palace;
pop_loose_modif[pos] = 0;
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
/* ...и СОСЕДА СПРАВА: передний торец пола заезжает в его
* клетку, и без этой пометки он оставался висеть над пустым
* тайлом. То же, что делает pop_skel_wake_tile. */
if ((pos % 10) < 9)
pop_set_redraw((uint8_t)(pos + 1), POP_RD_FLOOR, 2);
pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению */
} else { /* отсчёт 1..10 — дрожащий кадр */
@@ -2258,10 +2568,19 @@ void pop_loose_tick(void) __banked
for (col = 0; col < 10; col++) {
if (pop_ceil_modif[col] != 0) {
uint8_t m = ++pop_ceil_modif[col];
if (m == 0) m = pop_ceil_modif[col] = 1; /* см. pop_loose_tick выше */
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
if (m >= 0x84) { pop_ceil_modif[col] = 0;
/* Тот же спецкейс уровня 13, и здесь он ГЛАВНЫЙ: плиты,
* которым check_fall_flo раздал отложенный старт, лежат
* именно в ряду 2 комнаты СВЕРХУ, то есть ведёт их этот
* цикл, а не соседний. */
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
/* ждём молча */
} else if (m >= 0x84) { pop_ceil_modif[col] = 0;
pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
} else if (m >= 11 && pop_ceil_fell) {
pop_ceil_modif[col] = 10; /* сигнал занят — кадр подождём */
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
pop_ceil_modif[col] = 0;
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
@@ -2629,6 +2948,7 @@ static void kid_phys(void)
fall_accel();
fall_speed();
determine_col();
bump_into_opponent(); /* seg003: безоружный Кид отскакивает от стража */
check_collisions(); /* seg004: флаги перекрытия по колонкам ряда */
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
check_action();