diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index 19edf14..df85823 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -256,6 +256,32 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS. Осознанное расхождение: мигания Кида спрайтами тени во время вспышки нет — запись в [`../docs/impl_diff.md`](../docs/impl_diff.md). + +### LOOSE-SHAKE-RUNS. Дрожание плит: пометки по сменам кадра, а не по кадрам + +**Идея пользователя 2026-08-13, на этап полиша.** + +`pop_loose_tick` на КАЖДОМ кадре отсчёта ставит `pop_set_redraw(pos, +POP_RD_LOOSE, 1)` — полную перерисовку тайла, десять раз за отсчёт. А +кадр дрожания меняется не каждую фазу: таблицы +(`POP_LOOSE_FRAM_BOTTOM` = 43,73,43,74,74,43,43,43,74,74,74) дают + + фаза 1 2 3 4 5 6 7 8 9 10 + кадр 73 43 74 74 43 43 43 74 74 74 + ^^^^^ ^^^^^^^^ ^^^^^^^^ + парами и тройками одно и то же + +С фазы 5 кадры идут ТРОЙКАМИ. Значит вместо шести пометок с `pages = 1` +хватит двух — на фазах 5 и 8 — но с `pages = 2` (обе страницы дабл-буфера, +иначе на второй застынет старый спрайт и пойдёт мерцание через кадр). + +**Выигрыш ~20 % работы зелёного блока и ТОЛЬКО на дрожащих плитах.** +Наивный вариант «ставить лишь на смене кадра» выигрыша НЕ даёт: пять смен +по две страницы — те же десять перерисовок (проверено арифметикой +2026-08-13, до правки). + +Вопрос к этапу полиша: стоит ли неочевидный код такого узкого выигрыша. + ### BG-ONCE. Задний фон рисуется ОДИН раз; вместо чёрных баров — heal diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 6568637..6a7dbe5 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -2494,7 +2494,13 @@ static void check_loose_fall_on_kid(void) * (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */ void pop_loose_tick(void) __banked { - int pos; + uint8_t pos; + /* Массивы обходим УКАЗАТЕЛЕМ, а не индексом. С `int pos` SDCC держал + * счётчик в IX-фрейме, каждую итерацию заново складывал 16-битный адрес + * элемента, клал его в локал и тут же вычитывал обратно парами pop/push: + * 40 холостых итераций стоили 27 936 тактов (замер 2026-08-13). Ходьба + * указателем убирает и адресную арифметику, и IX-фрейм. */ + uint8_t *lm = pop_loose_modif; /* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80 * нет аппаратного деления, и SDCC выносил оба деления (__divuchar/ * __moduchar) в начало КАЖДОЙ итерации (общее подвыражение аргументов @@ -2502,14 +2508,15 @@ void pop_loose_tick(void) __banked * комнатах без loose-плит (условия if к тому моменту ещё не проверены). * Инкремент+сравнение считают row/col без деления. */ uint8_t row = 0, col = 0; - for (pos = 0; pos < 30; pos++) { - if (pop_loose_modif[pos] != 0) { - uint8_t m = ++pop_loose_modif[pos]; + pop_dbg_m9(); /* ЗАМЕР: вход pop_loose_tick */ + for (pos = 0; pos < 30; pos++, lm++) { + if (*lm != 0) { + uint8_t m = ++*lm; /* Отложенный старт уровня 13 досчитал до нуля. Ноль у нас * означает «не анимируется», и плита застряла бы навсегда — * перескакиваем на 1. Стартовое значение выбрано с учётом * этого перескока (см. pop_check_fall_flo). */ - if (m == 0) m = pop_loose_modif[pos] = 1; + if (m == 0) m = *lm = 1; if (m & 0x80) { /* тряска (do_knock) */ /* СПЕЦКЕЙС УРОВНЯ 13 (seg007:823): фазу со старшим битом НЕ * гасим и не трясём. На нём check_fall_flo раздаёт плитам @@ -2521,7 +2528,7 @@ void pop_loose_tick(void) __banked if (pop_current_level == POP_LOOSE_TILES_LEVEL) { /* ждём молча */ } else if (m >= 0x84) { /* конец тряски: сброс + покой */ - pop_loose_modif[pos] = 0; + *lm = 0; /* modif уже 0 → кадр покоя обоих тайлов (col и col+1: * правая грань loose живёт в соседе). ОБЕ страницы — * иначе на одной застывает дрожащий кадр → мерцание. */ @@ -2535,7 +2542,7 @@ void pop_loose_tick(void) __banked * Иначе из двух плит, провалившихся в одном кадре, дыру * запекала бы только вторая, а первая оставалась нарисованной * целой (уровень 13: check_fall_flo роняет гряду разом). */ - pop_loose_modif[pos] = 10; + *lm = 10; } else if (m >= 11) { /* loose_floor_delay — падение */ /* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!). * Debris появляется ТОЛЬКО там, где приземлился падающий @@ -2547,7 +2554,7 @@ void pop_loose_tick(void) __banked * его модификатором пустой клетки (seg007:846/1083): по нему * draw_tile_right выбирает blueline_fram1 у дыры. */ pop_trob_modif(g_room)[pos] = pop_palace; - pop_loose_modif[pos] = 0; + *lm = 0; pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */ /* ...и СОСЕДА СПРАВА: передний торец пола заезжает в его * клетку, и без этой пометки он оставался висеть над пустым @@ -2565,10 +2572,11 @@ void pop_loose_tick(void) __banked /* Плиты-ПОТОЛКИ (ряд 2 комнаты сверху): тот же animate_loose, но кадры * рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в * НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */ - for (col = 0; col < 10; col++) { - if (pop_ceil_modif[col] != 0) { - uint8_t m = ++pop_ceil_modif[col]; - if (m == 0) m = pop_ceil_modif[col] = 1; /* см. pop_loose_tick выше */ + lm = pop_ceil_modif; /* тот же указательный обход */ + for (col = 0; col < 10; col++, lm++) { + if (*lm != 0) { + uint8_t m = ++*lm; + if (m == 0) m = *lm = 1; /* см. pop_loose_tick выше */ if (m & 0x80) { /* тряска от сотрясения (do_knock) */ /* Тот же спецкейс уровня 13, и здесь он ГЛАВНЫЙ: плиты, * которым check_fall_flo раздал отложенный старт, лежат @@ -2576,13 +2584,13 @@ void pop_loose_tick(void) __banked * цикл, а не соседний. */ if (pop_current_level == POP_LOOSE_TILES_LEVEL) { /* ждём молча */ - } else if (m >= 0x84) { pop_ceil_modif[col] = 0; + } else if (m >= 0x84) { *lm = 0; pop_set_redraw_above(col, POP_RDA_CEIL, 2); } else pop_set_redraw_above(col, POP_RDA_CEIL, 1); } else if (m >= 11 && pop_ceil_fell) { - pop_ceil_modif[col] = 10; /* сигнал занят — кадр подождём */ + *lm = 10; /* сигнал занят — кадр подождём */ } else if (m >= 11) { /* loose_floor_delay — плита рушится */ - pop_ceil_modif[col] = 0; + *lm = 0; if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */ pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */ pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */ @@ -2592,8 +2600,11 @@ void pop_loose_tick(void) __banked } } } + pop_dbg_m10(); /* ЗАМЕР: оба цикла по тайлам пройдены */ pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */ + pop_dbg_m11(); /* ЗАМЕР: pop_loose_mob_tick сделан */ check_loose_fall_on_kid(); /* seg007:1192 — плита падает Киду на голову */ + pop_dbg_m12(); /* ЗАМЕР: check_loose_fall_on_kid сделан */ /* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД * разбором нового: иначе при двух плитах, севших подряд, land_bake * затирался новым и на одной странице оставался старый пол. */ diff --git a/applications/PoP/roomtest/pop_redraw.c b/applications/PoP/roomtest/pop_redraw.c index 8d0c308..6bbd9da 100644 --- a/applications/PoP/roomtest/pop_redraw.c +++ b/applications/PoP/roomtest/pop_redraw.c @@ -9,6 +9,7 @@ #include #include "pop_redraw.h" #include "pop_bg.h" +#include "pop_state.h" /* ВРЕМЕННО: счётчики состава перерисовок */ #define NTILES 30 #define NCOLS 10 @@ -79,9 +80,13 @@ void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked void pop_redraw_needed(void) __banked { uint8_t i, row = 0, col = 0, left = 0; + uint8_t cnt[10], tot = 0; /* ВРЕМЕННО: состав кадра */ if (!rd_pending) return; + for (i = 0; i < 10; i++) cnt[i] = 0; for (i = 0; i < NTILES; i++) { if (rd_cnt[i]) { + if (rd_kind[i] < 8) { cnt[rd_kind[i]]++; tot++; } /* ВРЕМЕННО */ + pop_dbg_kind(rd_kind[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; @@ -92,6 +97,7 @@ void pop_redraw_needed(void) __banked case POP_RD_CHOMP: pop_chomp_redraw(row, col); break; default: break; } + pop_dbg_m16(); /* ВРЕМЕННО */ if (--rd_cnt[i]) left++; else rd_kind[i] = 0; } @@ -99,11 +105,18 @@ void pop_redraw_needed(void) __banked } for (i = 0; i < NCOLS; i++) { if (rda_cnt[i]) { + cnt[8 + (rda_kind[i] == POP_RDA_CEIL ? 0 : 1)]++; tot++; /* ВРЕМЕННО */ + pop_dbg_kind((uint8_t)(8 + rda_kind[i])); /* ВРЕМЕННО */ if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i); else pop_ceil_bake_empty(i); + pop_dbg_m16(); /* ВРЕМЕННО */ if (--rda_cnt[i]) left++; else rda_kind[i] = 0; } } + if (tot > pop_dbg_rdmax_tot) { /* ВРЕМЕННО: расклад пика */ + pop_dbg_rdmax_tot = tot; + for (i = 0; i < 10; i++) pop_dbg_rdmax[i] = cnt[i]; + } rd_pending = left; } diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index 251d429..0b69c2e 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -570,16 +570,31 @@ void pop_room_redraw_seam_left(uint8_t rows) __banked * shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту * после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */ +/* Полоса кладки у потолка: дрожащая плита ряда −1 — порт redraw_needed_above + * (seg008:0221). + * + * Оригинал рисует ТОЛЬКО СВОЮ колонку: кусок, свисающий вправо, кладёт её же + * draw_tile_right. Здесь дополнительно звался `draw_tile(-1, col + 1)` — + * целый соседний тайл, — и это удваивало работу. Замер 2026-08-13: пик + * зелёного блока 887 000 тактов при кадровом интервале 430 000 состоял ровно + * из шести вызовов этой функции, то есть двенадцати отрисовок тайла. + * + * Батчинг смежных колонок (один heal на диапазон) пробовался и убран: плиты + * стартуют со случайными задержками (check_fall_flo), в кадре дрожат + * разрозненные колонки, пробег почти всегда длиной в одну — механика не + * окупалась, но крутилась каждый кадр. */ void pop_ceil_shake_draw(int col) __banked { int x = POP_COL_XH[col] * 8; - pop_heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */ + pop_dbg_m13(); /* ЗАМЕР: вход pop_ceil_shake_draw */ + pop_heal_off(x, 0, 64, CEIL_BAND_H); + pop_dbg_m14(); /* ЗАМЕР: heal сделан */ gfx_set_bank(GFX_BANK_SPRITE); pop_t_clip_top = POP_YOFF; draw_tile(-1, col); - if (col + 1 < 10) draw_tile(-1, col + 1); pop_t_clip_top = 0; gfx_set_bank(GFX_BANK_NORMAL); + pop_dbg_m15(); /* ЗАМЕР: draw_tile сделан */ } void pop_ceil_bake_empty(int col) __banked @@ -850,6 +865,8 @@ typedef struct { * разбора артефактов множественного падения; наружу таблица не объявлена и * никем не читается. */ mob_t mobs[MOB_MAX]; +/* Сколько кусков сейчас летит (active). Считает pop_loose_mob_tick. */ +static uint8_t mobs_live; /* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на * который он лёг → приложение/pop_map ставит debris + do_knock. */ @@ -863,14 +880,20 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254}; static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25}; #define MOB_Y_NONE 0x7FFF -static void mob_spawn(uint8_t room, int row, int col) +/* Свободный слот = не летит И дочистка завершена (иначе затрём хвост, + * который ещё не стёрт со второй страницы). Общий для обычного отрыва и + * для копии из loose_fall — раньше цикл был написан дважды. */ +static mob_t *mob_alloc(void) { uint8_t i; - mob_t *m = 0; - /* свободный слот = не летит И дочистка завершена (иначе затрём хвост, - * который ещё не стёрт со второй страницы) */ for (i = 0; i < MOB_MAX; i++) - if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; } + if (!mobs[i].active && !mobs[i].clean) return &mobs[i]; + return 0; +} + +static void mob_spawn(uint8_t room, int row, int col) +{ + mob_t *m = mob_alloc(); if (!m) return; /* таблица полна — кусок без спрайта (тайл всё равно пуст) */ m->x = POP_COL_XH[col] * 8; m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */ @@ -897,8 +920,7 @@ void pop_loose_mob_spawn(int row, int col) __banked * Через границу комнаты — как mob_down_a_row: y −= 192, ряд 0, комната снизу. */ static void mob_spawn_copy(const mob_t *src) { - uint8_t i; - mob_t *m = 0; + mob_t *m; int16_t y = (int16_t)(src->y + 6); int8_t row = (int8_t)(src->row + 1); uint8_t room = src->room; @@ -909,8 +931,7 @@ static void mob_spawn_copy(const mob_t *src) room = pop_room_link(room, 3 /* вниз */); if (!room) return; /* ушло из уровня — куску негде лететь */ } - for (i = 0; i < MOB_MAX; i++) - if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; } + m = mob_alloc(); if (!m) return; m->x = src->x; m->y = y; @@ -1193,6 +1214,11 @@ static void mob_mark_neighbour(const mob_t *m) static void mob_draw_pass(uint8_t pg, uint8_t want_defer) { uint8_t order[MOB_MAX], n = 0, i, j; + /* Ранний выход: проход зовётся ДВАЖДЫ за кадр (под Kid и поверх него), и + * без него мы перебирали бы все MOB_MAX слотов 28 раз в кадре даже + * когда в воздухе пусто. Счётчик ведёт тик, которому проход по слотам + * нужен и так. */ + if (!mobs_live) return; for (i = 0; i < MOB_MAX; i++) { mob_t *m = &mobs[i]; if (!m->active || m->room != pop_t_room) continue; @@ -1218,25 +1244,34 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer) void pop_loose_mob_tick(void) __banked { - uint8_t pg = gfx_get_draw_page() & 1, i; - for (i = 0; i < MOB_MAX; i++) { + uint8_t pg = gfx_get_draw_page() & 1, i, live = 0; + /* Слоты обходим УКАЗАТЕЛЕМ и пустые отсеиваем ЗДЕСЬ, а не внутри + * mob_tick_one. Индексная запись mobs[i] заставляла SDCC умножать i на + * sizeof(mob_t)=15 заново под каждое поле (.active/.clean/.x/.y), и 14 + * пустых слотов стоили 35 790 тактов в кадре, где не падает НИЧЕГО + * (замер 2026-08-13). Гард в самом mob_tick_one оставлен: функция + * зовётся и из других мест. */ + mob_t *m = mobs; + for (i = 0; i < MOB_MAX; i++, m++) { /* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом, * мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя * (DRAW-COST). Взводим ДО тика: кусок может исчезнуть прямо в нём, а * стереть себя ему всё равно надо будет. */ - if (mobs[i].active || mobs[i].clean) - /* Метка «фон трогали вот здесь» обязана покрывать ВЕСЬ коридор - * heal'а, а не габарит спрайта. Иначе персонаж, попавший в - * расширенную часть коридора, стирается heal'ом, но перерисовать - * себя не просит (pop_char_skip_mask его пропускает) — и просто - * исчезает с экрана. Ровно это и случилось, когда коридор - * расширили под транзитную перерисовку соседа (прогон - * 2026-08-13: в комнате 23 после падения гряды пропадал Кид). - * Запас сверху — на путь, пройденный за кадр (speed до 29): heal - * стирает ПРОШЛУЮ позицию, она выше текущей. */ - pop_cd_touch(MOB_X0(mobs[i].x), mobs[i].y - 27 + POP_YOFF, MOB_W, 64); - mob_tick_one(&mobs[i], pg); + if (!m->active && !m->clean) continue; /* пустой слот — ни адреса, ни вызова */ + /* Метка «фон трогали вот здесь» обязана покрывать ВЕСЬ коридор + * heal'а, а не габарит спрайта. Иначе персонаж, попавший в + * расширенную часть коридора, стирается heal'ом, но перерисовать + * себя не просит (pop_char_skip_mask его пропускает) — и просто + * исчезает с экрана. Ровно это и случилось, когда коридор + * расширили под транзитную перерисовку соседа (прогон + * 2026-08-13: в комнате 23 после падения гряды пропадал Кид). + * Запас сверху — на путь, пройденный за кадр (speed до 29): heal + * стирает ПРОШЛУЮ позицию, она выше текущей. */ + pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64); + mob_tick_one(m, pg); + if (m->active) live++; } + mobs_live = live; } static void mob_render(mob_t *m, uint8_t pg) diff --git a/applications/PoP/roomtest/pop_state.c b/applications/PoP/roomtest/pop_state.c index 33d354d..5d98df6 100644 --- a/applications/PoP/roomtest/pop_state.c +++ b/applications/PoP/roomtest/pop_state.c @@ -113,3 +113,36 @@ void pop_dbg_m1(void) { } void pop_dbg_m2(void) { } void pop_dbg_m3(void) { } void pop_dbg_m4(void) { } + +/* ВРЕМЕННО (разбор зелёного блока 2026-08-13): состав перерисовок в САМОМ + * тяжёлом кадре. pop_redraw_needed считает вызовы по видам, и если кадр + * оказался тяжелее прежнего максимума — сохраняет расклад сюда. + * Индексы 0..7 — POP_RD_*, 8..9 — POP_RDA_* (полоса у потолка). */ +uint8_t pop_dbg_rdmax[10]; +uint8_t pop_dbg_rdmax_tot; + +/* ВРЕМЕННО (замер внутренностей 2026-08-13): m5..m8 — границы внутри + * pop_ceil_shake_draw (heal / draw_tile) и mob_render (heal / блиты). */ +void pop_dbg_m5(void) { } +void pop_dbg_m6(void) { } +void pop_dbg_m7(void) { } +void pop_dbg_m8(void) { } + +/* ВРЕМЕННО (разбор pop_loose_tick 2026-08-13): m9..m12 — границы ВНУТРИ + * самого pop_loose_tick. Он банковый, но зовёт резидент W1 напрямую, + * так что брейкпоинты по этим адресам однозначны. */ +void pop_dbg_m9(void) { } +void pop_dbg_m10(void) { } +void pop_dbg_m11(void) { } +void pop_dbg_m12(void) { } + +/* ВРЕМЕННО (разбор pop_ceil_shake_draw 2026-08-13): heal / draw_tile. */ +void pop_dbg_m13(void) { } +void pop_dbg_m14(void) { } +void pop_dbg_m15(void) { } + +/* ВРЕМЕННО (разбор pop_redraw_needed по ВИДАМ 2026-08-13): вид приезжает в + * A (__sdcccall(1), uint8 arg1), брейкпоинт на первой инструкции логирует + * его вместе с тактами — так видно цену КАЖДОГО вида, а не число пометок. */ +void pop_dbg_kind(uint8_t k) { (void)k; } +void pop_dbg_m16(void) { } diff --git a/applications/PoP/roomtest/pop_state.h b/applications/PoP/roomtest/pop_state.h index 33d4918..841be8a 100644 --- a/applications/PoP/roomtest/pop_state.h +++ b/applications/PoP/roomtest/pop_state.h @@ -80,3 +80,16 @@ extern int8_t pop_jumped_mirror; /* ВРЕМЕННО: маркеры фаз кадра (замер). */ void pop_dbg_m1(void); void pop_dbg_m2(void); void pop_dbg_m3(void); void pop_dbg_m4(void); + +/* ВРЕМЕННО: состав перерисовок в самом тяжёлом кадре (замер). */ +extern uint8_t pop_dbg_rdmax[10]; +extern uint8_t pop_dbg_rdmax_tot; + +/* ВРЕМЕННО: маркеры внутренних замеров. */ +void pop_dbg_m5(void); void pop_dbg_m6(void); +void pop_dbg_m7(void); void pop_dbg_m8(void); +void pop_dbg_m9(void); void pop_dbg_m10(void); +void pop_dbg_m11(void); void pop_dbg_m12(void); +void pop_dbg_m13(void); void pop_dbg_m14(void); +void pop_dbg_m15(void); +void pop_dbg_kind(uint8_t k); void pop_dbg_m16(void); diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 4d98eb6..0d0b17f 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -452,6 +452,7 @@ int main(void) pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */ /* — ДО Kid, чтобы Kid всегда был поверх плиты) */ + pop_dbg_m5(); /* ЗАМЕР: pop_loose_tick сделан */ pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */ /* process_trobs — рисования внутри нет) */ if (pop_neigh_dirty) { @@ -478,7 +479,9 @@ int main(void) pop_level_access_end(); } } - pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */ + pop_dbg_m6(); /* ЗАМЕР: pop_process_trobs сделан */ + pop_redraw_needed(); + pop_dbg_m7(); /* ЗАМЕР: pop_redraw_needed сделан */ /* и разобрать пометки перерисовки тайлов (порт */ /* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */ { /* ворота соседа слева (room8) видны в col0 — */ uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */