Уровни 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
+53
View File
@@ -14,6 +14,7 @@
* пометки, поэтому pop_process_trobs — единственная функция модуля, которую
* нельзя звать из __banked-кода. */
#include "pop_bg.h"
#include "pop_tile.h" /* pop_palace — тип уровня как модификатор дыры */
#include "pop_guard.h" /* is_guard_notice — нажатая кнопка слышна стражу */
#include "pop_kid.h" /* Kid.curr_row/alive — условие остановки чомпера */
#include "pop_tune.h" /* POP_DBG_GATE_HOLD — отладочное растягивание окна */
@@ -31,6 +32,7 @@
#define TILE_CHOMP 0x12 /* чомпер (челюсти) */
#define TILE_TORCH 0x13
#define TILE_TORCH_DEBRIS 0x1E
#define TILE_EMPTY 0x00 /* remove_loose: на месте провалившейся плиты */
/* ---- Персистентное per-room modifier-состояние -------------------- */
static uint8_t room_modif[POP_NROOMS][POP_ROOMTILES];
@@ -305,6 +307,54 @@ static void animate_spike(uint8_t *m, int8_t *type)
*m = mod;
}
/* animate_loose (seg007:0CBA) — для плиты в НЕ отрисованной комнате.
*
* ЗАЧЕМ. В оригинале loose-пол — обычный trob, а do_trobs крутит весь
* список независимо от drawn_room: расшатанная плита доваливается, даже если
* Кид уже убежал в соседнюю комнату. У нас фаза живёт в pop_loose_modif[30]
* — массиве ТЕКУЩЕЙ комнаты, — и смена комнаты её обнуляла: плита,
* пробежав по которой Кид уходил вправо, оставалась целой (уровень 10,
* комната 2, тайл 2,9).
*
* Разделение обязанностей: пока комната отрисована, фазой владеет
* pop_loose_tick (ему нужны перерисовка, звук, mob-порядок), и trob здесь
* сразу снимается. На выходе из комнаты pop_loose_leave_room (pop_map.c)
* сбрасывает фазу в ПЕРСИСТЕНТНЫЙ room_modif и заводит trob — дальше плита
* досчитывает здесь, а на входе обратно pop_loose_reset забирает фазу
* назад. Персистентный модификатор и есть curr_room_modif оригинала, так
* что расхождения с SDLPoP нет — только двухуровневое хранение.
*
* Рисовать тут нечего (комната не на экране), поэтому от оригинала остаётся
* ровно счёт фазы и провал: тайл в EMPTY + отрыв падающего куска. */
static void animate_loose(uint8_t *m, int8_t *type, uint8_t room, uint8_t tp,
uint8_t cur_room)
{
uint8_t ph;
if (room == cur_room) { *type = -1; return; } /* хозяин — pop_loose_tick */
ph = (uint8_t)(*m + 1);
if (ph == 0) ph = 1; /* перескок нуля, см. pop_loose_tick */
if (ph & 0x80) { /* тряска (do_knock): 0x80..0x83 */
/* Спецкейс уровня 13 (seg007:823) — тот же, что в pop_loose_tick:
* отложенный старт от check_fall_flo живёт в старшем бите, и гасить
* его нельзя. Здесь он тоже нужен: гряда лежит в комнате СВЕРХУ, и
* пока Кид не вошёл в комнату под ней, счёт идёт заочно. */
if (pop_current_level == POP_LOOSE_TILES_LEVEL) { /* ждём молча */ }
else if (ph >= 0x84) { ph = 0; *type = -1; }
} else if (ph >= 11) { /* loose_floor_delay — провал */
/* remove_loose (seg007:0EB8): тайл → EMPTY, модификатором пустой
* клетки становится ТИП УРОВНЯ (по нему draw_tile_right выбирает
* blueline у дыры). Тот же порядок, что в pop_loose_tick. */
pop_level_set_tile(room, tp, TILE_EMPTY);
/* Комната не на экране, но её колонка 9 ВИДНА в шве текущей и лежит
* в кромочных снимках — попросить главный цикл их перечитать. */
pop_neigh_dirty = 1;
pop_loose_mob_spawn_at(room, TP_ROW[tp], TP_COL[tp]);
ph = pop_palace;
*type = -1;
}
*m = ph;
}
/* ---- Кнопки/ворота (seg007) --------------------------------------- *
* Таблицы связей LINKLOC/LINKMAP — в pop_level (мутабельная копия в W2).
* doorlink1 (d1) = LINKLOC, doorlink2 (d2) = LINKMAP. */
@@ -574,6 +624,9 @@ void pop_process_trobs(uint8_t cur_room) __banked
case TILE_GATE:
animate_door(&mod[tp], &type);
break;
case TILE_LOOSE:
animate_loose(&mod[tp], &type, room, tp, cur_room);
break;
case TILE_LEVELDOOR:
animate_leveldoor(&mod[tp], &type, cur_room);
break;