Files
Sprinter-SDCC/applications/PoP/roomtest/pop_map.h
T
snark13 67d62e3e8b Уровни 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>
2026-08-13 18:21:52 +03:00

280 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_map.h — коллизия Kid с картой комнаты (Фаза K3): тайловые запросы
* + физика падения + приземление. Порт SDLPoP seg005/seg006
* (check_on_floor/start_fall/do_fall/fall_accel/fall_speed/land/in_wall).
*
* K3.2 (падение): Kid, сойдя/сбежав с края пола (тайл под ним не floor),
* начинает падать (fall_y-ускорение, Char.y растёт), приземляется на пол
* ниже (soft/medium land) или продолжает вниз. Стена под персонажем —
* in_wall (выталкивание). Бег В стену (check_bumped) — отдельным шагом.
*/
#ifndef POP_MAP_H
#define POP_MAP_H
#include <stdint.h>
#include "pop_char.h" /* pop_char_t — окклюзия ворот спрашивает про персонажа */
/* Задать карту текущей комнаты: fg[30] = коды тайлов (row*10+col, 3×10).
* col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив
* МУТАБЕЛЬНЫЙ (loose-пол при падении → empty) — передавать ту же копию,
* что и в pop_room_draw, чтобы отрисовка видела изменения. */
void pop_map_set(uint8_t *fg) __banked;
/* Связи комнаты (0=нет соседа; иначе номер). Заодно кэширует fg соседей
* слева и справа ЦЕЛИКОМ (читает уровень сам). Оригинал добирается до любой
* колонки через find_room_of_tile, у нас уровень в EMM-странице и маппить её
* на каждый get_tile (горячий путь физики) дорого — поэтому копия соседей.
* Колонка персонажа считается из x и на диапазоне x 0..255 лежит в 5..13,
* то есть заведомо выходит за комнату; кромочных колонок не хватало, и
* get_tile отдавал мнимую стену (BUG-SWORD-GHOST-1). Звать в enter_room. */
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down) __banked;
/* Перечитать fg соседей (кромочные колонки коллизии) из уровня, не сбрасывая
* историю перекрытий. Нужен, когда тайл соседа изменился, пока тот не
* отрисован — см. pop_neigh_dirty в pop_state.h. */
void pop_map_refresh_edges(void) __banked;
/* БОКОВОЙ переход в соседнюю комнату: перенумеровать историю флагов
* перекрытия вместо того, чтобы её выбросить (иначе Кид с разбега проходит
* сквозь закрытые ворота шва — BUG-GATE-PASS-1). dcol — на сколько
* изменились номера колонок: ушли ВПРАВО −10, ВЛЕВО +10. Звать СРАЗУ после
* pop_map_set_edges и только для боковых переходов; вверх/вниз и все прочие
* входы в комнату остаются на полной инвалидации. Разбор — BUGS_CLOSED.md,
* расхождение с дословным портом — ../docs/impl_diff.md. */
void pop_coll_shift(int8_t dcol) __banked;
/* check_skel: тайл скелета -> пол + пометка перерисовки (обе страницы). */
void pop_skel_wake_tile(uint8_t tilepos) __banked;
/* Openness ворот в (col,row) с резолвом за край комнаты (соседи слева/справа).
* 0xFF — тайла нет. Нужен лучу видимости стража (guards.c). */
uint8_t pop_gate_modif(int8_t col, int8_t row) __banked;
/* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob).
* Звать в enter_room. */
void pop_map_set_room(uint8_t room) __banked;
/* Тайл-код текущей комнаты по клетке (col 0..9, row 0..2) — порт
* get_tile(room,col,row) для текущей комнаты. Нужен лучу видимости стража
* (guards.c): он идёт по ряду между Кидом и стражем. */
uint8_t pop_tile_at(int8_t col, int8_t row) __banked;
/* determine_col (seg006:014D): Kid.curr_col = m7(dx_weight()). Наружу — для
* pop_load_fram_det_col (pop_kid.c), порт load_fram_det_col. */
void pop_determine_col(void) __banked;
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
* респавн). */
extern uint8_t pop_kid_dead;
extern uint8_t hitp_curr;
extern uint8_t hitp_max; /* стартовое HP (для индикатора) */
extern int8_t hitp_delta; /* урон за кадр от меча; применяет pop_do_delta_hp */
/* check_fall_flo (seg000:1317) — спецсобытие уровня 13: вход в комнату 23
* или 16 роняет на Кида гряду плит из комнаты СВЕРХУ, каждую со своей
* задержкой. Звать при входе в комнату ПОСЛЕ pop_loose_reset (порядок
* check_the_end: start_chompers → check_fall_flo → check_shadow). */
void pop_check_fall_flo(void) __banked;
void pop_kid_hp_reset(void) __banked;
/* То же для БЕСШОВНОГО старта уровня (pop_seamless): HP не трогаем вовсе. */
void pop_kid_hp_carry(void) __banked;
/* hitp_beg_lev (seg003): HP, с которым НАЧАТ текущий уровень. Рестарт
* уровня (смерть/выпадение) откатывает HP ровно к нему; пройденный уровень
* подтягивает его к hitp_max (play_level_2, seg003:410) — так «жизнь» из
* большой склянки переезжает на следующий уровень. Правит главный цикл. */
extern uint8_t hitp_beg_lev;
#define POP_START_HITP 3 /* custom->start_hitp */
#define POP_MAX_HITP 10 /* custom->max_hitp_allowed */
void pop_kid_hp_newgame(void) __banked; /* начало ИГРЫ: hitp_beg_lev = 3 */
/* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object).
* pop_have_sword — «меч у Кида» (порт have_sword). pop_item_taken — 0 или
* tilepos+1 только что поднятого предмета: вызывающий обязан запечь тайл
* (он стал полом) на ОБЕИХ страницах и записать override комнаты. */
extern uint8_t pop_have_sword;
extern uint8_t pop_item_taken;
/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока
* НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */
extern uint8_t pop_flash_time;
/* Цвет вспышки (порт flash_color, seg006 proc_get_object / seg000 feather_fall):
* жёлтая — подобран меч, красная — зелья HP, зелёная — перо. */
#define POP_FLASH_YELLOW 0
#define POP_FLASH_RED 1
#define POP_FLASH_GREEN 2
#define POP_FLASH_WHITE 3 /* тень: слияние и её смерть (уровень 12) */
extern uint8_t pop_flash_color;
/* add_life (seg000:11EC): +1 к ПОТОЛКУ HP и долив до него. Наружу — ради
* слияния с тенью (уровень 12, guards.c в другом банке): большая склянка
* зовёт ту же ветку изнутри модуля. */
void pop_add_life(void) __banked;
/* is_feather_fall: кадров, пока действует зелье медленного падения. Читают
* play_seq (опкод JMP_IF_FEATHER — ветки stepfloat/bumpfloat в seqtbl) и
* приложение (сброс на старте уровня, seg003:189). */
extern uint8_t pop_feather;
/* upside_down (seg000:15E9): 1 — картинка перевёрнута вверх ногами (зелье
* типа 4 на уровне 9). Читают все слои отрисовки; сбрасывается стартом
* уровня и смертью Кида (seg000:1224).
* pop_upside_dirty — порт need_redraw_because_flipped: флаг «состояние
* переворота ИЗМЕНИЛОСЬ», по нему главный цикл один раз переворачивает уже
* нарисованное (pop_flip_screen) и снимает флаг. */
extern uint8_t pop_upside; /* ТЕКУЩЕЕ — его читают все слои отрисовки */
extern uint8_t pop_upside_want; /* ЖЕЛАЕМОЕ — сюда пишут зелье и смерть */
extern uint8_t pop_upside_dirty;
uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч / 3 пить */
void pop_proc_get_object(void) __banked; /* эффект — из play_seq (опкод GET_ITEM) */
/* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для
* pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189).
* Приложение переключает комнату по соответствующей связи и снимает флаг. */
extern uint8_t pop_leave_dir;
/* exit_room_timer (порт seg002 exit_room): пока >0, боковой/нижний leave
* заблокирован (декремент в pop_phys_tick). Приложение ставит =2 при входе
* в комнату (enter_room) — 2 кадра защиты от повторного/транзиентного leave
* (ping-pong у шва, транзиент отскока о ворота, баг #4/BUG-2). */
extern uint8_t pop_leave_timer;
/* Loose floors (проваливающиеся полы). pop_loose_modif[30] — состояние на
* тайл (0 покой / 0x80.. тряска / 1..11 отсчёт падения); читает pop_bg при
* отрисовке. pop_loose_tick() — анимация + перерисовка изменившихся тайлов
* на ТЕКУЩЕЙ странице; звать в основном цикле ПОСЛЕ set_draw_page(back),
* рядом с kid-отрисовкой. */
extern uint8_t pop_loose_modif[30];
void pop_loose_tick(void) __banked;
void pop_loose_reset(void) __banked; /* сброс loose-состояния при смене комнаты */
/* Отдать НЕЗАКОНЧЕННЫЕ loose уходящей комнаты в персистентный room_modif +
* список trob, чтобы они досчитались заочно (в оригинале loose и есть trob,
* а do_trobs крутит список независимо от drawn_room). Звать ПЕРЕД любой
* перезагрузкой комнаты, пока карта ещё описывает СТАРУЮ. */
void pop_loose_leave_room(void) __banked;
/* СТАРТ/РЕСТАРТ уровня: забыть фазы БЕЗ сдачи (load_level сносит всё сам).
* Звать рядом с pop_trob_reset, до первого enter_room. */
void pop_loose_forget(void) __banked;
/* Персонаж стоит НА тайле ворот, значит их решётку надо вернуть ПОВЕРХ него
* (порт условия draw_tile_fore, seg008:0D15 — разбор в pop_map.c). Бары
* рисуются в колонке ch->curr_col + 1: графика ворот принадлежит правому
* краю своего тайла. Рисование — pop_fore_over_char (pop_bg.c). */
uint8_t pop_gate_over_char(const pop_char_t *ch) __banked;
/* Установлен в tilepos+1 в кадре, когда loose-пол упал (кусок улетел ВНИЗ).
* Приложение: определить debris-приземление в комнате снизу. Снять (=0). */
extern uint8_t pop_loose_fell;
/* Плита-ПОТОЛОК: loose ряда 2 комнаты СВЕРХУ (в оригинале — тот же loose с
* отрицательным tilepos, seg006 check_press frame 79 / seg007 animate_loose).
* pop_map_set_above даёт МУТАБЕЛЬНУЮ копию ряда 2 верхнего соседа (ту же, что
* ушла в pop_room_set_above): провалившаяся плита становится в ней EMPTY —
* сверху открывается колодец. pop_ceil_modif[col] — состояние (как
* pop_loose_modif), читает pop_bg. Сигналы приложению (для персистентности
* между входами в комнату): pop_ceil_fell = col+1 провалившейся плиты,
* pop_debris_at = tilepos+1 щебня, оставшегося в ЭТОЙ комнате. Снимать (=0). */
void pop_map_set_above(uint8_t *above_row2_fg) __banked;
extern uint8_t pop_ceil_modif[10];
extern uint8_t pop_ceil_fell;
extern uint8_t pop_debris_at;
/* Взводится в кадре, когда Kid получил урон (hitp_delta<0 оригинала) —
* отрисовка персонажа рисует «брызги» поверх Kid, а главный цикл снимает
* флаг. */
extern uint8_t pop_kid_hurt;
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
* приземления/удара. Зовётся из pop_phys_tick. */
void pop_check_knock(void) __banked;
/* Установлен в 1, когда Kid пересёк НИЖНЮЮ границу комнаты (провал в дыру
* или спуск-зацеп с кромки). Координаты Kid УЖЕ репроецированы в кадр
* комнаты снизу (y -= 189, curr_row = 0). Приложение решает: если есть
* links.down — сменить комнату (pop_room_load+перерисовка) и продолжить;
* иначе — выпал из уровня (респавн). Снять флаг после обработки. */
extern uint8_t pop_fell_out;
/* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed +
* determine_col + check_action (триггер падения / do_fall / приземление).
* Порядок как в оригинале (seg000 play_kid_frame).
*
* Физика внутри работает с АКТИВНЫМ персонажем (Char), как в оригинале, и
* сама открывает окно loadkid/savekid — звать вне окон Char. */
void pop_phys_tick(void) __banked;
/* То же для стража — порт хвоста play_guard_frame (seg000:1246). Звать из
* главного цикла СРАЗУ после pop_guard_tick() (тот прокручивает ИИ и
* последовательность), тоже вне окон Char: функция открывает своё.
*
* Физика у стража та же самая (одни функции над Char), отличается только
* список вызовов: нет ухода из комнаты, нет check_knock, вместо check_bumped
* — check_guard_bumped. Тот же вход обслужит скелета/тень/визиря, когда
* они появятся: в оригинале все они живут в слоте Guard.
*
* «Нет ухода из комнаты» — про СВОЙ ход. Следом за Кидом стража переносит
* exit_room/follow_guard (seg002:03C7/039E) — отдельная механика, у нас не
* портирована (GUARD-PHYS). */
void pop_guard_phys_tick(void) __banked;
/* clip_char (seg006:1749), верхняя граница клипа спрайта персонажа: если тайл
* над головой — стена/пол, всё выше линии своей полосы не рисуется (Kid,
* ушедший головой под пол при спуске/висе, не просвечивает сквозь щели).
* obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер.
* Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт pop_char_draw. */
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked;
/* Парная половина clip_char: ЭКРАННЫЙ X, правее которого спрайт не рисуется
* (правый косяк двери уровня в кадрах подъёма 224..228). 0 = обрезки нет. */
int pop_clip_char_right(void) __banked;
/* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после
* pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */
#define EDGE_CLOSER 0 /* край уступа над ямой (дистанция = до кромки) */
#define EDGE_WALL 1 /* стена впереди/под персонажем */
#define EDGE_FLOOR 2 /* пол (нет препятствия рядом, дистанция = 11) */
/* Расстояние до края/стены впереди + побочно g_edge_type (pop_edge_type). */
int pop_edge_distance(void) __banked;
uint8_t pop_edge_type(void) __banked;
/* Тайл, на котором остановился разбор края (curr_tile2 оригинала). */
uint8_t pop_edge_tile(void) __banked;
/* Стена впереди в пределах стопа? Если да — прижимает Kid.x к корректной
* позиции (передний край у стены, симметрично) и возвращает 1. Для
* control_running (чистый стоп у стены). */
int pop_wall_ahead(void) __banked;
/* Разбег-прыжок: выравнивание по кромке пола (run_jump, seg005:0AA8).
* Смотрит на 1–2 тайла вперёд; если там провал/пика — подгоняет Kid.x под
* толчок ровно с кромки. 1 = прыгать, 0 = ОТМЕНИТЬ прыжок (Кид не в той
* фазе относительно кромки; control_up гасить НЕЛЬЗЯ — попытка повторится
* на следующем кадре). Без этого провал в три тайла непроходим. */
uint8_t pop_run_jump_align(void) __banked;
/* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО
* прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x.
* check_jump_up/grab_up (K4.4). control_up гасит caller. */
uint8_t pop_jump_up_seq(void) __banked;
/* ---- Вис/зацеп/спуск (K4.2/K4.3) — тайловые решения для pop_ctrl --- *
* Управление (control_hanging/can_climb_up/hang_fall/down_pressed) в
* pop_ctrl; здесь — предикаты + выбор seq (+ выравнивание Kid.x). */
uint8_t pop_hang_grab_ready(void) __banked; /* grab_timer истёк — можно climb-up */
uint8_t pop_hang_climb_up_seq(void) __banked; /* seq_10 / seq_73 (подтягивание) */
int8_t pop_hang_wall(void) __banked; /* Shift в висе: 1=у стены,-1=fall,0=держ.*/
uint8_t pop_hang_fall_seq(void) __banked; /* seq_23 / seq_11 (сорваться), +Kid.x */
uint8_t pop_down_action(void) __banked; /* 1=обработано(спуск/отступ), 0=присед */
/* Дверь уровня рядом и ОТКРЫТА ПОЛНОСТЬЮ? → поставить Кида в проём (x +
* разворот влево, go_up_leveldoor) и вернуть 1; иначе 0, Кид не тронут.
* Ветка двери уровня из up_pressed (seg005:0482). Последовательность
* seq_70 ставит вызывающий (pop_ctrl) — тайлы наши, управление его. */
uint8_t pop_leveldoor_enter(void) __banked;
/* Зеркало уровня 4 (check_mirror, seg003:0798). Звать раз в кадр ПЕРЕД
* отрисовкой персонажей — в оригинале это первая строка draw_people
* (seg008:228A). Родит тень, если Кид только что пролетел сквозь зеркало. */
void pop_check_mirror(void) __banked;
#endif