# ЗЕЛЁНАЯ фаза (слой фона) — анализ и оптимизация Рабочий документ: живёт между сессиями. Внизу **журнал правок** — каждая запись с замером до/после. Сцена, рецепт воспроизведения и зонды — [`perf_l13_room23.md`](perf_l13_room23.md); там же ответ про 8-битные габариты. Парная фаза — [`perf_cyan_phase.md`](perf_cyan_phase.md). Границы фазы в `roomtest.c`: от `PROF(4)` (строка 450) до `PROF(6)` (строка 620). Содержимое: `pop_loose_tick`, `pop_process_trobs`, `pop_redraw_needed`, шов, смена уровня, сигналы провалов, вспышка. **Бюджет: ≤ 400 000 тактов (растровый кадр = 430 000).** | состояние | зелёная | |---|---:| | покой в комнате 23 | 35 760 | | дрожат 6 плит-потолков | 366 000 | | **пик каскада (провалы + посадки)** | **805 000 (2,0× бюджета)** | --- ## 1. Раскладка (замер 2026-08-17, `c312e4a`, зонды m5/m6/m7 + kind/m16) ### 1.1 Верхний уровень | участок | покой | дрожь | пик | |---|---:|---:|---:| | `pop_loose_tick` | 25 602 | 35 646 | **196 152** | | `pop_process_trobs` | 1 050 | 1 050 | 1 050 | | **`pop_redraw_needed`** | 924 | **319 944** | **737 322** | | хвост (шов, смена уровня, сигналы, вспышка) | 9 336 | 9 336 | 11 964 | **`pop_redraw_needed` — 91 % фазы на пике.** ### 1.2 `pop_loose_tick` изнутри (зонды m9..m12) | участок | покой | пик | |---|---:|---:| | два цикла по тайлам (30 + 10 позиций) | 11 004 | 9 852 | | **`pop_loose_mob_tick`** (heal летящих кусков) | 7 416 | **178 494** | | `check_loose_fall_on_kid` | 6 120 | 3 480 | | хвост | 324 | 3 252 | `pop_loose_mob_tick` при шести кусках в воздухе — **≈29 700 такта на кусок**. Это heal прошлой позиции (коридор `MOB_W × 64`) плюс `pop_cd_touch` на коридор плюс `mob_tick_one`. ### 1.3 Цена ОДНОЙ перерисовки по видам (66 + 12 + 12 замеров) | вид | штук замерено | средняя цена | сколько за кадр | Σ за кадр | |---|---:|---:|---:|---:| | `RDA_CEIL` — дрожащая плита-потолок | 66 | **48 658** | до 6 | 292 000 | | `RDA_CEIL_GONE` — запечь колодец | 12 | **251 023** | до 2 | 619 000 | | `RD_FLOOR` — щебень на месте посадки | 12 | **198 259** | до 2 | 397 000 | Для масштаба: ОДНА запечка колодца = 58 % растрового кадра, две = 144 %. `pop_dbg_rdmax` подтверждает состав пика: шесть пометок, все `RDA_CEIL`. ### 1.4 `RDA_CEIL` = 48 658 изнутри (зонды m13/m14/m15) | участок | такты | |---|---:| | `pop_heal_off(x, 0, 64, CEIL_BAND_H=8)` | 8 491 | | `draw_tile(-1, col)` | **39 029** | Блитов внутри этого `draw_tile` — **1,17 на тайл** (7 вызовов `pop_blit_b` на 6 тайлов), то есть 15–16 тыс. тактов. **Остальные ~23 700 такта — накладные `draw_tile`, ни одного пикселя.** ### 1.5 `RDA_CEIL_GONE` = 251 023 и `RD_FLOOR` = 198 259 - `pop_ceil_bake_empty(col)` — бар + **шесть** `draw_tile` (ряд −1 и ряд 0, колонки col−1..col+1): 6 × 39 000 ≈ 234 000. - `pop_floor_bake(row,col)` — бар 60×39 от `yb+26` + **два** `draw_tile` НИЖНЕГО ряда; там стена, то есть `wall_pattern` — ≈**99 000 на тайл**. ### 1.6 Почему `draw_tile` стоит 39 000 при одном блите Кодоген. Подсчёт в `bank7_pop_room.asm` и совпадение с замером до процентов (одно `-N(ix)` ≈ 19 номинальных тактов ≈ 46 замеренных): | функция | стековый кадр | обращений `(ix)` | ожидаемо | замерено | |---|---:|---:|---:|---:| | `draw_tile` | **26 Б** | **513** | ~23 600 | **~23 700** | | `pop_ceil_bake_empty` | — | 8 | ~370 | | | `pop_floor_bake` | — | 12 | ~550 | | | `mob_tick_one` | 16 Б | 99 | ~4 550 | из ~29 700/кусок | | `mob_draw_pass` | 18 Б | 20 | ~900 | | У `draw_tile` пятнадцать локалей, объявленных `int` (`code`/`lcode`/`lmod`/`xh`/`x`/`dby`/`dmy`/`t`/`lt`/`rbl_code`/`rbl_mod`/ `wmod`/`base_id`/`base_yb`), — в регистры они не влезли, и функция целиком уехала в стековый кадр. Байтовые по природе из них все, кроме `x`/`dby`/`dmy` (экранные координаты, бывают отрицательными). --- ## 2. Способы ускорения По убыванию отдачи. Оценки — от замеров выше; каждая позиция сверена с `SDLPoP/src/seg008.c` (правило проекта: механику сверять с оригиналом ДО кодинга). ### G1. Окно клипа для ЗАПЕЧКИ — самый крупный кусок **Отдача: −250 000 … −400 000 на пиковый кадр.** `pop_ceil_bake_empty` и `pop_floor_bake` восстанавливают ИЗВЕСТНЫЙ прямоугольник (тот, который сами же залили баром), а зовут полный `draw_tile`, который честно рисует и то, что в этот прямоугольник не попадает. У `pop_floor_bake` бар — 60×39 от `yb+26`, а тело дворцовой кладки рисуется НИЖЕ `yb+64`, то есть **вся стена рисуется мимо цели**. В `pop_blit_b` уже есть дешёвый предфильтр по окну `pop_t_fclip_*` — он режет кусок ДО `atlas_image` и `gfx_w0_map`, то есть почти даром. Достаточно выставлять это окно на время запечки. Грабли, которые надо обойти: `pop_t_fclip_on` в `pop_blit_b` заодно подавляет `pop_cd_touch` (признак «это fore-проход поверх персонажа»). Для запечки это как раз правильно — она помечает область сама, — но проверить обязательно; если понадобится, ввести отдельный флаг «окно только для отсева». Сверка с оригиналом: у него та же идея, только через `wipetable` — `draw_tile_wipe(height)` кладёт прямоугольник в таблицу, а `draw_table` рисует уже с известным клипом (seg008:1368, 1373). ### G2. Расколоть `draw_tile` + отдельный лист для ряда −1 **Отдача: 39 029 → 18 000–20 000 на тайл полосы, то есть −140 000 на кадр дрожания; и она умножается на G1 (шесть вызовов в запечке).** Два шага: 1. **Отдельный `draw_tile_aboveroom(col)`** — дословный порт seg008:01F2. Оригинал для ряда −1 делает РОВНО шесть вещей: `draw_tile_floorright`, `draw_tile_anim_topright`, `draw_tile_right`, `draw_tile_bottom(1)`, `draw_loose(1)`, `draw_tile_fore`. Ни базы, ни `draw_tile_anim`, ни `draw_tile_anim_right`, ни `wall_pattern`, ни вычисления `rbl_*` через `row+1 > 2`. У нас всё это отсекается ветками `if (row >= 0)` уже ВНУТРИ общего `draw_tile` — то есть кадр IX, пролог и вся арифметика оплачиваются всегда. 2. **Расколоть общий `draw_tile` по образцу оригинала** (seg008:01C7 — девять узких листьев). Каждый лист получает `xh`/`dby`/`dmy` параметрами (в регистрах), локалей у него единицы, кадра IX нет. Заодно проверить `redraw_needed_above` (seg008:02C1): оригинал делает `draw_tile_wipe(3)` — стирает **32×3**, а мы `pop_heal_off(x,0,64,8)` = 64×8. Площадь в 5 раз больше нужной (8 491 такта), хотя основная цена там — накладные вызова. ### G3. `blit_b_clip` — 22 Б кадра, 211 `(ix)` **Отдача: 14 088 → ~10 500 на блит. В зелёной фазе ВСЕ блиты клипованные (полоса у потолка выставляет `pop_t_clip_top`) — при 7–17 блитах это −25 000 … −60 000; в циане и fore-проходе больше.** `dx/dy/dw/dh/sx/sy` объявлены `int`. По построению `sx/sy/dw/dh ≤ 255` (габарит любого спрайта ≤ 56×63, см. `perf_l13_room23.md` §5) — их можно сделать `uint8_t`, оставив 16-битными только экранные `dx/dy`. Прецедент: тот же приём в `pop_blit_b` (коммит `c312e4a`) дал −19 % на клипованном пути и −16 % на быстром, `_CODE` −42 Б. Проверка по правилу OPT-BLIT: после правки смотреть пролог в `.sprinter-cc-roomtest/pop_tile.asm` — исчез ли `ld iy,#-22`, сколько осталось `-N(ix)`. ### G4. `mob_tick_one` / `pop_loose_mob_tick` — 29 700 на кусок **Отдача: −30 000 … −48 000 на кадр при шести кусках.** 16-байтовый кадр, 99 `(ix)`. Байтовыми могут быть габарит heal-коридора и разность `prev_y`; `x`/`y` — 16-битные. Плюс `pop_cd_touch` на коридор — один на кусок, 4 502 такта (см. `perf_cyan_phase.md` §1.3 про его цену). ### G5. `LOOSE-SHAKE-RUNS` — пометки по сменам кадра **Отдача: ~20 % работы фазы, и только на дрожащих плитах. Оценка старая (2026-08-13), не перемерена.** Подробности и арифметика — в `roomtest/TASKS_OPEN.md#loose-shake-runs`. Коротко: наивный вариант «помечать только на смене кадра» выигрыша НЕ даёт (пять смен × две страницы = те же десять перерисовок); работает только версия «пары и тройки» — две пометки с `pages = 2` на фазах 5 и 8. Тот же приём применим к плитам-ПОТОЛКАМ (`pop_set_redraw_above`), где сейчас идёт пометка каждый кадр отсчёта. Вопрос открытый: стоит ли неочевидный код такого узкого выигрыша. Браться после G1–G4. ### G6. `BG-ONCE` — heal вместо чёрного бара Предложение пользователя, см. `roomtest/TASKS_OPEN.md#bg-once`. Сейчас запечка = «залить чёрным + перерисовать фон», то есть двойная работа; heal по координатам бара делает то же за один проход. Прямо дополняет G1. --- ## 3. Что НЕ делать (проверено, отрицательный результат) - **Не ускорять передачу пикселей** — она на пределе железа (3+3 такта на байт через акселератор), memory `blit_cost_model`. В пиковом кадре «железный» минимум всех блитов ≈150 000 из 1 437 150. - **Не откладывать запекание на другой кадр** — запечка пишет ОЗУ-копию, из которой восстанавливает heal; отложенная даёт призрак плиты на месте дыры (проверено 2026-08-13). - **Не батчить смежные колонки в `pop_ceil_shake_draw`** — пробовалось и убрано: плиты стартуют со случайными задержками, в кадре дрожат разрозненные колонки, пробег почти всегда длиной в одну. - **Не искать проблему в W3-скобке** (`_bgi_begin`/`_bgi_end` — пять инструкций) и не списывать разброс на прерывания (внутри размерной группы разброс три такта). --- ## 4. Журнал правок | дата | что сделано | зелёная: покой / дрожь / пик | коммит | |---|---|---|---| | 2026-08-17 | базовый замер (до правок этой серии) | 35 760 / 366 000 / **805 000** | `c312e4a` |