From 9e1a55bdc301d29d2248b589d5a939f56d7fed9f Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 13 Aug 2026 19:56:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B0=D0=B4=D0=B0=D1=8E=D1=89=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=BB=D0=B8=D1=82=D1=8B:=20=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B9=D1=82=D1=8B=20=D0=BF=D0=B0=D0=B4=D0=B0=D1=8E=D1=89?= =?UTF-8?q?=D0=B5=D0=B3=D0=BE=20=D0=BD=D0=B0=D0=B1=D0=BE=D1=80=D0=B0,=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=80=D1=8B=20=D1=81=D0=BB=D0=BE=D1=91?= =?UTF-8?q?=D0=B2,=20=D0=B0=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=20midtable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Спрайты: у падающего куска obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из таблиц по этому индексу — 70/74/72, а не 41/43/42. Мы рисовали плиту В ПОКОЕ, отсюда «цельная ровная» вместо двух половин со сдвигом правой на пиксель. Подпорка «перерисовать соседний тайл поверх куска» убрана: она тащила на плиту чужой узор и окно, да ещё стоила по одной полной отрисовке тайла на кусок за кадр. Клип объекта (clip.right = 40) НЕ портирован — единицы поля не выяснены, буквальные 40 пикселей срезают задний угол плиты. Цена отказа — правая грань куска и передние части чужих тайлов его не перекрывают (MOB-CLIP-RIGHT, BUGS_OPEN.md). Коридор heal сужен по высоте 32 -> 24: реальный след спрайта 16 px. Ширина оставлена 64 — из неё используются 62, экономить нечего; заодно в комментарии зафиксировано, что «выравнивание блока акселератора» ничем не подтверждается и требует проверки артефактом. docs/midtable_analysis.md — разбор слоёв оригинала и замеры: - логический кадр 1 289 526 тактов; - pop_redraw_needed 978 тактов (0,08 %); - fore-проход 128 778 тактов (10 %) на ОДНОГО персонажа, но выполняется лишь на 4 % кадров — гасит пропуск неизменившегося персонажа; - максимум объектов на одном тайле 2 (пара из loose_fall), значит сортировка внутри тайла бесплатна. Вывод: единственный риск порта objtable — сохранится ли окно клипа fore-прохода; до разбора redraw_frames_fore выбирать вариант рано. Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/README.md | 1 + applications/PoP/docs/midtable_analysis.md | 227 +++++++++++++++++++++ applications/PoP/roomtest/pop_room.c | 90 ++++---- applications/PoP/roomtest/pop_state.c | 1 + applications/PoP/roomtest/pop_state.h | 1 + 5 files changed, 270 insertions(+), 50 deletions(-) create mode 100644 applications/PoP/docs/midtable_analysis.md diff --git a/applications/PoP/docs/README.md b/applications/PoP/docs/README.md index d864cdf..cf4ac7f 100644 --- a/applications/PoP/docs/README.md +++ b/applications/PoP/docs/README.md @@ -12,6 +12,7 @@ | [`perf_backlog.md`](perf_backlog.md) | **Отложенная оптимизация отрисовки** с замерами + как мерить (wait-state'ы, границы кадра) | | [`levels_plan.md`](levels_plan.md) | Следующий этап: уровни 2+, второй тайлсет, читы SDLPoP | | [`levels_12_15_plan.md`](levels_12_15_plan.md) | **Уровни 12/13** (тень, Джафар, падающие плиты) + что такое 14/15 и 0. 2026-08-13 | +| [`midtable_analysis.md`](midtable_analysis.md) | **Слои отрисовки**: как устроены back/mid/fore и objtable в оригинале, чего стоит порт, развилки. 2026-08-13 | | [`layout_plan_v2.md`](layout_plan_v2.md) | Раскладка кода по окнам/банкам/страницам + замеры скорости отрисовки | | [`room_model_plan.md`](room_model_plan.md) | `kid_room ≠ drawn_room` (straddle): сделан S1, остальное впереди | | [`host_tests_plan.md`](host_tests_plan.md) | Модульные тесты движка под ucsim_z80: два шва, регрессии из `BUGS_CLOSED.md`, дифф против SDLPoP | diff --git a/applications/PoP/docs/midtable_analysis.md b/applications/PoP/docs/midtable_analysis.md new file mode 100644 index 0000000..4b0a3f8 --- /dev/null +++ b/applications/PoP/docs/midtable_analysis.md @@ -0,0 +1,227 @@ +# Слои отрисовки: как устроен оригинал и чего стоит порт + +Разбор 2026-08-13, по `../SDLPoP/src/seg008.c`. Повод — семь дефектов +падающих плит на уровне 13, из которых три оказались не багами кода, а +следствием того, что у нас нет слоя, в котором объекты и куски тайлов +сортируются между собой. Решение по этому документу ещё не принято. + +--- + +## 1. Как это работает в оригинале + +### 1.1 Три таблицы, а не «слои» + +`draw_tables` (seg008:1373) рисует ровно в таком порядке: + +``` +restore_peels(); +draw_wipes(0); +draw_table(0); // BACKTABLE +draw_table(3); // MIDTABLE +draw_wipes(1); +draw_table(1); // FORETABLE +``` + +Это грубое разделение на три уровня глубины. Куда попадёт кусок тайла, +решает переменная `ptr_add_table`, которую вызывающий переставляет перед +`draw_tile*`: по умолчанию `add_backtable`, в оверлее кромки — +`add_midtable` (`draw_other_overlay`, seg008:1499), а `add_foretable` +вызывается явно и точечно. + +### 1.2 Объекты живут НЕ в таблицах, а в objtable — и привязаны к ТАЙЛУ + +Персонажи, падающие куски, мечи, брызги попадают в `objtable`, и у каждой +записи есть поле `tilepos` — тайл, которому объект принадлежит. + +Ключевое: объекты рисуются **не отдельным проходом после фона**, а ВНУТРИ +обхода тайлов. В `redraw_needed_tile` (seg008:207) стоит: + +```c +if (tile_object_redraw[tilepos]) { + if (tile_object_redraw[tilepos] == 0xFF) + draw_objtable_items_at_tile(tilepos - 1); + draw_objtable_items_at_tile(tilepos); + tile_object_redraw[tilepos] = 0; +} +if (redraw_frames_fore[tilepos]) draw_tile_fore(); +``` + +То есть на каждом тайле: сначала его фоновые куски, потом объекты ЭТОГО +тайла, потом его передние куски. + +### 1.3 Порядок глубины складывается из ТРЁХ независимых механизмов + +| механизм | что даёт | +|---|---| +| порядок обхода тайлов: ряды **2, 1, 0**, колонки 0..9 (seg008:129) | тайл, обойдённый позже, рисуется поверх | +| сортировка объектов ВНУТРИ одного тайла (`sort_curr_objs`, seg008:1553) | кто из объектов одного тайла поверх кого | +| три таблицы back/mid/fore | грубая глубина для кусков тайлов | + +Сортировка внутри тайла (`compare_curr_objs`, seg008:1572) — пузырьком, и +правил в ней три: + +``` +объект типа 1 (ТЕНЬ) — всегда первым; +оба объекта — падающие плиты (0x80): y1 < y2 → по УБЫВАНИЮ y; +любая другая пара: y1 > y2 → по ВОЗРАСТАНИЮ y. +``` + +Обратный порядок для пары плит — не описка: две плиты из `loose_fall` летят +в 6 пикселях друг от друга, и верхняя обязана лечь поверх нижней. + +### 1.4 Что из этого следует + +**«Сортируемый midtable» — неточное имя.** Глобальной сортировки среднего +слоя в оригинале нет. Есть привязка объекта к тайлу и сортировка внутри +тайла; всё остальное решает порядок обхода. Это принципиально дешевле +общей сортировки: объектов на один тайл обычно 1-2. + +--- + +## 2. Что делаем мы + +Наш кадр — жёсткая последовательность проходов, без привязки объектов к +тайлам: + +``` +фон: pop_loose_tick (физика кусков) -> pop_process_trobs -> pop_redraw_needed + -> редрой шва +объекты: pop_loose_mob_draw (куски ПОД Kid, отсортированы по y между собой) + pop_char_draw(OPP/KID) (порядок задаёт guard_over_kid) + pop_loose_mob_draw_over (куски ПОВЕРХ Kid) +перед: pop_fore_over_char — ТОЛЬКО в окне вокруг персонажа +``` + +Отличия, из которых растут все три оставшихся дефекта: + +1. **Объект не знает своего тайла.** Глубина «кусок против Кида» считается + отдельной формулой (`pop_room.c`, поле `defer`), а «кусок против КУСКА + ТАЙЛА» не считается вовсе — куски тайлов рисуются раньше всех объектов. +2. **Передний слой считается только вокруг персонажа.** Это наша + оптимизация (memory `pop_fore_layer_cost`: полный проход стоил 78 % + кадра). Падающая плита в чужом углу комнаты передних частей тайлов + поверх себя не получает — отсюда «плита перед колонной». +3. **Оверлей кромки идёт после персонажей** и потому безусловно поверх + всех, тогда как у оригинала он в midtable и сортируется. + +--- + +## 3. Что затрагивает порт + +| участок | объём правки | +|---|---| +| `pop_room.c` — куски | привязать к тайлу, убрать `defer`, убрать собственную сортировку | +| `pop_cdraw.c` — персонажи | то же: объект вместо слота, привязка к тайлу | +| `pop_bg.c` — `overlay_mid_tile`, `fore_only_tile`, `ceil_over_kid_tile` | вызов из обхода тайлов, а не из отдельного прохода | +| `pop_redraw.c` — пометки | добавить «на этом тайле есть объект» (порт `tile_object_redraw`) | +| `roomtest.c` — главный цикл | вместо трёх проходов один: обход тайлов с объектами внутри | +| окно fore-клипа (`pop_t_fclip_*`) | смысл меняется: клип по тайлу, а не по персонажу | + +Плюс новая структура objtable и её сортировка — но маленькая, на тайл. + +--- + +## 4. Плюсы + +* **Уходят разом** MOB-CLIP-RIGHT, MID-OVERLAY-LAYER и «плита перед + колонной»: все три — следствие отсутствия привязки к тайлу, а не + самостоятельные баги. +* **Уходят подпорки.** Перерисовка соседнего тайла поверх куска, + `defer`, ручная сортировка кусков, отдельный проход `draw_over` — + всё это заменяется одним механизмом. +* **Совпадение с оригиналом по построению.** Дальше любой вопрос «что + поверх чего» решается чтением seg008, а не экспериментом в MAME. +* **Возможный выигрыш по кадру.** Сейчас fore-проход считает окно вокруг + персонажа и всё равно перебирает до девяти тайлов; при привязке к тайлу + передние части рисуются только там, где реально есть помеченный объект. + Но это НАДО ЗАМЕРИТЬ, а не обещать. + +--- + +## 5. Минусы и риски + +* **Риск регресса широкий.** Трогается порядок отрисовки ВСЕГО: Кид, + соперник, меч, брызги, зеркало, куски, оверлеи, полоса потолка. + Уровни 1-11 приняты и держатся на текущем порядке. +* **Наша оптимизация fore-окна может не пережить порт в прежнем виде.** + Она даёт 3.2x на самом дорогом проходе (memory `pop_fore_layer_cost`). + Если привязка к тайлу заставит рисовать передние части шире — можно + потерять больше, чем выиграть. +* **Дабл-буфер.** У оригинала один экран с dirty-rect, у нас две страницы + со своими копиями фона и heal. Пометка «на тайле есть объект» обязана + быть счётчиком страниц, как остальные наши пометки, — иначе объект + перерисуется на одной странице и не перерисуется на другой. +* **Банки.** Отрисовка размазана по трём банкам (`pop_bg` 2, `pop_cdraw` 4, + `pop_room` 7) плюс резидент. Единый обход тайлов с объектами внутри + означает, что цикл обхода зовёт код из всех трёх — надо проверить, что не + упрёмся в границы банков и трамплины. +* **Объём.** Это не правка, а этап: сопоставимо с тем, что делалось для + fore-слоя. + +--- + +## 6. Развилки + +**A. Полный порт** — objtable с привязкой к тайлу, сортировка внутри тайла, +объекты внутри обхода. Максимально близко к оригиналу, максимальный риск и +объём. + +**B. Частичный: только привязать КУСКИ к тайлам.** Персонажей оставить как +есть. Закрывает MOB-CLIP-RIGHT и «плиту перед колонной», не трогает +проверенный порядок персонажей. Дешевле и безопаснее; MID-OVERLAY-LAYER +остаётся. + +**C. Отложить** до этапа BG-ONCE и делать вместе — там всё равно +пересматриваются слои, и два пересмотра подряд дороже одного. + +Рекомендация: **B или C**. Вариант A целиком оправдан только если мы +одновременно берёмся за BG-ONCE — тогда это один пересмотр слоёв вместо +двух, и замер кадра делается один раз. + +--- + +## 7. ЗАМЕРЫ (сделаны 2026-08-13, уровень 13) + +Метод — маркеры-пустышки в РЕЗИДЕНТЕ вокруг измеряемого вызова плюс +брейкпоинты с `printf totalcycles` (memory `z80_profiling_method`). В +банковый код брейкпоинт ставить нельзя: 0xC000 — общее окно всех банков. + +| что | такты | доля логического кадра | +|---|---|---| +| логический кадр целиком | **1 289 526** | 100 % | +| `pop_redraw_needed` | **978** | 0,08 % | +| **fore-проход, ОДИН персонаж** | **128 778** | **10 %** | + +Плюс два счётчика за прогон ~229 логических кадров: + +* fore-проход вызван **10 раз** — на 96 % кадров он не выполняется вовсе + (пропуск неизменившегося персонажа, `pop_char_skip_mask`). Средняя цена + по кадру выходит ~0,4 %, но КАК ТОЛЬКО персонаж движется — платим все 10 % + каждый кадр, и при двух персонажах это ~20 %; +* **максимум объектов на одном тайле = 2** (комната 16, пара из + `loose_fall`: плита сбивает плиту, дальше летят обе). В комнате 23, где + гряда падает в пустоту, максимум 1. + +### 7.1 Что эти числа меняют в оценке + +**Сортировка внутри тайла — бесплатна.** Два объекта, пузырёк на два +элемента. Возражение против варианта A, которое закладывалось в §5, снято. + +**`pop_redraw_needed` можно не считать вовсе.** 978 тактов против 128 778 у +fore-прохода — соотношение 131 к 1. + +**Единственный настоящий риск порта — окно клипа fore-прохода.** Если +привязка объектов к тайлам заставит рисовать передние части шире нынешнего +окна вокруг персонажа, мы потеряем 10 % кадра, и потеряем их НА ДВИЖЕНИИ, +когда бюджет и так самый напряжённый. + +### 7.2 Что осталось выяснить ДО выбора варианта + +Как оригинал отсекает лишнюю работу переднего слоя: `redraw_frames_fore[tilepos]` +рисует передние части ТОЛЬКО у помеченных тайлов (seg008:214). Надо понять, +насколько узко эта пометка ставится — если не шире нашего окна, вариант A +безопасен по кадру; если шире, остаёмся на B. + +**Обновлённая рекомендация:** решение упирается в §7.2, и до его разбора +выбирать между A и B преждевременно. B остаётся безопасным запасным +вариантом. diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index edce330..e459901 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -938,7 +938,22 @@ void pop_loose_mob_spawn_at(uint8_t room, int row, int col) __banked * [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной * странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */ #define MOB_X0(mx) ((mx) - 4) -#define MOB_W 64 /* mob_x-4 .. mob_x+59 (крышка +57 с запасом 2px) */ +/* Коридор heal — по РЕАЛЬНОМУ следу спрайта, а не «с запасом». + * След: по x mob_x .. mob_x+58, по y mob_y-16 .. mob_y — это объединение + * трёх частей (41 = 32x13 с y-3, 42 = 26x15 с x+32,y-1, 43 = 32x3 с y). + * + * Ширина оставлена прежней (64 = mob_x-4 .. mob_x+60): нужных 62 плюс поля. + * А вот высота была 32 при нужных 16 — вдвое лишнего на самой дорогой + * операции кадра, помноженной на число падающих плит; сужена до 24. + * + * ПРО ЗАПАС ПО ШИРИНЕ: обоснования «выравнивание блока акселератора» я не + * подтвердил — ни pop_heal_fast, ни gfx_heal координаты не округляют, а в + * memory про акселератор таких требований нет. Единственный источник — + * наблюдение в pop_clip_sprite про 1-2 px слайверы правой грани. Сузить + * ширину можно, но ТОЛЬКО подтвердив прогоном, что слайверы не появятся. */ +#define MOB_W 64 /* mob_x-4 .. mob_x+60 */ +#define MOB_HEAL_UP 20 /* верх коридора = mob_y − 20 */ +#define MOB_HEAL_H 24 /* .. mob_y + 4 */ /* Остановить падающий кусок (при смене комнаты — состояние per-tile не * должно утечь в новую комнату). */ @@ -1031,7 +1046,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg) * ЗАДЕЛ: правильнее не перерисовывать соседа вовсе, а КЛИПОВАТЬ * правую часть куска — оригинал так и делает (add_mob_to_objtable, * seg007:1161: clip.right = 40). Тогда коридор вернётся к 64x32. */ - pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - 27, MOB_W, 32); + pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - MOB_HEAL_UP, MOB_W, MOB_HEAL_H); m->prev_y[pg] = MOB_Y_NONE; } if (!m->active) { m->clean--; return; } @@ -1194,55 +1209,30 @@ static void mob_render(mob_t *m, uint8_t pg) * left(41) на obj_y-3, bottom(43) на obj_y, right(42) на obj_x+4 (=col+1, * заходит в тайл СПРАВА) на obj_y-1. */ gfx_set_bank(GFX_BANK_SPRITE); - pop_env_b(43, m->x, m->y); - pop_env_b(41, m->x, m->y - 3); - pop_env_b(42, m->x + 32, m->y - 1); + /* КЛИПА ОБЪЕКТА ЗДЕСЬ НЕТ. У оригинала он есть — add_mob_to_objtable + * (seg007:1161) ставит куску clip.right = 40, — но единицы этого поля я + * НЕ выяснил: буквальные 40 экранных пикселей срезают правый задний угол + * плиты (прогон 2026-08-13). У оригинала obj_x живёт в удвоенных + * единицах (char_x_left = obj_x / 2 + 58), так что 40 может означать и + * не пиксели. До выяснения рисуем кусок целиком. + * + * Подпорки «перерисовать соседний тайл поверх куска» тоже нет: она тащила + * на плиту чужой узор и окно (MOB-CLIP-RIGHT). Цена отказа — правая + * грань куска не прячется за передней гранью соседнего пола; артефакт + * мельче прежнего и уйдёт вместе с сортируемым midtable. */ + /* Спрайты ПАДАЮЩЕГО куска, а не лежащей плиты. У объекта в objtable + * obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из + * таблиц по этому индексу (seg008:518/596/608): + * loose_fram_left[10] = 70 (32x13, y-3) + * loose_fram_bottom[10] = 74 (32x3, y) + * loose_fram_right[10] = 72 (26x16, x+32, y-1) + * Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и + * «цельная ровная плита» вместо двух частей со сдвигом правой половины + * на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом). */ + pop_env_b(74, m->x, m->y); + pop_env_b(70, m->x, m->y - 3); + pop_env_b(72, m->x + 32, m->y - 1); m->prev_y[pg] = m->y; - /* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob: - * set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob). - * Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола, - * её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем - * сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу - * до heal'а следующего кадра — виден как хвост во время падения). */ - if (m->col + 1 < 10) { - /* Ряд соседа берём ИЗ КООРДИНАТЫ куска (y_to_row_mod4(ypos)), а НЕ из - * m->row: последний — логический счётчик «сквозь какой ряд летим», и - * mob_down_a_row уводит его на ряд ВПЕРЁД (для плиты нижнего ряда — - * сразу в 3, которого нет). Тогда пол соседа не перерисовывался - * вообще, и правый кусок 42 оставался ПОВЕРХ него: грань плиты видна - * через пол + перекрыта передняя грань пола. - * Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на - * границе рядов. Порт draw_mob (seg007:13E5). */ - int8_t r = pop_y_to_row((int16_t)m->y); - int8_t rt = pop_y_to_row((int16_t)(m->y - 18)); - /* ОКНО = КОРИДОР HEAL ЭТОГО КУСКА, ни пикселем шире. draw_tile - * рисует тайл ЦЕЛИКОМ (пол, грани, верх кладки ряда ниже — порядка - * 64x63), причём в GFX_BANK_SPRITE, то есть мимо теневой копии фона. - * Всё, что он клал за пределами коридора, не стиралось НИКОГДА: - * кусок улетал, а в соседнем столбце оставался обрезок пола - * (найдено прогоном 2026-08-13). - * - * Расширять под это heal НЕЛЬЗЯ: heal — самая дорогая операция - * кадра, а на 13-м уровне таких кусков одновременно шесть. Поэтому - * режем не heal под перерисовку, а перерисовку под heal — тем же - * окном, которым fore-слой отсекает лишние тайлы. */ - pop_t_fclip_x0 = MOB_X0(m->x); - pop_t_fclip_x1 = MOB_X0(m->x) + MOB_W; - pop_t_fclip_y0 = m->y - 27 + POP_YOFF; - pop_t_fclip_y1 = pop_t_fclip_y0 + 32; - pop_t_fclip_ly0 = pop_t_fclip_y0; /* переворота у кусков нет */ - pop_t_fclip_ly1 = pop_t_fclip_y1; - pop_t_fclip_on = 1; - if (r < 0) pop_t_clip_top = POP_YOFF; /* полоса у потолка — только срез */ - draw_tile(r, m->col + 1); - pop_t_clip_top = 0; - if (rt != r) { - if (rt < 0) pop_t_clip_top = POP_YOFF; - draw_tile(rt, m->col + 1); - pop_t_clip_top = 0; - } - pop_t_fclip_on = 0; - } gfx_set_bank(GFX_BANK_NORMAL); /* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */ pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30); diff --git a/applications/PoP/roomtest/pop_state.c b/applications/PoP/roomtest/pop_state.c index 9a2ca60..e47a943 100644 --- a/applications/PoP/roomtest/pop_state.c +++ b/applications/PoP/roomtest/pop_state.c @@ -102,3 +102,4 @@ uint8_t pop_seamless; * pop_dbg_roomnav и ../docs/impl_diff.md. Снимается первой же СМЕНОЙ * КОМНАТЫ ОБЫЧНЫМ ХОДОМ и стартом уровня. */ uint8_t pop_nav_hold; + diff --git a/applications/PoP/roomtest/pop_state.h b/applications/PoP/roomtest/pop_state.h index 07dcbc0..7b90ac1 100644 --- a/applications/PoP/roomtest/pop_state.h +++ b/applications/PoP/roomtest/pop_state.h @@ -74,3 +74,4 @@ void pop_dbg_trap(void); * следующий pop_check_mirror родит тень; 0 — обычное состояние. * Межбанковый: пишет pop_map, читает pop_map же, но через главный цикл. */ extern int8_t pop_jumped_mirror; +