From 24bb724c2260c8d3a5bc0b6314859d207d6d547b Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 29 Jul 2026 20:37:03 +0300 Subject: [PATCH] =?UTF-8?q?PoP=20roomtest=20(=D0=BF=D0=BB=D0=B0=D0=BD=20v2?= =?UTF-8?q?,=20=D1=84=D0=B0=D0=B7=D0=B0=201a):=20=D0=BF=D0=BE=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BA=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B8=D1=81?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B8=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=20=D0=BF=D1=80=D1=8F=D0=BC=D1=8B=D1=85=20=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Порт архитектуры оригинала: логика анимации тайлов НИЧЕГО не рисует, она ставит флаг (set_redraw_full/set_wipe/redraw_20h/redraw_21h, seg007), а отрисовка идёт отдельным проходом redraw_needed (seg008:0178). У нас появился pop_redraw.c/.h: pop_set_redraw(tilepos, вид, страницы) + pop_set_redraw_above(col, ...) + pop_redraw_needed(), который зовёт главный цикл в слое фона (до kid_draw). Отличие от оригинала (наша платформа): счётчик пометки — это ЧИСЛО СТРАНИЦ дабл-буфера (обычно 2), а вид перерисовки хранится явно (heal+поверх или запечь фон), потому что у нас у каждой страницы своя ОЗУ-копия фона. В оригинале вид кодируется тем, в какой из таблиц redraw_frames_* стоит флаг. pop_trob переведён на пометки: пики, кнопки, дверь уровня. Его самодельные массивы spike_rest/button_rest/ldoor_rest/rest_pending удалены — их роль теперь у счётчика страниц в pop_redraw. Прямыми вызовами pop_bg осталось только пламя факела и пузырёк зелья: это не тайловая перерисовка, а покадровый оверлей; из-за них pop_process_trobs остаётся единственной функцией модуля, которую нельзя звать из банка. Зачем: из __banked-кода резидентная страница W3 недостижима транзитивно (docs/layout_plan_v2.md §2 R2), поэтому логика, которую будут звать стражи, не должна вызывать pop_bg. Проверено в MAME: комната 6 — Kid на кнопке-открывалке, решётка в шве поднимается; сошёл с кнопки — закрывается; упал в шахту на пики — пики выдвинулись и отрисованы (кадр 177). Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/Makefile | 4 +- applications/PoP/roomtest/pop_redraw.c | 78 ++++++++++++++++++++++++ applications/PoP/roomtest/pop_redraw.h | 50 ++++++++++++++++ applications/PoP/roomtest/pop_trob.c | 83 +++++++------------------- applications/PoP/roomtest/roomtest.c | 7 ++- 5 files changed, 156 insertions(+), 66 deletions(-) create mode 100644 applications/PoP/roomtest/pop_redraw.c create mode 100644 applications/PoP/roomtest/pop_redraw.h diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index e8444cd..16c62b4 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -12,7 +12,7 @@ MEMORY ?= small # __banked-модулей (стражи): из банка резидентная страница W3 не видна — # ни напрямую, ни транзитивно. См. docs/layout_plan_v2.md §2 (R2), §4. EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c -EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c +EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c BG_DIR := $(CURDIR)/../poc/res/bg KID_DIR := $(CURDIR)/../poc/res/kid @@ -42,4 +42,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py cd $(TC) && python3 pop_extract_kid_data.py -$(EXAMPLE).exe: pop_bg.c pop_bg.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) +$(EXAMPLE).exe: pop_bg.c pop_bg.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) diff --git a/applications/PoP/roomtest/pop_redraw.c b/applications/PoP/roomtest/pop_redraw.c new file mode 100644 index 0000000..45af5fa --- /dev/null +++ b/applications/PoP/roomtest/pop_redraw.c @@ -0,0 +1,78 @@ +/* + * pop_redraw.c — пометки перерисовки тайлов и их разбор. См. pop_redraw.h. + * + * Живёт в W1/W2 (НЕ в резиденте W3): пометки ставит логика, которая должна + * оставаться достижимой из будущих __banked-модулей. Сам разбор зовёт + * pop_bg (резидент W3) — это законно, потому что разбор дёргает только + * главный цикл. + */ +#include +#include "pop_redraw.h" +#include "pop_bg.h" + +#define NTILES 30 +#define NCOLS 10 + +static uint8_t rd_kind[NTILES], rd_cnt[NTILES]; +static uint8_t rda_kind[NCOLS], rda_cnt[NCOLS]; +/* Сколько всего непустых пометок: без него проход по 40 ячейкам крутился бы + * КАЖДЫЙ кадр впустую (замер 2026-07-27 на прежнем rest-цикле: ~12 % кадра). */ +static uint8_t rd_pending; + +void pop_redraw_reset(void) +{ + uint8_t i; + for (i = 0; i < NTILES; i++) { rd_kind[i] = 0; rd_cnt[i] = 0; } + for (i = 0; i < NCOLS; i++) { rda_kind[i] = 0; rda_cnt[i] = 0; } + rd_pending = 0; +} + +void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) +{ + if (tilepos >= NTILES) return; + if (!rd_cnt[tilepos]) rd_pending++; + rd_kind[tilepos] = kind; + if (pages > rd_cnt[tilepos]) rd_cnt[tilepos] = pages; +} + +void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) +{ + if (col >= NCOLS) return; + if (!rda_cnt[col]) rd_pending++; + rda_kind[col] = kind; + if (pages > rda_cnt[col]) rda_cnt[col] = pages; +} + +/* redraw_needed (seg008:0178): пройти пометки и нарисовать. row/col ведём + * бегущими счётчиками, а не через i/10 и i%10 — у Z80 нет деления, и SDCC + * выносит __divuchar/__moduchar в вершину цикла как общее подвыражение + * аргументов (30*2 делений за кадр даже когда рисовать нечего). */ +void pop_redraw_needed(void) +{ + uint8_t i, row = 0, col = 0, left = 0; + if (!rd_pending) return; + for (i = 0; i < NTILES; i++) { + if (rd_cnt[i]) { + switch (rd_kind[i]) { + case POP_RD_SPIKE: pop_spike_redraw(row, col); break; + case POP_RD_LOOSE: pop_loose_shake_draw(row, col); break; + case POP_RD_LOOSE_GONE: pop_loose_bake_empty(row, col); break; + case POP_RD_FLOOR: pop_floor_bake(row, col); break; + case POP_RD_LEVELDOOR: pop_leveldoor_redraw(row, col); break; + default: break; + } + if (--rd_cnt[i]) left++; + else rd_kind[i] = 0; + } + if (++col == 10) { col = 0; row++; } + } + for (i = 0; i < NCOLS; i++) { + if (rda_cnt[i]) { + if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i); + else pop_ceil_bake_empty(i); + if (--rda_cnt[i]) left++; + else rda_kind[i] = 0; + } + } + rd_pending = left; +} diff --git a/applications/PoP/roomtest/pop_redraw.h b/applications/PoP/roomtest/pop_redraw.h new file mode 100644 index 0000000..cdad683 --- /dev/null +++ b/applications/PoP/roomtest/pop_redraw.h @@ -0,0 +1,50 @@ +/* + * pop_redraw.h — пометки «перерисовать тайл» и их разбор. + * + * Порт архитектуры оригинала: логика анимации НИЧЕГО не рисует, она ставит + * флаг (`set_redraw_full`/`set_wipe`/`redraw_20h`/`redraw_21h`, seg007), а + * отрисовка идёт отдельным проходом (`redraw_needed`, seg008:0178). У нас + * так же: pop_map/pop_trob зовут pop_set_redraw*, а pop_redraw_needed() + * (главный цикл) разбирает пометки и дёргает pop_bg. + * + * ЗАЧЕМ (кроме сходства с SDLPoP): из __banked-кода резидентная страница W3 + * недостижима — ни напрямую, ни транзитивно (docs/layout_plan_v2.md §2 R2). + * Пока pop_map звал pop_bg, его нельзя было использовать из банка стражей. + * Пометки живут в W1/W2 и зовутся откуда угодно. + * + * ОТЛИЧИЕ ОТ ОРИГИНАЛА: у нас не один экран с dirty-rect, а две страницы + * дабл-буфера со своей ОЗУ-копией фона, поэтому счётчик пометки — это ЧИСЛО + * СТРАНИЦ (обычно 2), а вид перерисовки хранится явно: heal+рисовать поверх + * (транзиент) или запечь новый фон. В оригинале вид кодируется тем, в какой + * из таблиц redraw_frames_* стоит флаг. + */ +#ifndef POP_REDRAW_H +#define POP_REDRAW_H + +#include + +/* Виды перерисовки тайла (row*10+col). */ +#define POP_RD_SPIKE 1 /* пики: heal + оба тайла (redraw_21h) */ +#define POP_RD_LOOSE 2 /* loose: дрожащий кадр / кадр покоя */ +#define POP_RD_LOOSE_GONE 3 /* loose провалился: запечь пусто */ +#define POP_RD_FLOOR 4 /* пол изменился: кнопка/щебень/поднятый меч */ +#define POP_RD_LEVELDOOR 5 /* створка двери уровня (ПРАВАЯ половина) */ + +/* Виды перерисовки полосы у потолка (по колонке). */ +#define POP_RDA_CEIL 1 /* плита-потолок: дрожащий кадр / покой */ +#define POP_RDA_CEIL_GONE 2 /* плита-потолок провалилась: колодец */ + +/* Пометить тайл/колонку: kind — вид, pages — на скольких страницах + * дабл-буфера перерисовать (1 = только текущая, 2 = обе). Повторная + * пометка перекрывает вид и берёт максимум счётчика. */ +void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages); +void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages); + +/* Разобрать пометки: нарисовать всё помеченное на ТЕКУЩЕЙ (back) странице и + * уменьшить счётчики. Звать из главного цикла в слое ФОНА — до kid_draw. */ +void pop_redraw_needed(void); + +/* Сбросить все пометки (смена комнаты/уровня). */ +void pop_redraw_reset(void); + +#endif diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index dea400e..8c01350 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -5,7 +5,13 @@ */ #include "pop_trob.h" #include "pop_level.h" -#include "pop_bg.h" /* pop_spike_redraw */ +#include "pop_redraw.h" /* пометки перерисовки тайлов (вместо прямых вызовов) */ +/* pop_bg нужен ТОЛЬКО для пламени факела и пузырька зелья: это не тайловая + * перерисовка, а покадровый оверлей поверх фона (в оригинале — свои + * add_backtable/add_foretable внутри draw_tile). Всё остальное идёт через + * пометки, поэтому pop_process_trobs — единственная функция модуля, которую + * нельзя звать из __banked-кода. */ +#include "pop_bg.h" /* Коды тайлов (fg & 0x1F), которые анимируются. */ #define TILE_SPIKE 0x02 @@ -30,11 +36,6 @@ static trob_t trobs[TROBS_MAX]; static uint8_t trobs_count; static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */ -/* Отложенная перерисовка «покоя» законченных пик/кнопок текущей комнаты - * (2 кадра = обе страницы дабл-буфера, как loose_rest). */ -static uint8_t spike_rest[POP_ROOMTILES]; -static uint8_t button_rest[POP_ROOMTILES]; - /* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button: * признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её * изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под @@ -43,18 +44,6 @@ static uint8_t button_rest[POP_ROOMTILES]; * расхождение было именно в безусловном редрое). */ static uint8_t trob_drawn[POP_ROOMTILES]; -/* Сколько тайлов ждут отложенного редроя (spike_rest/button_rest). Без - * него цикл по 30 тайлам крутился КАЖДЫЙ кадр впустую: замер в MAME - * (2026-07-27) — служебная часть pop_process_trobs ~50 000 тактов при - * кадре 430 080, то есть ~12 % кадра на пустой проход. */ -static uint8_t rest_pending; - -/* Дверь уровня: отложенный редрой ВТОРОЙ страницы дабл-буфера. Створка - * анимируется покадрово в back-страницу, поэтому последний кадр анимации - * попадает только на одну из двух страниц — вторая застревает на шаг раньше - * и дверь «дрожит» через кадр. ldoor_pos = tilepos ПРАВОЙ половины. */ -static uint8_t ldoor_rest, ldoor_pos; - /* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и * выбора следующего кадра пламени. */ static unsigned long trob_seed; @@ -70,9 +59,8 @@ void pop_trob_reset(void) { uint8_t i; for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0; - for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; } - rest_pending = 0; - ldoor_rest = 0; + for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0; + pop_redraw_reset(); trobs_count = 0; } @@ -116,9 +104,8 @@ void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type) void pop_trob_room_changed(void) { uint8_t i; - for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; } - rest_pending = 0; - ldoor_rest = 0; + for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0; + pop_redraw_reset(); } /* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её @@ -373,26 +360,6 @@ void pop_process_trobs(uint8_t cur_room) * выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее * подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет * активных пик/кнопок (тот же случай, что был в pop_loose_tick). */ - if (rest_pending) { - uint8_t row = 0, col = 0, left = 0; - for (i = 0; i < POP_ROOMTILES; i++) { - if (spike_rest[i]) { - pop_spike_redraw(row, col); - if (--spike_rest[i]) left++; - } - if (button_rest[i]) { - pop_button_redraw(row, col); - if (--button_rest[i]) left++; - } - if (++col == 10) { col = 0; row++; } - } - rest_pending = left; - } - if (ldoor_rest) { /* доложить финальный кадр створки на 2-ю страницу */ - pop_leveldoor_redraw(ldoor_pos / 10, ldoor_pos % 10); - ldoor_rest--; - } - if (trobs_count == 0) return; /* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не @@ -477,24 +444,20 @@ void pop_process_trobs(uint8_t cur_room) * неизменной сигнатурой, и стёртое уже не возвращается: рядом с * Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной * странице дабл-буфера из двух). */ - uint8_t trow = tp / 10, tcol = tp % 10; - pop_spike_redraw(trow, tcol); - /* Последний кадр (пика убралась, trob умирает) — на ВТОРУЮ страницу - * его уже никто не положит: ставим отложенный редрой. */ - if (type < 0) { spike_rest[tp] = 1; rest_pending++; } + /* Последний кадр (пика убралась, trob умирает) — пометить ОБЕ + * страницы: живой trob метит каждый кадр по одной. */ + pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1)); } if (room == cur_room && code == TILE_LEVELDOOR) { /* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там * её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе * kid_heal вернул бы из фона закрытую створку. Куски двери * непрозрачны, поэтому wipe не нужен. */ - uint8_t trow = tp / 10, tcol = tp % 10; - if (tcol + 1 < 10) { - pop_leveldoor_redraw(trow, tcol + 1); - /* Анимация кончилась — этот кадр лёг только на ОДНУ страницу; - * повторить его на второй, иначе дверь дрожит через кадр. */ - if (type < 0) { ldoor_pos = (uint8_t)(tp + 1); ldoor_rest = 1; } - } + /* Створку рисует ПРАВАЯ половина двери; последний кадр анимации + * помечаем на обе страницы, иначе дверь дрожит через кадр. */ + if (tp % 10 + 1 < 10) + pop_set_redraw((uint8_t)(tp + 1), POP_RD_LEVELDOOR, + (uint8_t)(type < 0 ? 2 : 1)); } if (room == cur_room && code == TILE_SWORD) { /* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1): @@ -503,9 +466,7 @@ void pop_process_trobs(uint8_t cur_room) * следующим кадром через rest-цикл. */ uint8_t sig = (uint8_t)(mod[tp] == 1); if (sig != trob_drawn[tp]) { - uint8_t trow = tp / 10, tcol = tp % 10; - pop_button_redraw(trow, tcol); - button_rest[tp] = 1; rest_pending++; + pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */ trob_drawn[tp] = sig; } } @@ -517,9 +478,7 @@ void pop_process_trobs(uint8_t cur_room) * считаем только в момент редроя (редко), а не каждый кадр. */ uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1); if (sig != trob_drawn[tp]) { - uint8_t trow = tp / 10, tcol = tp % 10; - pop_button_redraw(trow, tcol); - button_rest[tp] = 1; rest_pending++; + pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */ trob_drawn[tp] = sig; } } diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 3df6f1d..ec00f53 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -15,6 +15,7 @@ #include "pop_map.h" #include "pop_level.h" #include "pop_trob.h" +#include "pop_redraw.h" __sfr __at (0xFE) io_border; @@ -343,8 +344,10 @@ int main(void) pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */ /* — ДО Kid, чтобы Kid всегда был поверх плиты) */ io_border = 6; /* голубой */ - pop_process_trobs(cur_room); /* пики/ворота: продвинуть + перерисовать (тоже */ - /* слой фона — ДО Kid) */ + pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */ + /* process_trobs — рисования внутри нет) */ + pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */ + /* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */ io_border = 4; /* синий */ { /* ворота соседа слева (room8) видны в col0 — */ uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */