Уровни 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
+56
View File
@@ -355,6 +355,8 @@ int main(void)
}
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
pop_check_mouse(); /* спецсобытие ур.8: мышь жмёт кнопку */
pop_check_killed_shadow(); /* спецсобытие ур.12: смерть тени = смерть Кида */
pop_frame_timers(); /* timers (seg003:0735): вспышка слияния */
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
pop_ctrl_tick(); /* ввод -> control(): смена seq */
/* Кто в этом кадре не изменился с прошлой отрисовки ЭТОЙ страницы —
@@ -396,6 +398,7 @@ int main(void)
pop_next_level = (uint8_t)(pop_current_level + 1);
}
else if (down) { /* L2: переход в комнату снизу — продолжает падать */
pop_nav_hold = 0; /* сменил комнату НОГАМИ — чит забыт */
enter_room(down); /* координаты Kid уже репроецированы pop_map */
gfx_set_draw_page(back); /* вернуть текущую back для отрисовки */
} else { /* выпал из уровня (нет комнаты снизу) — рестарт */
@@ -424,9 +427,20 @@ int main(void)
pop_checkpoint = 1;
hitp_beg_lev = hitp_max;
}
/* Соседи чекпойнта по leave_room (seg002:0490): в ветке ухода
* ВПРАВО (pop_leave_dir == 2) — sword_disappears + meet_Jaffar,
* в ветке ВЛЕВО — Jaffar_exit. Комната во всех трёх ещё СТАРАЯ,
* как и в оригинале (goto_other_room идёт после). */
if (pop_leave_dir_prev == 2) {
pop_sword_disappears();
pop_meet_jaffar();
} else if (left) {
pop_jaffar_exit();
}
if (nb) {
/* side для pop_guard_follow — то же направление, по которому
* ищем соседа (roomleave_result оригинала). */
pop_nav_hold = 0; /* сменил комнату НОГАМИ — чит забыт */
enter_room_side(nb, (uint8_t)(pop_leave_dir_prev - 1));
gfx_set_draw_page(back);
}
@@ -438,6 +452,30 @@ int main(void)
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */
/* process_trobs — рисования внутри нет) */
if (pop_neigh_dirty) {
/* Тайл СОСЕДА изменился заочно (провалилась брошенная плита —
* animate_loose). Обе кромочные копии — снимки на момент входа
* в комнату, обновляем обе: g_lcol/g_rcol (коллизия у шва) и
* lcol_fg (отрисовка: колонка 9 левого соседа видна в col0
* через draw_tile_anim_right, pop_room.c). Без второй шов
* продолжал рисовать плиту, которой уже нет — LOOSE-ROOM-CHANGE.
* Событие редкое, поэтому по сигналу, а не опросом каждый кадр. */
uint8_t lft = pop_room_link(cur_room, 0);
pop_neigh_dirty = 0;
pop_map_refresh_edges();
if (lft) {
uint8_t i;
pop_level_access_begin();
for (i = 0; i < 3; i++) {
uint8_t c = pop_level_tile_raw(lft, (uint8_t)(9 + i * 10));
if (c == (uint8_t)(lcol_fg[i] & 0x1F)) continue;
lcol_fg[i] = c;
seam_rows |= (uint8_t)(1 << i);
seam_redraw = 2; /* обе страницы дабл-буфера */
}
pop_level_access_end();
}
}
pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */
/* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */
{ /* ворота соседа слева (room8) видны в col0 — */
@@ -471,6 +509,18 @@ int main(void)
}
}
}
/* Бесшовный выход уровня 12 (play_level_2, seg000:900): двери уровня
* у него нет — уровень кончается ФАКТОМ попадания в комнату 23.
* Условие «уровень ещё не сменён» обязательно: комната 23 есть и у
* 13-го, причём она там СТАРТОВАЯ, и без гарда переход зациклился бы
* (12 → 13 → 14 в первом же кадре нового уровня). */
if (pop_current_level == POP_SEAMLESS_LEVEL &&
pop_next_level == pop_current_level &&
!pop_nav_hold && /* не по чит-телепорту, см. pop_state.c */
kid_room == POP_SEAMLESS_ROOM) {
pop_next_level = (uint8_t)(pop_current_level + 1);
pop_seamless = 1;
}
if (pop_next_level != pop_current_level) {
/* Смена уровня целиком — в банке (pop_level_switch): грузится
* файл, спрайты соперника и тайлсет. Раз на уровень. */
@@ -550,6 +600,7 @@ int main(void)
* HP — красный, перо (медленное падение) — зелёный. */
if (pop_flash_color == POP_FLASH_RED) flash_bg(255, 0, 0);
else if (pop_flash_color == POP_FLASH_GREEN) flash_bg(85, 255, 85);
else if (pop_flash_color == POP_FLASH_WHITE) flash_bg(255, 255, 255);
else flash_bg(255, 255, 85);
pop_flash_time--;
if (!pop_flash_time) pop_flash_color = POP_FLASH_YELLOW;
@@ -567,6 +618,11 @@ int main(void)
* пролетел сквозь зеркало, тень должна родиться ДО того, как слот
* соперника пойдёт рисоваться, иначе кадр её проглотит. */
pop_check_mirror();
/* Падающие плиты, идущие ПОД Kid. Здесь, а не внутри pop_loose_tick:
* фон кадра (перепечатки тайлов, шов) обязан быть готов ДО любого
* спрайта, иначе перепечатка ложится поверх уже нарисованного куска —
* так окно задней стены и передняя грань пола оказывались на плите. */
pop_loose_mob_draw();
{ /* Кто позже — тот поверх; порядок задаёт обход тайлов, а не роль
* персонажа (см. guard_over_kid). Отрисовка одна на всех Char
* (pop_cdraw.c), различает их только слот. */