PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат
Скелет (L3-SKEL, ассеты + механика): - pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3 curr_guard_color = 0, оригинал палитру не подменяет); - pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом стража и был невидим; - load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень — только в кадрах 150..189. Пока ветка была одна (charid_2_guard), скелет получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался; - check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в комнате 3 при падении (seg002:252), autocontrol_skeleton; - leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня. Цвета стражей (BUG-GUARD-COLOR-1, закрыт): - все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257). Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP меняется вместе со стражем. Грабля: gfx_pal_load отдаёт указатель в BIOS, а тот читает только #4000-#BFFF — таблицу из банка копируем в стек. Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт): - pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19. Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать обратно. +48 байт W2. Окклюзия соперника: - pop_fore_over_char получил проход other_overlay_tile (порядок midtable, seg008:1B06) и расширение перебора объединённым прямоугольником «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх кладки и верхней грани пола; - клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP; - ROOMNAV после смерти Кида делает честный pop_start_level: телепорт «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с вернувшейся кучей костей. Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком (101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) — render_room анимированные тайлы пропускает, и в атласе не было ни одного. Число EMM-страниц не изменилось. Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок (резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен гард «код наехал на данные» (DATA_LOC). Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3: level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT). В roomtest.c временно оставлен автостоп на падении соперника (отладка падений скелета) — помечен ВРЕМЕННО. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,7 @@ uint8_t pop_cheats;
|
||||
* теряется примерно в 44 % случаев, поэтому байты копятся и один из
|
||||
* полусотни пропадает ДО чтения порта: нажатие «не срабатывает».
|
||||
*
|
||||
* Что НЕ помогает (замерено, TASKS.md/KBD-1): редкий опрос (несколько
|
||||
* Что НЕ помогает (замерено, TASKS_CLOSED.md/KBD-1): редкий опрос (несколько
|
||||
* вызовов за кадр — они попадают туда, где прерывания и так разрешены, и
|
||||
* лишь дублируют трамплин) и укорачивание DI-окон графики (в кадре вообще
|
||||
* без блитов потерь БОЛЬШЕ). Снимать `di` в accel-ядрах нельзя — машина
|
||||
@@ -98,7 +98,9 @@ static void kbd_idle(void)
|
||||
#define ROOM_1 1
|
||||
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
||||
|
||||
#define FIRST_LEVEL 1 /* с какого уровня начинается игра */
|
||||
#ifndef FIRST_LEVEL
|
||||
#define FIRST_LEVEL 1 /* с какого уровня начинается игра (-DFIRST_LEVEL=N) */
|
||||
#endif
|
||||
|
||||
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
|
||||
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
|
||||
@@ -259,16 +261,25 @@ static void flash_bg(uint8_t r, uint8_t g, uint8_t b)
|
||||
/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ
|
||||
* страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет
|
||||
* активной страницу 1 — вызывающий восстанавливает нужную draw-страницу. */
|
||||
static void enter_room(uint8_t room)
|
||||
/* side — куда ушёл КИД (0 left, 1 right, 2 up, 3 down); 0xFF = «не переход
|
||||
* за Кидом» (старт уровня, респавн, чит-навигация). От него зависит ровно
|
||||
* одно: пойдёт ли страж следом (pop_guard_follow, порт exit_room+
|
||||
* follow_guard) или останется в прежней комнате. */
|
||||
static void enter_room_side(uint8_t room, uint8_t side)
|
||||
{
|
||||
uint8_t pg;
|
||||
pop_guard_leave(); /* запомнить стража уходящей комнаты */
|
||||
/* Страж идёт за Кидом? — решаем ДО того, как что-либо загрузим: функции
|
||||
* нужны и старая комната (Guard.room), и целевая. Пошёл — ни leave, ни
|
||||
* enter звать нельзя: он уже перенесён и живёт дальше как есть. */
|
||||
uint8_t follow = (uint8_t)(side <= 3 ? pop_guard_follow(room, side) : 0);
|
||||
if (!follow) pop_guard_leave(); /* запомнить стража уходящей комнаты */
|
||||
uint8_t *room_mod;
|
||||
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg, rcol_fg, rcol_bg, below_fg);
|
||||
pop_map_set(room_fg);
|
||||
/* Соседей слева/справа карта забирает из уровня САМА (целиком, см.
|
||||
* pop_map_set_edges) — отдельной копии в приложении нет. */
|
||||
pop_map_set_edges(pop_room_link(room, 0), pop_room_link(room, 1),
|
||||
pop_room_link(room, 2), pop_room_link(room, 3),
|
||||
lcol_fg, rcol_fg);
|
||||
pop_room_link(room, 2), pop_room_link(room, 3));
|
||||
pop_room_set_below(below_fg);
|
||||
/* Ряд 2 верхнего соседа → полоса кладки у потолка (row0). fg — статика
|
||||
* уровня, mod — живой room_modif (ленивая инициализация). */
|
||||
@@ -296,7 +307,8 @@ static void enter_room(uint8_t room)
|
||||
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
||||
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
||||
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||||
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
|
||||
if (!follow)
|
||||
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
|
||||
pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */
|
||||
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
||||
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
|
||||
@@ -325,6 +337,10 @@ static void enter_room(uint8_t room)
|
||||
pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */
|
||||
}
|
||||
|
||||
/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход.
|
||||
* Страж такой смены не «видит» — он остаётся в своей комнате. */
|
||||
static void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
||||
|
||||
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
||||
* (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и
|
||||
* для запуска, и для рестарта после смерти / выпадения из уровня: в
|
||||
@@ -378,6 +394,7 @@ static uint8_t pop_checkpoint;
|
||||
|
||||
static void pop_start_level(void)
|
||||
{
|
||||
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
|
||||
uint8_t entry_pose = pop_level_entry_pose(pop_current_level);
|
||||
uint8_t start_room = pop_level_start_room();
|
||||
uint8_t pos = pop_level_start_pos();
|
||||
@@ -444,6 +461,7 @@ int main(void)
|
||||
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
|
||||
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
|
||||
uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
|
||||
uint8_t autofrz = 0; /* ВРЕМЕННО: автостоп на падении уже сработал */
|
||||
uint16_t dead_frames = 0;
|
||||
#ifdef ROOMNAV
|
||||
uint8_t nav_prev = 0;
|
||||
@@ -525,6 +543,22 @@ int main(void)
|
||||
* стоит, можно спокойно разбирать позу Kid и окклюзию. */
|
||||
if (kbd_raw_down(0x16)) frozen = 1;
|
||||
if (kbd_raw_down(0x1E)) frozen = 0;
|
||||
/* ВРЕМЕННО: автостоп на падении соперника в провал — сцену «скелет
|
||||
* просвечивает сквозь кладку» руками не поймать (падение длится
|
||||
* несколько кадров). Замирает сам, дальше снимок и чтение памяти.
|
||||
*
|
||||
* Срабатывает ОДИН раз на падение: пока стоит фриз, состояние
|
||||
* персонажа не меняется, поэтому условие остаётся истинным — без
|
||||
* этого флага снятие фриза клавишей '2' тут же гасилось повторным
|
||||
* автостопом в той же итерации. Взводится заново, когда падение
|
||||
* кончилось (страж убран или вышел из freefall).
|
||||
* Снять вместе с разбором. */
|
||||
if (Guard.charid != 0 && Guard.action == 4 /* in_freefall */ &&
|
||||
Guard.y > 150) {
|
||||
if (!autofrz) { frozen = 1; autofrz = 1; }
|
||||
} else {
|
||||
autofrz = 0;
|
||||
}
|
||||
{ /* Читы (seg000: switch под if (cheats_enabled)). Все — по
|
||||
* ФРОНТУ нажатия, иначе срабатывали бы каждый кадр.
|
||||
* K — убить стража;
|
||||
@@ -592,6 +626,14 @@ int main(void)
|
||||
((kbd_raw_down(0x7B) || kbd_raw_down(0x4E)) << 1));
|
||||
if (nav && !nav_prev) {
|
||||
uint8_t r = cur_room, i, c, pos = 20, any = 0xFF;
|
||||
/* Кид МЁРТВ: сначала честный рестарт уровня, потом телепорт.
|
||||
* Навигация только переставляет Кида (kid_init + сброс HP),
|
||||
* то есть «оживляет» его мимо start_level — а тот обязан
|
||||
* вернуть тайлы, модификаторы и ОЧИСТИТЬ слот соперника
|
||||
* (seg003:100: Guard.direction = dir_56_none). Без этого
|
||||
* после смерти оставался живой скелет рядом с вернувшейся
|
||||
* кучей костей. */
|
||||
if (pop_kid_dead) { pop_start_level(); dead_frames = 0; }
|
||||
r = (nav & 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
|
||||
: (uint8_t)(r == 1 ? 24 : r - 1);
|
||||
enter_room(r);
|
||||
@@ -616,6 +658,7 @@ int main(void)
|
||||
back = dbuf ? (uint8_t)(front ^ 1) : 0;
|
||||
gfx_set_draw_page(back);
|
||||
|
||||
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
|
||||
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
||||
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
||||
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
|
||||
@@ -625,11 +668,13 @@ int main(void)
|
||||
kid_tick(); /* play_seq: следующий кадр */
|
||||
pop_phys_tick(); /* K3: падение/приземление/стена */
|
||||
pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */
|
||||
pop_guard_phys_tick(); /* и его физика — та же, что у Кида (play_guard_frame) */
|
||||
/* Боёвка, порядок как в play_frame (seg000:872): сначала пометить
|
||||
* попадания с обеих сторон, потом применить, потом свести HP. */
|
||||
pop_check_sword_hurting();
|
||||
pop_check_sword_hurt();
|
||||
pop_do_delta_hp();
|
||||
pop_guard_fallout(); /* страж провалился ниже комнаты — убрать (seg002:241) */
|
||||
if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */
|
||||
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
||||
pop_fell_out = 0;
|
||||
@@ -643,6 +688,7 @@ int main(void)
|
||||
} else if (pop_leave_dir) { /* L3: уход вбок/вверх (координаты уже репроецированы) */
|
||||
uint8_t nb = pop_room_link(cur_room, (uint8_t)(pop_leave_dir - 1));
|
||||
uint8_t left = (uint8_t)(pop_leave_dir == 1);
|
||||
uint8_t pop_leave_dir_prev = pop_leave_dir;
|
||||
pop_leave_dir = 0;
|
||||
/* level3_set_chkp (seg002:0665). ВНИМАНИЕ на комнату: оригинал
|
||||
* зовёт его из leave_room ДО goto_other_room, то есть Char.room
|
||||
@@ -662,7 +708,9 @@ int main(void)
|
||||
hitp_beg_lev = hitp_max;
|
||||
}
|
||||
if (nb) {
|
||||
enter_room(nb);
|
||||
/* side для pop_guard_follow — то же направление, по которому
|
||||
* ищем соседа (roomleave_result оригинала). */
|
||||
enter_room_side(nb, (uint8_t)(pop_leave_dir_prev - 1));
|
||||
gfx_set_draw_page(back);
|
||||
}
|
||||
}
|
||||
@@ -705,6 +753,13 @@ int main(void)
|
||||
if (pop_level_load_num(pop_next_level) != 0) {
|
||||
pop_next_level = pop_current_level; /* файла нет — остаёмся */
|
||||
} else {
|
||||
/* Набор спрайтов соперника у каждого уровня СВОЙ
|
||||
* (load_lev_spr, seg000:1092): уровень 3 — скелет, у него и
|
||||
* кадров меньше, чем у стража. Без перезагрузки соперник
|
||||
* рисуется чужим атласом: часть индексов уходит за его
|
||||
* пределы, и персонаж просто не виден. */
|
||||
pop_guard_free();
|
||||
pop_guard_load();
|
||||
/* Всё состояние прошлого уровня сносит сам pop_start_level:
|
||||
* pop_trob_reset обнуляет room_seen (модификаторы комнат
|
||||
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают
|
||||
@@ -783,6 +838,13 @@ int main(void)
|
||||
PROF(6); /* kid_draw (тот же циан) */
|
||||
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
||||
if (g_after) pop_guard_draw();
|
||||
/* Брызги стража рисует сам pop_guard_draw (порядок objtable), а
|
||||
* флаг снимаем здесь: стража могло не оказаться в отрисованной
|
||||
* комнате — тогда брызги просто пропадают, как в оригинале.
|
||||
* Снимаем ПО ФАКТУ, а не безусловно: запись в кадре удара —
|
||||
* готовая точка останова для отладки в MAME (watchpoint), и
|
||||
* лишней записи каждый кадр нет. */
|
||||
if (pop_guard_hurt) pop_guard_hurt = 0;
|
||||
}
|
||||
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
|
||||
if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */
|
||||
|
||||
Reference in New Issue
Block a user