From 7c1ded340c1a58c26cede1be77c5547b6ab2f3c5 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 12 Aug 2026 17:43:22 +0300 Subject: [PATCH] =?UTF-8?q?L9-INVERT:=20=D0=B7=D0=B5=D1=80=D0=BA=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9=20?= =?UTF-8?q?=D1=84=D0=BE=D0=BD=D0=B0=20+=20=D0=B2=D1=85=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=BD=D0=B0=D1=82=D1=83=20=D0=B2=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=BD=D0=BA=208?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit II.3: pop_upside проведён в единственную точку тайлового слоя (pop_blit_b / blit_b_clip): позиция через FLIP_TOP, блит — vflip-двойник. Этим зеркалятся полная отрисовка комнаты, точечные редрои, trob-анимации, fore-слой и кладка. В клипованном пути обрезка сверху экрана съедает нижние строки источника, поэтому кусок пересчитывается как sy' = h - sy - dh. MEM-COLD2 п.1: enter_room_side/enter_room — в банк 8 (куча 450 -> 1231 Б). Рабочие массивы комнаты остались в _DATA, в банк уехал только код. Проверено в MAME (уровень 9, чит U): комната перевёрнута целиком, пол вверху, дверь и кладка вверх ногами. tests-host 6/6, size-check OK. Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/l9_invert_plan.md | 24 +++- applications/PoP/roomtest/pop_tile.c | 38 +++++- applications/PoP/roomtest/roomtest.c | 159 ++-------------------- applications/PoP/roomtest/roomtest_cold.c | 144 ++++++++++++++++++++ applications/PoP/roomtest/roomtest_cold.h | 24 +++- 5 files changed, 228 insertions(+), 161 deletions(-) diff --git a/applications/PoP/docs/l9_invert_plan.md b/applications/PoP/docs/l9_invert_plan.md index bdc2913..379c815 100644 --- a/applications/PoP/docs/l9_invert_plan.md +++ b/applications/PoP/docs/l9_invert_plan.md @@ -243,7 +243,29 @@ void gfx_copy_rect(uint8_t src_page, int sx, int sy, вероятный класс багов здесь). Решение: зеркалит ВЫЗЫВАЮЩИЙ, у примитивов семантика не меняется. -## II.3 Фон +## II.3 Фон — СДЕЛАНО 2026-08-12 + +Флаг проведён в ЕДИНСТВЕННУЮ точку — `pop_blit_b`/`blit_b_clip` +(`pop_tile.c`): позиция пересчитывается макросом `FLIP_TOP` (он один на весь +слой — дублировать нельзя, иначе двойной переворот), а блит выбирает +vflip-двойник. Тем самым зеркалятся и полная отрисовка комнаты, и точечные +редрои, и анимации trob, и fore-слой, и кладка — все они ходят через эту +функцию. + +Тонкость клипованного пути: при зеркале обрезка СВЕРХУ экрана съедает +НИЖНИЕ строки источника, поэтому кусок пересчитывается как +`sy' = h - sy - dh`. + +Проверено в MAME (уровень 9, чит U): комната перевёрнута целиком — пол +вверху, дверь уровня и кладка вверх ногами. Персонажи пока обычные (II.4). + +Цена: три функции libbgi ушли в резидент, куча 1574 → 450 Б, поэтому тем же +заходом сделан **MEM-COLD2 п.1** — `enter_room_side`/`enter_room` уехали в +банк 8 (куча вернулась к **1231 Б**, банк занят на 14.6%). Рабочие массивы +комнаты остались в `_DATA` и видны обеим половинам; `enter_room` обязана быть +`__banked` — её зовёт главный цикл. + +### Контракт (исходное описание) Одна точка: `pop_tile.c` (блит куска атласа) выбирает vflip-двойник по `pop_upside` и пересчитывает `y`. Тогда автоматически попадают: diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index c84d191..eaf636a 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -10,7 +10,8 @@ #include #include #include -#include "pop_bg.h" /* POP_YOFF */ +#include "pop_bg.h" /* POP_YOFF, POP_PLAYFIELD_H */ +#include "pop_map.h" /* pop_upside — переворот (зелье инверсии) */ #include "pop_cdraw.h" /* POP_CD_TOUCH — фон тронут, персонажей перерисовать */ #include "pop_tile.h" #include "pop_level.h" /* pop_doorlink2 — подстановка нажатой кнопки */ @@ -262,6 +263,12 @@ void pop_cd_clear(uint8_t p) * платили и блиты БЕЗ клипа (`ld -n(ix)` = 19 тактов номинала, у нас ~46). * А блитов без клипа большинство: весь фон вне fore-прохода — факелы, зелья, * перерисовка тайлов — идёт с pop_t_fclip_on == 0. */ +/* Экранный y при ПЕРЕВОРОТЕ (зелье инверсии): поле отражается внутри себя, + * борта не трогаются. Одна формула на весь слой фона — если её продублируют + * где-то ещё, получим двойной переворот (см. l9_invert_plan.md §II.2: + * зеркалит ТОЛЬКО вызывающий, у примитивов семантика не меняется). */ +#define FLIP_TOP(top, h) (2 * POP_YOFF + POP_PLAYFIELD_H - (top) - (int)(h)) + static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h) { int dx = x, dy = top, dw = w, dh = h, sx = 0, sy = 0; @@ -275,6 +282,20 @@ static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h) if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy; } if (dw <= 0 || dh <= 0) return; + /* ПЕРЕВОРОТ: экранная строка (dy+i) берёт источник (h-1-i), поэтому + * обрезка СВЕРХУ экрана съедает НИЖНИЕ строки источника. В терминах + * куска источника это sy' = h - sy - dh (при обрезке только сверху даёт + * 0, при обрезке только снизу — верхний край). */ + if (pop_upside) { + int fsy = h - sy - dh; + if (fsy < 0) fsy = 0; + if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 && + dx + dw <= 320 && dy + dh <= 256 && w < 256) + gfx_blit_part_noclip_vflip(dx, dy, img, (uint8_t)sx, (uint8_t)fsy, + (uint8_t)dw, (uint8_t)dh); + else + gfx_blit_part_vflip(dx, dy, img, sx, fsy, dw, dh); + } else if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 && dx + dw <= 320 && dy + dh <= 256 && w < 256) { if (sx == 0 && sy == 0 && dw == w && dh == h) @@ -313,6 +334,10 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */ + /* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно + * fore-слоя, полоса у потолка, отсев по экрану) считается уже в + * экранных координатах результата. */ + if (pop_upside) top = FLIP_TOP(top, h); if (w && h) { if (!pop_t_fclip_on && !pop_t_clip_top) { /* БЫСТРЫЙ путь: клипа нет вовсе. Так идут все блиты фона вне @@ -321,10 +346,13 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) * платит за клип/16-бит/split, а не за пиксели). W3-скобку * ставит сама libbgi: из кода с --w3 её вызывать нельзя. */ if (x >= 0 && top >= 0 && w < 256 && h < 256 && - x + (int)w <= 320 && top + (int)h <= 256) - gfx_blit_noclip(x, top, img); - else - gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h); + x + (int)w <= 320 && top + (int)h <= 256) { + if (pop_upside) gfx_blit_noclip_vflip(x, top, img); + else gfx_blit_noclip(x, top, img); + } else { + if (pop_upside) gfx_blit_part_vflip(x, top, img, 0, 0, (int)w, (int)h); + else gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h); + } pop_cd_touch(x, top, (int)w, (int)h); } else { blit_b_clip(img, x, top, (int)w, (int)h); diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index d678ab1..2866947 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -124,14 +124,14 @@ static void kbd_idle(void) * соседей для кромок. Заполняются pop_room_load из уровня. File-scope, * чтобы enter_room переключал комнату без длинного списка параметров. */ uint8_t room_fg[30]; -static uint8_t room_bg[30]; +uint8_t room_bg[30]; /* lcol_fg/rcol_fg — ШЕСТЬ записей: [0..2] кромочная колонка соседа (col9/col0), * [3..5] следующая за ней (col8/col1) для коллизии, когда Kid стоит В шве и * смотрит наружу (колонка −2 / 11). См. pop_level.h. */ -static uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11]; -static uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */ +uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11]; +uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */ uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */ -static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */ +uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */ /* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid * слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та @@ -167,7 +167,7 @@ static void update_kid_render_dx(void) * Видимой её делает draw_tile: нажатый opener рисуется как ПОЛ (seg008 * get_tile_to_draw), а у пола есть правая грань, у кнопки — нет. * Поэтому в сигнатуру ряда подмешиваем бит «кнопка нажата». */ -static uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r) +uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r) { uint8_t code = (uint8_t)(lfg[r] & 0x1F); uint8_t mod = m[9 + r * 10]; @@ -176,9 +176,9 @@ static uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r) return mod; } -static uint8_t seam_sig[3]; /* последняя нарисованная openness гейта в шве */ -static uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */ -static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */ +uint8_t seam_sig[3]; /* последняя нарисованная openness гейта в шве */ +uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */ +uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */ /* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7 * уровня 3 (level3_set_chkp, seg002:0665); по нему do_startpos подменяет @@ -214,149 +214,6 @@ static void flash_bg(uint8_t r, uint8_t g, uint8_t b) gfx_pal_set(1, 0, r, g, b); } -/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ - * страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет - * активной страницу 1 — вызывающий восстанавливает нужную draw-страницу. */ -/* 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; - /* Страж идёт за Кидом? — решаем ДО того, как что-либо загрузим: функции - * нужны и старая комната (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)); - /* Боковой переход: колонки сдвинулись ровно на 10 (x ∓140), флаги - * перекрытия при этом инвариантны — историю перенумеровываем, а не - * теряем. Без этого решётка шва не бампит на кадре входа и Кид с - * разбега проходит сквозь закрытые ворота (BUG-GATE-PASS-1). */ - if (side == 0) pop_coll_shift(+10); /* ушёл ВЛЕВО: col += 10 */ - else if (side == 1) pop_coll_shift(-10); /* ушёл ВПРАВО: col −= 10 */ - pop_room_set_below(below_fg); - /* Ряд 2 верхнего соседа → полоса кладки у потолка (row0). fg — статика - * уровня, mod — живой room_modif (ленивая инициализация). */ - { - uint8_t up = pop_room_link(room, 2), c; - if (up) { - uint8_t *um = pop_trob_modif(up); - pop_level_access_begin(); - for (c = 0; c < 10; c++) above_fg[c] = pop_level_tile_raw(up, (uint8_t)(20 + c)); - pop_level_access_end(); - for (c = 0; c < 10; c++) above_mod[c] = um[20 + c]; - pop_room_set_above(above_fg, above_mod); - pop_map_set_above(above_fg); /* плита-потолок: пробой снизу + провал */ - } else { - pop_room_set_above(0, 0); /* нет комнаты сверху → пол (кромка) */ - pop_map_set_above(0); - } - } - pop_map_set_room(room); - /* Kid.room — как в оригинале: «комната персонажа». Полная модель - * (Kid.room ≠ отрисованной у шва) ещё не портирована, но сравнение - * Kid.room == Guard.room нужно уже сейчас: без него луч видимости - * стража никогда не срабатывает. */ - Kid.room = room; - pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ - pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */ - pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */ - pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */ - /* check_shadow (seg002:0064) стоит ПЕРЕД enter_guard и на «шадоу»- - * уровнях забирает слот соперника себе: вернула 1 — стража поднимать - * НЕЛЬЗЯ (в комнате 24 уровня 5 он есть в данных, но оригинал его туда - * не пускает). Порядок ровно как в оригинале, не «сначала страж, потом - * заменим». */ - if (!follow && !pop_check_shadow(room)) - pop_guard_enter(room); /* страж комнаты (порт enter_guard) */ - pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */ - /* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ - * per-room (room_modif живёт между входами); рисуем и коллидируем по ним, - * а не по свежему backtable. Фаза P0. */ - room_mod = pop_trob_modif(room); - /* Модификаторы швов — из ЖИВОГО room_modif соседей (openness ворот, - * видимых через шов, персистентен: ворота room8(0,9) видны в col0 room6). - * pop_room_load заполнил lcol_bg/rcol_bg статикой уровня — перекрываем. */ - { - uint8_t lft = pop_room_link(room, 0), rgt = pop_room_link(room, 1); - if (lft) { uint8_t *m = pop_trob_modif(lft), i; - for (i = 0; i < 3; i++) { - lcol_bg[i] = m[9 + i * 10]; - /* шов рисуется в обе страницы ниже — синхр. сигнатуру */ - seam_sig[i] = seam_row_sig(m, lcol_fg, i); - } } - if (rgt) { uint8_t *m = pop_trob_modif(rgt); - rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; } - } - seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */ - /* ENTER-ROOM-FAST: комната рисуется ОДИН раз — в скрытую страницу, — и - * тут же показывается флипом; вторая страница дабл-буфера получает её - * КОПИЕЙ акселератора (gfx_copy_page, ~384 burst-строки против почти - * полумиллиона тактов на вторую отрисовку). Побочный эффект важнее - * тактов: игрок больше не видит, КАК рисуются тайлы — только готовый - * кадр. - * - * Копия идёт банком 0x50, то есть обновляет и VRAM, и ОЗУ-копию - * страницы-приёмника — иначе heal на ней восстанавливал бы фон прошлой - * комнаты (инвариант gfx_copy_page, регресс tests/pageflip). - * - * Видимая страница здесь МЕНЯЕТСЯ, поэтому главный цикл перечитывает её - * в начале каждого кадра (front = gfx_get_visible_page()), а не ведёт - * свой счёт. */ - { - gfx_rect_t full; - pg = (uint8_t)(gfx_get_visible_page() ^ 1); /* скрытая */ - gfx_set_draw_page(pg); - setfillstyle(SOLID_FILL, BLACK); - bar(0, 0, 319, 255); - pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg); - gfx_set_visible_page(pg); /* показать готовое */ - gfx_set_draw_page((uint8_t)(pg ^ 1)); - full.x = 0; full.y = 0; full.w = 320; full.h = 256; - gfx_copy_page(&full, GFX_COPY_DIRECT); /* вторую — копией */ - } - cur_room = room; - kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */ - pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */ -} - -/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход. - * Страж такой смены не «видит» — он остаётся в своей комнате. */ -void enter_room(uint8_t room) { enter_room_side(room, 0xFF); } - -/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos - * (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и - * для запуска, и для рестарта после смерти / выпадения из уровня: в - * оригинале это буквально один и тот же путь (play_level_2 зовёт - * start_level заново), поэтому и у нас не три копии четырёх строк. - * - * Позиция берётся ИЗ ДАННЫХ уровня (pop_level_start_*), а не из #define: - * start_pos — тайл 0..29 (row*10+col), start_dir — направление, в котором - * персонаж должен ОКАЗАТЬСЯ. - * - * Направление ИНВЕРТИРУЕТСЯ (`Char.direction = ~level.start_dir`): персонаж - * ставится спиной и доворачивается кадрами позы входа. Сама поза — из - * tbl_entry_pose (data.h:848 = {0,1,0,…,2,…}): - * 1 (уровень 1) — падение внутрь (seq_7_fall) + нажатие кнопки room5(0,2); - * это оно захлопывает решётку за спиной в первой комнате; - * 2 (уровень 13) — вбегание (seq_84_run); - * иначе — разворот на месте (seq_5_turn). - * Таблица индексируется НОМЕРОМ УРОВНЯ — pop_level_entry_pose(). */ -#define SEQ_TURN 5 -#define SEQ_FALL 7 -#define SEQ_RUN 84 -#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */ -#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */ -#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */ - int main(void) { /* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1 diff --git a/applications/PoP/roomtest/roomtest_cold.c b/applications/PoP/roomtest/roomtest_cold.c index 602c307..84f37a0 100644 --- a/applications/PoP/roomtest/roomtest_cold.c +++ b/applications/PoP/roomtest/roomtest_cold.c @@ -255,6 +255,150 @@ void pop_dbg_room_label(uint8_t room) __banked room_label_redraw--; } +/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ + * страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет + * активной страницу 1 — вызывающий восстанавливает нужную draw-страницу. */ +/* side — куда ушёл КИД (0 left, 1 right, 2 up, 3 down); 0xFF = «не переход + * за Кидом» (старт уровня, респавн, чит-навигация). От него зависит ровно + * одно: пойдёт ли страж следом (pop_guard_follow, порт exit_room+ + * follow_guard) или останется в прежней комнате. */ +void enter_room_side(uint8_t room, uint8_t side) __banked +{ + uint8_t pg; + /* Страж идёт за Кидом? — решаем ДО того, как что-либо загрузим: функции + * нужны и старая комната (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)); + /* Боковой переход: колонки сдвинулись ровно на 10 (x ∓140), флаги + * перекрытия при этом инвариантны — историю перенумеровываем, а не + * теряем. Без этого решётка шва не бампит на кадре входа и Кид с + * разбега проходит сквозь закрытые ворота (BUG-GATE-PASS-1). */ + if (side == 0) pop_coll_shift(+10); /* ушёл ВЛЕВО: col += 10 */ + else if (side == 1) pop_coll_shift(-10); /* ушёл ВПРАВО: col −= 10 */ + pop_room_set_below(below_fg); + /* Ряд 2 верхнего соседа → полоса кладки у потолка (row0). fg — статика + * уровня, mod — живой room_modif (ленивая инициализация). */ + { + uint8_t up = pop_room_link(room, 2), c; + if (up) { + uint8_t *um = pop_trob_modif(up); + pop_level_access_begin(); + for (c = 0; c < 10; c++) above_fg[c] = pop_level_tile_raw(up, (uint8_t)(20 + c)); + pop_level_access_end(); + for (c = 0; c < 10; c++) above_mod[c] = um[20 + c]; + pop_room_set_above(above_fg, above_mod); + pop_map_set_above(above_fg); /* плита-потолок: пробой снизу + провал */ + } else { + pop_room_set_above(0, 0); /* нет комнаты сверху → пол (кромка) */ + pop_map_set_above(0); + } + } + pop_map_set_room(room); + /* Kid.room — как в оригинале: «комната персонажа». Полная модель + * (Kid.room ≠ отрисованной у шва) ещё не портирована, но сравнение + * Kid.room == Guard.room нужно уже сейчас: без него луч видимости + * стража никогда не срабатывает. */ + Kid.room = room; + pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ + pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */ + pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */ + pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */ + /* check_shadow (seg002:0064) стоит ПЕРЕД enter_guard и на «шадоу»- + * уровнях забирает слот соперника себе: вернула 1 — стража поднимать + * НЕЛЬЗЯ (в комнате 24 уровня 5 он есть в данных, но оригинал его туда + * не пускает). Порядок ровно как в оригинале, не «сначала страж, потом + * заменим». */ + if (!follow && !pop_check_shadow(room)) + pop_guard_enter(room); /* страж комнаты (порт enter_guard) */ + pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */ + /* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ + * per-room (room_modif живёт между входами); рисуем и коллидируем по ним, + * а не по свежему backtable. Фаза P0. */ + room_mod = pop_trob_modif(room); + /* Модификаторы швов — из ЖИВОГО room_modif соседей (openness ворот, + * видимых через шов, персистентен: ворота room8(0,9) видны в col0 room6). + * pop_room_load заполнил lcol_bg/rcol_bg статикой уровня — перекрываем. */ + { + uint8_t lft = pop_room_link(room, 0), rgt = pop_room_link(room, 1); + if (lft) { uint8_t *m = pop_trob_modif(lft), i; + for (i = 0; i < 3; i++) { + lcol_bg[i] = m[9 + i * 10]; + /* шов рисуется в обе страницы ниже — синхр. сигнатуру */ + seam_sig[i] = seam_row_sig(m, lcol_fg, i); + } } + if (rgt) { uint8_t *m = pop_trob_modif(rgt); + rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; } + } + seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */ + /* ENTER-ROOM-FAST: комната рисуется ОДИН раз — в скрытую страницу, — и + * тут же показывается флипом; вторая страница дабл-буфера получает её + * КОПИЕЙ акселератора (gfx_copy_page, ~384 burst-строки против почти + * полумиллиона тактов на вторую отрисовку). Побочный эффект важнее + * тактов: игрок больше не видит, КАК рисуются тайлы — только готовый + * кадр. + * + * Копия идёт банком 0x50, то есть обновляет и VRAM, и ОЗУ-копию + * страницы-приёмника — иначе heal на ней восстанавливал бы фон прошлой + * комнаты (инвариант gfx_copy_page, регресс tests/pageflip). + * + * Видимая страница здесь МЕНЯЕТСЯ, поэтому главный цикл перечитывает её + * в начале каждого кадра (front = gfx_get_visible_page()), а не ведёт + * свой счёт. */ + { + gfx_rect_t full; + pg = (uint8_t)(gfx_get_visible_page() ^ 1); /* скрытая */ + gfx_set_draw_page(pg); + setfillstyle(SOLID_FILL, BLACK); + bar(0, 0, 319, 255); + pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg); + gfx_set_visible_page(pg); /* показать готовое */ + gfx_set_draw_page((uint8_t)(pg ^ 1)); + full.x = 0; full.y = 0; full.w = 320; full.h = 256; + gfx_copy_page(&full, GFX_COPY_DIRECT); /* вторую — копией */ + } + cur_room = room; + kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */ + pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */ +} + +/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход. + * Страж такой смены не «видит» — он остаётся в своей комнате. */ +void enter_room(uint8_t room) __banked { enter_room_side(room, 0xFF); } + +/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos + * (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и + * для запуска, и для рестарта после смерти / выпадения из уровня: в + * оригинале это буквально один и тот же путь (play_level_2 зовёт + * start_level заново), поэтому и у нас не три копии четырёх строк. + * + * Позиция берётся ИЗ ДАННЫХ уровня (pop_level_start_*), а не из #define: + * start_pos — тайл 0..29 (row*10+col), start_dir — направление, в котором + * персонаж должен ОКАЗАТЬСЯ. + * + * Направление ИНВЕРТИРУЕТСЯ (`Char.direction = ~level.start_dir`): персонаж + * ставится спиной и доворачивается кадрами позы входа. Сама поза — из + * tbl_entry_pose (data.h:848 = {0,1,0,…,2,…}): + * 1 (уровень 1) — падение внутрь (seq_7_fall) + нажатие кнопки room5(0,2); + * это оно захлопывает решётку за спиной в первой комнате; + * 2 (уровень 13) — вбегание (seq_84_run); + * иначе — разворот на месте (seq_5_turn). + * Таблица индексируется НОМЕРОМ УРОВНЯ — pop_level_entry_pose(). */ +#define SEQ_TURN 5 +#define SEQ_FALL 7 +#define SEQ_RUN 84 +#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */ +#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */ +#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */ + + /* ---- Порядок «Kid vs страж» (BUG-DRAWORDER-1) ----------------------- * * В оригинале никто не «поверх» по определению: и Кид, и страж попадают в diff --git a/applications/PoP/roomtest/roomtest_cold.h b/applications/PoP/roomtest/roomtest_cold.h index 4fbefe8..1798dd6 100644 --- a/applications/PoP/roomtest/roomtest_cold.h +++ b/applications/PoP/roomtest/roomtest_cold.h @@ -23,14 +23,30 @@ /* ---- то, что холодная половина берёт у главного цикла ---------------- */ -extern uint8_t room_fg[30]; /* тайлы ОТРИСОВАННОЙ комнаты (fg & 0x1F) */ +/* Рабочие массивы комнаты: заполняет вход в комнату (банк), читают и он, и + * главный цикл (шов, straddle, чит-навигация). Данные банковых модулей + * линкуются в общий _DATA, поэтому обе половины видят их как обычные + * глобалы — в банк уехал только КОД. */ +extern uint8_t room_fg[30], room_bg[30]; +extern uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11]; +extern uint8_t above_fg[10], above_mod[10]; +extern uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (straddle) */ +extern uint8_t seam_sig[3]; /* последняя нарисованная openness в шве */ +extern uint8_t seam_rows, seam_redraw; + +/* Сигнатура ряда шва: openness ворот соседа либо «кнопка нажата». Живёт в + * резиденте — её зовёт и кадровый путь главного цикла, и вход в комнату. */ +uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r); extern uint8_t cur_room; /* её номер (drawn_room) */ extern uint8_t pop_checkpoint; /* checkpoint (seg000:270) — ставит уход */ /* влево из комнаты 7 уровня 3 */ -/* Вход в комнату БЕЗ перехода за Кидом (старт уровня, респавн, чит-обход): - * страж такой смены не «видит» и остаётся в своей комнате. */ -void enter_room(uint8_t room); +/* Вход в комнату. side — куда ушёл КИД (0 L, 1 R, 2 U, 3 D); 0xFF = «не + * переход за Кидом» (старт уровня, респавн, чит-навигация): от этого + * зависит, пойдёт ли страж следом. enter_room — обёртка с 0xFF. + * Обе в банке 8: зовутся раз на комнату, а кода там на полтора килобайта. */ +void enter_room_side(uint8_t room, uint8_t side) __banked; +void enter_room(uint8_t room) __banked; /* ---- холодная половина ----------------------------------------------- */