From 7f3e32ddd64a2af6d9e684074ad13e477d219183 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 20 Jul 2026 20:22:52 +0300 Subject: [PATCH] =?UTF-8?q?roomtest:=20L2=20=E2=80=94=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=85=D0=BE=D0=B4=20=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=BD?= =?UTF-8?q?=D0=B0=D1=82=D1=83=20=D1=81=D0=BD=D0=B8=D0=B7=D1=83=20(=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BB/=D1=81=D0=BF=D1=83=D1=81=D0=BA)?= =?UTF-8?q?=20+=20=D1=84=D0=B8=D0=BA=D1=81=D1=8B=20=D0=BE=D0=BA=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D0=B7=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При пересечении нижней границы комнаты Kid переходит в комнату links.down, продолжая падать/спускаться (порт SDLPoP goto_other_room/leave_room). - pop_map check_leave_below (порт leave_room, seg002): триггер по Kid.y>=211 (не curr_row) — единый для провала И спуска-зацепа/climbdown; репроекция координат в кадр комнаты снизу (y-=189, curr_row=y_to_row_mod4) + сигнал pop_fell_out. Убран прежний преждевременный триггер y>=189 из do_fall (из-за него climbdown с curr_row=3 ждал y_land[4]=244 → пролёт через комнату). - roomtest: enter_room(room) — загрузка комнаты в рабочие массивы + отрисовка фона в обе страницы; обработчик pop_fell_out переключает комнату по pop_room_link(down) или респавнит (нет комнаты снизу). cur_room-трекинг. - pop_loose_reset (pop_map) + pop_loose_mob_reset (pop_bg): сброс loose-состояния (индексировано позицией тайла) при смене комнаты — иначе течёт в новую. Фиксы окклюзии при висе/спуске (сверено с SDLPoP): - other_overlay_tile: пустой тайл НЕ окклюдирует — draw_tile для него рисует лишь фоновую сетку (BLUELINE «силуэт кладки»), которая в оригинале уходит в y-сортируемый midtable ПОЗАДИ Kid; у нас клалась поверх. Пропускаем. - pop_fore_over_kid: порт set_char_collision (seg006:0723) — char_bottom_row = y_to_row_mod4(obj_y), обёртка -1 → ряд 3 (Kid НИЖЕ комнаты при спуске), а не ряд 0. Прежний кламп в 0 давал rT>rB → цикл окклюзии не выполнялся, нога рисовалась поверх пола. single-room: ряд 3 клампится в 2 (передняя грань пола). Проверено в MAME: провал вниз, спуск-зацеп/climbdown с кромки, окклюзия ноги. Известно (позже): осколки упавшего loose в комнате снизу (перенос mob), per-room modified-tile tracking (re-entry восстанавливает исходные тайлы), HP. Co-Authored-By: Claude Opus 4.8 --- applications/PoP/roomtest/pop_bg.c | 35 +++++++++++++--- applications/PoP/roomtest/pop_bg.h | 1 + applications/PoP/roomtest/pop_map.c | 39 +++++++++++++++-- applications/PoP/roomtest/pop_map.h | 8 +++- applications/PoP/roomtest/roomtest.c | 63 ++++++++++++++++++---------- 5 files changed, 114 insertions(+), 32 deletions(-) diff --git a/applications/PoP/roomtest/pop_bg.c b/applications/PoP/roomtest/pop_bg.c index 8034f5d..8440854 100644 --- a/applications/PoP/roomtest/pop_bg.c +++ b/applications/PoP/roomtest/pop_bg.c @@ -493,6 +493,10 @@ void pop_loose_mob_spawn(int row, int col) #define MOB_X0(mx) ((mx) - 4) #define MOB_W 64 /* mob_x-4 .. mob_x+59 (крышка +57 с запасом 2px) */ +/* Остановить падающий кусок (при смене комнаты — состояние per-tile не + * должно утечь в новую комнату). */ +void pop_loose_mob_reset(void) { mob_active = 0; } + void pop_loose_mob_tick(void) { if (!mob_active) return; @@ -586,7 +590,7 @@ static int8_t col_from_x(int xpos) /* get_tile_div_mod: колонка ( return (int8_t)xh; } -static int8_t y_to_row(int y) /* y_to_row_mod4 (TILE_SIZEY=63) */ +static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=63) */ { return (int8_t)((y + 60) / 63 % 4 - 1); } @@ -642,8 +646,16 @@ static void climb_overlay_tile(int row, int col, uint8_t fidx) static void other_overlay_tile(int row, int col) { uint8_t code = tile_code(row, col); - uint8_t left = tile_code(row, col - 1); - if (left == 0 || (code != 0 && col > 0 && tile_code(row, col - 2) == 0)) + uint8_t left; + /* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для + * него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань + * пустого соседа). В оригинале она уходит в y-сортируемый midtable и + * ложится ПОЗАДИ Kid; у нас draw_tile кладёт её прямо поверх → сетка на + * висящем/падающем Kid. Фон запечён один раз в pop_room_draw — над + * спрайтом его перерисовывать не нужно. */ + if (code == 0) return; + left = tile_code(row, col - 1); + if (left == 0 || (col > 0 && tile_code(row, col - 2) == 0)) draw_tile(row, col); } @@ -658,8 +670,21 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, xr = xl + wh; cL = col_from_x(xl); if (cL < 0) cL = 0; else if (cL > 9) cL = 9; cR = col_from_x(xr); if (cR < 0) cR = 0; else if (cR > 9) cR = 9; - rB = y_to_row(obj_y); if (rB < 0) rB = 0; else if (rB > 2) rB = 2; - rT = y_to_row(obj_y - (int)h + 1); if (rT < 0) rT = 0; else if (rT > 2) rT = 2; + /* Порт set_char_collision (seg006:0723): нижний ряд = y_to_row_mod4(obj_y), + * причём обёртка -1 (Kid НИЖЕ комнаты — climbdown/провал, obj_y в полосе + * «ряд 3») трактуется как ряд 3, НЕ ряд 0. Верхний ряд считается от + * char_top_y с ресетом в 0, если весь спрайт ниже комнаты (>=192). */ + { + int top_y = obj_y - (int)h + 1; + if (top_y >= 192) top_y = 0; + rT = y_to_row(top_y); + rB = y_to_row(obj_y); + if (rB == -1) rB = 3; /* обёртка → ряд 3 (ниже комнаты) */ + } + /* single-room roomtest: ряд 3 (комната снизу) в этом виде не рисуется → + * окклюдер спереди = ряд 2 (передняя грань пола, за которой нога). */ + if (rB < 0) rB = 0; else if (rB > 2) rB = 2; + if (rT < 0) rT = 0; else if (rT > 2) rT = 2; /* Голова/руки почти всегда торчат в ряд НАД опорным (Kid выше тайла). * y_to_row(верх спрайта) из-за +60-сдвига может схлопнуться в опорный * ряд (rT==rB) — тогда ряд над ним не попадёт в перебор и его окклюдер diff --git a/applications/PoP/roomtest/pop_bg.h b/applications/PoP/roomtest/pop_bg.h index f393322..7c44ff4 100644 --- a/applications/PoP/roomtest/pop_bg.h +++ b/applications/PoP/roomtest/pop_bg.h @@ -65,6 +65,7 @@ void pop_loose_bake_empty(int row, int col); * зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */ void pop_loose_mob_spawn(int row, int col); void pop_loose_mob_tick(void); +void pop_loose_mob_reset(void); /* остановить кусок (смена комнаты) */ /* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом * sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index d66030b..4bd26ff 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -360,10 +360,9 @@ static void do_fall(void) land(); else if (Kid.curr_row < 2) inc_curr_row(); /* следующий ряд ВНУТРИ комнаты */ - else - pop_fell_out = 1; /* упал за низ комнаты (нет пола, - комнаты снизу нет) — приложение - сбросит Kid (K-later: переход) */ + /* curr_row==2, нет пола: продолжает падать (fall_speed двигает y); + * пересечение нижней границы комнаты ловит check_leave_below (y>=211, + * порт leave_room) — общий путь для провала И спуска-зацепа. */ } } @@ -689,6 +688,20 @@ void pop_check_knock(void) } } +/* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest + * индексируются позицией тайла и не должны утечь из старой комнаты в новую. + * Плюс останавливает падающий кусок (pop_bg). */ +void pop_loose_reset(void) +{ + int i; + for (i = 0; i < 30; i++) { + pop_loose_modif[i] = 0; + loose_bake[i] = 0; + loose_rest[i] = 0; + } + pop_loose_mob_reset(); +} + /* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ * (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */ void pop_loose_tick(void) @@ -737,6 +750,23 @@ void pop_loose_tick(void) pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */ } +/* y_to_row_mod4 (seg006): ряд по y с оборотом mod4 (TILE_SIZEY=63). */ +static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); } + +/* check_leave_below (порт leave_room, seg002: leave_dir=3 при chary>=211): + * Kid пересёк нижнюю границу комнаты (провал в дыру ИЛИ спуск-зацеп/climbdown + * — единый триггер по y). Репроекция в кадр комнаты снизу (goto_other_room + * down: y-=189, curr_row=y_to_row_mod4) + сигнал pop_fell_out — приложение + * решит: переход (links.down) или выпал из уровня (нет комнаты снизу). */ +static void check_leave_below(void) +{ + if (Kid.y >= 211) { + Kid.y = (uint8_t)(Kid.y - 189); + Kid.curr_row = y_to_row((int16_t)Kid.y); + pop_fell_out = 1; + } +} + void pop_phys_tick(void) { if (grab_timer) grab_timer--; @@ -747,4 +777,5 @@ void pop_phys_tick(void) check_action(); check_press(); /* seg006: стойка/пробой loose → make_loose_fall */ pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */ + check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */ } diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index 4c4f290..6c792d7 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -26,13 +26,17 @@ void pop_map_set(uint8_t *fg); * рядом с kid-отрисовкой. */ extern uint8_t pop_loose_modif[30]; void pop_loose_tick(void); +void pop_loose_reset(void); /* сброс loose-состояния при смене комнаты */ /* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда * приземления/удара. Зовётся из pop_phys_tick. */ void pop_check_knock(void); -/* Установлен в 1, когда Kid упал ниже комнаты (комнаты снизу нет в этом - * тесте). Приложение должно сбросить Kid (kid_init) и снять флаг. */ +/* Установлен в 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 + diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index cfa7d42..0d99b2a 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -18,19 +18,40 @@ /* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */ #define SEQ_STAND 2 #define ROOM_1 1 +#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */ + +/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы + * соседей для кромок. Заполняются pop_room_load из уровня. File-scope, + * чтобы enter_room переключал комнату без длинного списка параметров. */ +static uint8_t room_fg[30], room_bg[30]; +static uint8_t lcol_fg[3], lcol_bg[3], below_fg[10]; +static uint8_t cur_room; /* номер текущей комнаты (для переходов) */ + +/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ + * страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет + * активной страницу 1 — вызывающий восстанавливает нужную draw-страницу. */ +static void enter_room(uint8_t room) +{ + uint8_t pg; + pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg, below_fg); + pop_map_set(room_fg); + pop_room_set_below(below_fg); + pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ + for (pg = 0; pg < 2; pg++) { + gfx_set_draw_page(pg); + setfillstyle(SOLID_FILL, BLACK); + bar(0, 0, 319, 255); + pop_room_draw(room, room_fg, room_bg, lcol_fg, lcol_bg); + } + cur_room = room; +} int main(void) { /* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1 * по умолчанию (без мерцания); SPACE переключает в однобуфер для * отладки багов рисования (edge по sp_prev, чтоб не дёргать). */ - uint8_t pg, front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0; - /* МУТАБЕЛЬНАЯ рабочая копия ТЕКУЩЕЙ комнаты в обычной памяти (W2) — - * на отрисовку (pop_room_draw) И коллизию (pop_map): loose-пол при - * падении меняет тайл на empty, оба модуля должны это видеть. Плюс - * срезы соседей для кромок. Заполняются pop_room_load из уровня. */ - uint8_t room_fg[30], room_bg[30]; - uint8_t lcol_fg[3], lcol_bg[3], below_fg[10]; + uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0; if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */ puts("pop_bg_load failed"); @@ -56,21 +77,13 @@ int main(void) * ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ * страницы, в которую рисуем. Дальше каждый тик восстанавливаем * ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */ - /* Извлечь комнату 1 из уровня в рабочие массивы (W2): тайлы + срезы - * соседей (правый столбец left-комнаты, верхний ряд down-комнаты). */ - pop_room_load(ROOM_1, room_fg, room_bg, lcol_fg, lcol_bg, below_fg); - pop_room_set_below(below_fg); /* ряд снизу для «стены вниз» */ - for (pg = 0; pg < 2; pg++) { - gfx_set_draw_page(pg); - setfillstyle(SOLID_FILL, BLACK); - bar(0, 0, 319, 255); - pop_room_draw(ROOM_1, room_fg, room_bg, lcol_fg, lcol_bg); - } + /* Извлечь и отрисовать стартовую комнату 1 (в обе страницы). */ + enter_room(ROOM_1); gfx_set_draw_page(0); /* K2b: Kid стоит на уступе (row1), управляется с клавиатуры. - * ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход. */ - pop_map_set(room_fg); /* карта для коллизии/падения (K3) */ + * ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход. + * Карта коллизии (pop_map_set) уже задана в enter_room. */ kid_init(SEQ_STAND, 1, 1, 0); kid_draw(); pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(), @@ -114,9 +127,17 @@ int main(void) kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */ kid_tick(); /* play_seq: следующий кадр */ pop_phys_tick(); /* K3: падение/приземление/стена */ - if (pop_fell_out) { /* упал за низ комнаты — сброс на старт */ + if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */ + uint8_t down = pop_room_link(cur_room, LINK_DOWN); pop_fell_out = 0; - kid_init(SEQ_STAND, 1, 1, 0); + if (down) { /* L2: переход в комнату снизу — продолжает падать */ + enter_room(down); /* координаты Kid уже репроецированы pop_map */ + gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */ + } else { /* выпал из уровня (нет комнаты снизу) — респавн */ + enter_room(ROOM_1); + gfx_set_draw_page(back); + kid_init(SEQ_STAND, 1, 1, 0); + } } pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */ /* — ДО Kid, чтобы Kid всегда был поверх плиты) */