2 Commits

Author SHA1 Message Date
snark13 4cffbc9aa4 PoP roomtest (план v2, фаза 1b): pop_map переведён на пометки перерисовки
Loose-полы, плита-потолок и щебень от приземления больше не зовут pop_bg —
ставят пометки (pop_set_redraw / pop_set_redraw_above), которые разбирает
pop_redraw_needed из главного цикла.  Удалены самодельные счётчики
loose_bake/loose_rest/ceil_rest/ceil_bake/land_bake: их роль (вторая
страница дабл-буфера) теперь у счётчика страниц в пометке.

Осталось ОДНО исключение: падающий кусок (mob) — spawn/tick/pos.  Это
движущийся ОБЪЕКТ, а не перерисовка тайла, и в оригинале он живёт отдельно
(mobs + draw_moving), поэтому разделение его на логику и отрисовку —
следующая фаза.  Из-за него pop_loose_tick остаётся единственной функцией
pop_map, которую нельзя звать из __banked-кода; вся коллизия, физика,
кромки и предметы — то, что понадобится стражам — чисты от графики.

Замер: _CODE 24718 -> 24462, _DATA 4301 -> 4219 (ушли rest-массивы).

Проверено в MAME: комната 12, осторожный шаг на плиту — тряска, падение
плиты, Kid проваливается на ряд 1, дыра и щебень отрисованы; на
замороженном кадре обе страницы дабл-буфера побайтово совпадают в области
изменений (7 строк).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:44:56 +03:00
snark13 24bb724c22 PoP roomtest (план v2, фаза 1a): пометки перерисовки вместо прямых блитов
Порт архитектуры оригинала: логика анимации тайлов НИЧЕГО не рисует, она
ставит флаг (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 <noreply@anthropic.com>
2026-07-29 20:37:03 +03:00
6 changed files with 187 additions and 124 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ MEMORY ?= small
# __banked-модулей (стражи): из банка резидентная страница W3 не видна — # __banked-модулей (стражи): из банка резидентная страница W3 не видна —
# ни напрямую, ни транзитивно. См. docs/layout_plan_v2.md §2 (R2), §4. # ни напрямую, ни транзитивно. См. docs/layout_plan_v2.md §2 (R2), §4.
EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c 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 BG_DIR := $(CURDIR)/../poc/res/bg
KID_DIR := $(CURDIR)/../poc/res/kid 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 kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
cd $(TC) && python3 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)
+30 -57
View File
@@ -19,7 +19,14 @@
#include "pop_kid.h" #include "pop_kid.h"
#include "pop_map.h" #include "pop_map.h"
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */ #include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
#include "pop_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */ #include "pop_redraw.h" /* пометки перерисовки (порт set_redraw_*) */
/* pop_bg нужен ТОЛЬКО падающему куску (mob): spawn/tick/pos — это движущийся
* ОБЪЕКТ, а не перерисовка тайла (в оригинале он и живёт отдельно: mobs +
* draw_moving). Из-за него pop_loose_tick — единственная функция модуля,
* которую нельзя звать из __banked-кода; вся коллизия/физика/предметы, то
* есть то, что понадобится стражам, чиста от графики. Разделение mob на
* логику и отрисовку — задел следующей фазы. */
#include "pop_bg.h"
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */ #include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */ /* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
@@ -142,11 +149,9 @@ static uint8_t g_lcol[6], g_rcol[6];
* отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. — * отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. —
* тряска, 1..11 — отсчёт до падения). */ * тряска, 1..11 — отсчёт до падения). */
uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */ uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */
static uint8_t ceil_rest[10]; /* «перерисовать ПОКОЙ» (2 = обе страницы) */
static uint8_t ceil_bake[10]; /* «запечь колодец» на второй странице */
static uint8_t land_bake, land_bake_pos; /* щебень от приземления — вторая страница */
static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */ static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */
uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */ uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */
uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */ uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */
void pop_map_set_above(uint8_t *above_row2_fg) { g_above = above_row2_fg; } void pop_map_set_above(uint8_t *above_row2_fg) { g_above = above_row2_fg; }
@@ -1027,8 +1032,6 @@ static void check_bumped(void)
* отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/ * отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/
* make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */ * make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */
uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */ uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */
static uint8_t loose_bake[30]; /* счётчик «запечь пусто» (2 = обе страницы) */
static uint8_t loose_rest[30]; /* счётчик «перерисовать ПОКОЙ» после тряски (2 = обе стр.) */
/* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */ /* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */
@@ -1131,18 +1134,13 @@ void pop_loose_reset(void)
int i; int i;
for (i = 0; i < 30; i++) { for (i = 0; i < 30; i++) {
pop_loose_modif[i] = 0; pop_loose_modif[i] = 0;
loose_bake[i] = 0;
loose_rest[i] = 0;
} }
for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) {
pop_ceil_modif[i] = 0; pop_ceil_modif[i] = 0;
ceil_rest[i] = 0;
ceil_bake[i] = 0;
} }
pop_ceil_fell = 0; pop_ceil_fell = 0;
pop_debris_at = 0; pop_debris_at = 0;
pop_loose_landed = 0; pop_loose_landed = 0;
land_bake = 0;
pop_loose_mob_reset(); pop_loose_mob_reset();
} }
@@ -1201,24 +1199,17 @@ void pop_loose_tick(void)
* Инкремент+сравнение считают row/col без деления. */ * Инкремент+сравнение считают row/col без деления. */
uint8_t row = 0, col = 0; uint8_t row = 0, col = 0;
for (pos = 0; pos < 30; pos++) { for (pos = 0; pos < 30; pos++) {
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */ if (pop_loose_modif[pos] != 0) {
pop_loose_bake_empty(row, col);
loose_bake[pos]--;
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
* страницы дабл-буфера — иначе на одной остаётся застывший
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
pop_loose_shake_draw(row, col);
loose_rest[pos]--;
} else if (pop_loose_modif[pos] != 0) {
uint8_t m = ++pop_loose_modif[pos]; uint8_t m = ++pop_loose_modif[pos];
if (m & 0x80) { /* тряска (do_knock) */ if (m & 0x80) { /* тряска (do_knock) */
if (m >= 0x84) { /* конец тряски: сброс + перерисовать покой */ if (m >= 0x84) { /* конец тряски: сброс + покой */
pop_loose_modif[pos] = 0; pop_loose_modif[pos] = 0;
loose_rest[pos] = 2; /* покой на ОБЕИХ страницах */ /* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
* правая грань loose живёт в соседе). ОБЕ страницы —
* иначе на одной застывает дрожащий кадр → мерцание. */
pop_set_redraw(pos, POP_RD_LOOSE, 2);
} else { } else {
pop_loose_shake_draw(row, col); /* дрожащий кадр */ pop_set_redraw(pos, POP_RD_LOOSE, 1); /* дрожащий кадр */
} }
} else if (m >= 11) { /* loose_floor_delay — падение */ } else if (m >= 11) { /* loose_floor_delay — падение */
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!). /* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
@@ -1228,12 +1219,11 @@ void pop_loose_tick(void)
* (стена-вниз от тайла снизу + чёрный фон). */ * (стена-вниз от тайла снизу + чёрный фон). */
g_fg[pos] = TILE_EMPTY; g_fg[pos] = TILE_EMPTY;
pop_loose_modif[pos] = 0; pop_loose_modif[pos] = 0;
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */ pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
loose_bake[pos] = 1; /* + вторая страница следующим кадром */ pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
pop_loose_mob_spawn(row, col); /* отрыв падающего куска (L4) */ pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению */
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
} else { /* отсчёт 1..10 — дрожащий кадр */ } else { /* отсчёт 1..10 — дрожащий кадр */
pop_loose_shake_draw(row, col); pop_set_redraw(pos, POP_RD_LOOSE, 1);
} }
} }
if (++col == 10) { col = 0; row++; } /* row/col без деления */ if (++col == 10) { col = 0; row++; } /* row/col без деления */
@@ -1242,27 +1232,21 @@ void pop_loose_tick(void)
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в * рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */ * НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
for (col = 0; col < 10; col++) { for (col = 0; col < 10; col++) {
if (ceil_rest[col]) { if (pop_ceil_modif[col] != 0) {
pop_ceil_shake_draw(col); /* modif уже 0 → кадр покоя */
ceil_rest[col]--;
} else if (pop_ceil_modif[col] != 0) {
uint8_t m = ++pop_ceil_modif[col]; uint8_t m = ++pop_ceil_modif[col];
if (m & 0x80) { /* тряска от сотрясения (do_knock) */ if (m & 0x80) { /* тряска от сотрясения (do_knock) */
if (m >= 0x84) { pop_ceil_modif[col] = 0; ceil_rest[col] = 2; } if (m >= 0x84) { pop_ceil_modif[col] = 0;
else pop_ceil_shake_draw(col); pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
} else if (m >= 11) { /* loose_floor_delay — плита рушится */ } else if (m >= 11) { /* loose_floor_delay — плита рушится */
pop_ceil_modif[col] = 0; pop_ceil_modif[col] = 0;
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */ if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
pop_ceil_bake_empty(col); /* колодец наверх на ТЕКУЩЕЙ странице */ pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */
ceil_bake[col] = 1; /* + вторая страница следующим кадром */
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */ pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */ pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
} else { } else {
pop_ceil_shake_draw(col); /* отсчёт 1..10 — дрожащий кадр */ pop_set_redraw_above(col, POP_RDA_CEIL, 1); /* отсчёт 1..10 */
} }
} else if (ceil_bake[col]) {
pop_ceil_bake_empty(col);
ceil_bake[col]--;
} }
} }
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */ pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
@@ -1270,29 +1254,18 @@ void pop_loose_tick(void)
/* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД /* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД
* разбором нового: иначе при двух плитах, севших подряд, land_bake * разбором нового: иначе при двух плитах, севших подряд, land_bake
* затирался новым и на одной странице оставался старый пол. */ * затирался новым и на одной странице оставался старый пол. */
if (land_bake && pop_loose_landed) {
uint8_t r = 0, c = land_bake_pos;
while (c >= 10) { c -= 10; r++; }
pop_floor_bake(r, c);
land_bake = 0;
}
if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */ if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */
uint8_t pos = (uint8_t)(pop_loose_landed - 1); uint8_t pos = (uint8_t)(pop_loose_landed - 1);
uint8_t r = 0, c = pos; uint8_t r = 0, c = pos;
pop_loose_landed = 0; pop_loose_landed = 0;
while (c >= 10) { c -= 10; r++; } while (c >= 10) { c -= 10; r++; }
g_fg[pos] = TILE_DEBRIS; g_fg[pos] = TILE_DEBRIS;
/* Перерисовка ПО ПЛОЩАДИ ПОЛА (не bake_empty: та стирает и верх стены /* Перерисовка ПО ПЛОЩАДИ ПОЛА (не LOOSE_GONE: та стирает и верх стены
* ряда ниже — оставался чёрный прямоугольник под тайлом). */ * ряда ниже — оставался чёрный прямоугольник под тайлом). Обе
pop_floor_bake(r, c); * страницы дабл-буфера. */
land_bake_pos = pos; land_bake = 1; /* вторая страница дабл-буфера */ pop_set_redraw(pos, POP_RD_FLOOR, 2);
do_knock(r); /* удар плиты трясёт loose этого ряда */ do_knock(r); /* удар плиты трясёт loose этого ряда */
pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */ pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */
} else if (land_bake) {
uint8_t r = 0, c = land_bake_pos;
while (c >= 10) { c -= 10; r++; }
pop_floor_bake(r, c);
land_bake = 0;
} }
} }
+78
View File
@@ -0,0 +1,78 @@
/*
* pop_redraw.c — пометки перерисовки тайлов и их разбор. См. pop_redraw.h.
*
* Живёт в W1/W2 (НЕ в резиденте W3): пометки ставит логика, которая должна
* оставаться достижимой из будущих __banked-модулей. Сам разбор зовёт
* pop_bg (резидент W3) — это законно, потому что разбор дёргает только
* главный цикл.
*/
#include <stdint.h>
#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;
}
+50
View File
@@ -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 <stdint.h>
/* Виды перерисовки тайла (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
+21 -62
View File
@@ -5,7 +5,13 @@
*/ */
#include "pop_trob.h" #include "pop_trob.h"
#include "pop_level.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), которые анимируются. */ /* Коды тайлов (fg & 0x1F), которые анимируются. */
#define TILE_SPIKE 0x02 #define TILE_SPIKE 0x02
@@ -30,11 +36,6 @@ static trob_t trobs[TROBS_MAX];
static uint8_t trobs_count; static uint8_t trobs_count;
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */ 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: /* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её * признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
* изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под * изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под
@@ -43,18 +44,6 @@ static uint8_t button_rest[POP_ROOMTILES];
* расхождение было именно в безусловном редрое). */ * расхождение было именно в безусловном редрое). */
static uint8_t trob_drawn[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) — нужен для стартовой фазы факела и /* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
* выбора следующего кадра пламени. */ * выбора следующего кадра пламени. */
static unsigned long trob_seed; static unsigned long trob_seed;
@@ -70,9 +59,8 @@ void pop_trob_reset(void)
{ {
uint8_t i; uint8_t i;
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0; 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; } for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
rest_pending = 0; pop_redraw_reset();
ldoor_rest = 0;
trobs_count = 0; 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) void pop_trob_room_changed(void)
{ {
uint8_t i; uint8_t i;
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; } for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
rest_pending = 0; pop_redraw_reset();
ldoor_rest = 0;
} }
/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её /* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
@@ -373,26 +360,6 @@ void pop_process_trobs(uint8_t cur_room)
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее * выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет * подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */ * активных пик/кнопок (тот же случай, что был в 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; if (trobs_count == 0) return;
/* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не /* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
@@ -477,24 +444,20 @@ void pop_process_trobs(uint8_t cur_room)
* неизменной сигнатурой, и стёртое уже не возвращается: рядом с * неизменной сигнатурой, и стёртое уже не возвращается: рядом с
* Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной * Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной
* странице дабл-буфера из двух). */ * странице дабл-буфера из двух). */
uint8_t trow = tp / 10, tcol = tp % 10; /* Последний кадр (пика убралась, trob умирает) — пометить ОБЕ
pop_spike_redraw(trow, tcol); * страницы: живой trob метит каждый кадр по одной. */
/* Последний кадр (пика убралась, trob умирает) — на ВТОРУЮ страницу pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1));
* его уже никто не положит: ставим отложенный редрой. */
if (type < 0) { spike_rest[tp] = 1; rest_pending++; }
} }
if (room == cur_room && code == TILE_LEVELDOOR) { if (room == cur_room && code == TILE_LEVELDOOR) {
/* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там /* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
* её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе * её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
* kid_heal вернул бы из фона закрытую створку. Куски двери * kid_heal вернул бы из фона закрытую створку. Куски двери
* непрозрачны, поэтому wipe не нужен. */ * непрозрачны, поэтому wipe не нужен. */
uint8_t trow = tp / 10, tcol = tp % 10; /* Створку рисует ПРАВАЯ половина двери; последний кадр анимации
if (tcol + 1 < 10) { * помечаем на обе страницы, иначе дверь дрожит через кадр. */
pop_leveldoor_redraw(trow, tcol + 1); if (tp % 10 + 1 < 10)
/* Анимация кончилась — этот кадр лёг только на ОДНУ страницу; pop_set_redraw((uint8_t)(tp + 1), POP_RD_LEVELDOOR,
* повторить его на второй, иначе дверь дрожит через кадр. */ (uint8_t)(type < 0 ? 2 : 1));
if (type < 0) { ldoor_pos = (uint8_t)(tp + 1); ldoor_rest = 1; }
}
} }
if (room == cur_room && code == TILE_SWORD) { if (room == cur_room && code == TILE_SWORD) {
/* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1): /* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1):
@@ -503,9 +466,7 @@ void pop_process_trobs(uint8_t cur_room)
* следующим кадром через rest-цикл. */ * следующим кадром через rest-цикл. */
uint8_t sig = (uint8_t)(mod[tp] == 1); uint8_t sig = (uint8_t)(mod[tp] == 1);
if (sig != trob_drawn[tp]) { if (sig != trob_drawn[tp]) {
uint8_t trow = tp / 10, tcol = tp % 10; pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */
pop_button_redraw(trow, tcol);
button_rest[tp] = 1; rest_pending++;
trob_drawn[tp] = sig; 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); uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
if (sig != trob_drawn[tp]) { if (sig != trob_drawn[tp]) {
uint8_t trow = tp / 10, tcol = tp % 10; pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */
pop_button_redraw(trow, tcol);
button_rest[tp] = 1; rest_pending++;
trob_drawn[tp] = sig; trob_drawn[tp] = sig;
} }
} }
+5 -2
View File
@@ -15,6 +15,7 @@
#include "pop_map.h" #include "pop_map.h"
#include "pop_level.h" #include "pop_level.h"
#include "pop_trob.h" #include "pop_trob.h"
#include "pop_redraw.h"
__sfr __at (0xFE) io_border; __sfr __at (0xFE) io_border;
@@ -343,8 +344,10 @@ int main(void)
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */ pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */ /* — ДО Kid, чтобы Kid всегда был поверх плиты) */
io_border = 6; /* голубой */ io_border = 6; /* голубой */
pop_process_trobs(cur_room); /* пики/ворота: продвинуть + перерисовать (тоже */ pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */
/* слой фона — ДО Kid) */ /* process_trobs — рисования внутри нет) */
pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */
/* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */
io_border = 4; /* синий */ io_border = 4; /* синий */
{ /* ворота соседа слева (room8) видны в col0 — */ { /* ворота соседа слева (room8) видны в col0 — */
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */ uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */