Files
Sprinter-SDCC/applications/PoP/docs/perf_l11_room15.md
T
snark13 41deb69013 Реестр: P15 закрыт замерами обеих позиций
Лёгкая: 628 542 -> 464 796 (-163 746), персонажи не рисуются вовсе.
Тяжёлая: 758 358 -> 617 487 (-140 871), рисуется только Кид — он
действительно стоит под пламенем, а страж нет.  Пятирастровые кадры в
тяжёлой позиции исчезли (было 27 %).

Итог восьми позиций: 801 768 -> 464 796 в лёгкой (-42 %).  До цели
430 000 осталось 35 000 в лёгкой и 187 000 в тяжёлой.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:09:47 +03:00

16 KiB
Raw Blame History

Сцена и замер: факел + чомпер + страж, уровень 11 комната 15

Вторая целевая сцена для оптимизации (первая — perf_l13_room23.md, каскад плит). Здесь узкое место другое: не разовый пик на каскаде, а постоянная цена статичной комнаты, в которой одновременно живут два факела, чомпер и страж.

Такты — totalcycles MAME (не такты Z80, ≈2,4× номинала, memory sprinter_wait_states_2x). Растровый кадр = 430 000. Хвост кадра — три gfx_wait_vsync, поэтому логический кадр занимает 3 растра, пока работа укладывается в один; при работе 1..2 растра период становится 4.

1. Сцена

Уровень 11, комната 15. Проверено чтением состояния машины: pop_current_level = 0x0B, cur_room = 15.

кто где кадр
Кид (0,2), x=98 15 (стойка с мечом)
чомпер (0,3) застывший (trob мёртв)
факел (0,2) — пламя рисуется в ячейке (0,3) анимируется каждый кадр
факел (0,7) — пламя в ячейке (0,8) анимируется каждый кадр
страж (0,8), x=170 171 (боевая стойка)

Ни Кид, ни страж не двигаются: сцена статична, разброс замера — сотни тактов на 800 000.

2. Замер (09f32ce, база модуля roomtest.c = 0x42AD)

867 кадров, зонды A/C/D/E; детализация — тремя отдельными прогонами (m1, m5..m7, M/F). Период кадра: 4 растра во всех 866 интервалах.

участок зонды медиана доля работы
синяя: ввод + heal A→m1 141 048 17,6 %
синяя: логика m1→C 152 190 19,0 %
синяя, всего A→C 293 238 36,6 %
зелёная: pop_loose_tick C→m5 28 872 3,6 %
зелёная: pop_process_trobs m5→m6 92 448 11,5 %
зелёная: pop_redraw_needed m6→m7 190 260 23,7 %
зелёная: шов/ворота соседа m7→D 9 336 1,2 %
зелёная, всего C→D 320 916 40,0 %
циан: check_mirror + loose_mob_draw D→M 37 458 4,7 %
циан: Кид + страж + fore + HP M→F 148 566 18,5 %
циан: char_fore(KID) + борта F→E 1 740 0,2 %
циан, всего D→E 187 758 23,4 %
работа A→E 801 768 1,86 растра

Циан здесь не выделяется: 187 758 — это 0,44 растрового кадра, а разброс за 867 кадров всего 174 такта (187 674..187 848). Впечатление «циан ~150 % кадра» на глаз не подтвердилось — при периоде 4 растра полосы бордюра размазаны по кадрам и на глаз не читаются.

3. Главная находка: чомпер справа от факела — 190 260 тактов/кадр

pop_dbg_rdmax_tot = 1: за кадр перерисовывается РОВНО ОДИН тайл, и стоит он все 190 260 тактов зелёной фазы.

ПОПРАВКА 2026-08-19 (после реализации P1). Механизм ниже описан верно, но ГЛАВНЫМ источником 190 260 тактов он НЕ был. Зонд pop_dbg_kind показал, что все 312 перерисовок в прогоне — вид POP_RD_CHOMP (полная), и ни одной от факела: собственная пометка чомпера просто перебивала пометку соседа. Настоящая причина — в §6. Урок ровно тот, что уже записан в defer_unexplained_quirks: механизм, который правдоподобно объясняет цифру, ещё не доказан цифрой.

Цепочка:

  1. TORCH_ANIM_DIV = 1 — факел меняет кадр пламени КАЖДЫЙ логический кадр;
  2. пламя запекается в фон (pop_torch_draw, GFX_BANK_NORMAL), а канвас пламени 16×18 лежит в ячейке правого соседа (seg008:560) — то есть поверх чомпера;
  3. поэтому pop_process_trobs метит соседа: if (trob_rcode[i] == TILE_CHOMP) pop_set_redraw(tp + 1, POP_RD_CHOMP, 1) (порт set_redraw_anim_right);
  4. pop_chomp_redraw отвечает на пометку heal 32×64 + полный draw_tile.

Расхождение с оригиналом именно в шаге 4. set_redraw_anim_right метит слой anim, и оригинал возвращает только его (draw_tile_anim_toprightdraw_tile_anim_rightdraw_tile_anim) — одну графику чомпера поверх огня. Мы вместо этого стираем и пересобираем тайл целиком, со всеми слоями (draw_tile_right, base, bottom, loose), которые пламя вообще не трогало.

Цена по модели блита (blit_cost_model, 8791 + 198·h + 5,96·w·h): heal 32×64 ≈ 33 700, значит на один draw_tile уходит ≈ 156 000 — сходится с известным замером «полная запечка щебня 179 914».

Чомпер при этом застывший: своей анимации у него нет, поза не меняется, возвращать нужно ровно ту же графику поверх свежего пламени.

4. Что это даёт и куда смотреть дальше

Ранжирование по цене (доля от 801 768):

# участок такты что делать
1 redraw_needed: чомпер под факелом 190 260 вернуть только слой anim, как в оригинале — без heal и без остальных слоёв
2 синяя: логика двух Char 152 190 графики нет вообще; разобрать pop_check_can_guard_see_kid и два play_seq
3 циан: Кид + страж 148 566 оба будятся каждый кадр — метки фона от чомпера/факела накрывают обоих
4 синяя: heal двух Char 141 048 следствие того же: skip не срабатывает ни разу
5 process_trobs: два факела 92 448 ≈46 000 на факел при блите пламени 16×18 ≈ 14 000 — разобрать накладные
6 loose_tick 28 872 в комнате нет ни одной loose-плиты

Пункты 3 и 4 — одна тема: пока фон трогают каждый кадр, pop_char_skip_mask не может пропустить ни Кида, ни стража. Пламя метит узко (16×18), а вот pop_chomp_redraw метит весь тайл со свесом — то есть пункт 1 чинит и часть пунктов 3/4.

Связанные задачи: HEAL-WIDTH и G8 в perf_green_phase.md — та же болезнь (полный тайл там, где хватает полосы), но на другом материале.

6. Настоящая причина 190 260 тактов: перерисовка неизменной позы

Найдено при реализации P1, сверкой с animate_chomper (seg007:0448). Функция оригинала заканчивается так:

if ((curr_modifier & 0x7F) < 6) {
    redraw_at_trob();
}

То есть чомпер перерисовывается только пока фаза меньше 6 — пять кадров из пятнадцати (POP_CHOMPER_SPEED = 15). Это не оптимизация оригинала, а следствие таблицы поз: chomper_fram1 = {3,2,0,1,4,3,3}, и начиная с фазы 5 и до конца круга поза одна и та же — 3. Рисовать её десять кадров подряд значит рисовать ровно ту же картинку.

Мы же метили тайл БЕЗУСЛОВНО, каждый кадр, пока trob жив — то есть платили полный draw_tile плюс heal 32×64 за неизменную картинку в двух третях кадров. А trob у чомпера живёт, пока Кид в том же РЯДУ (animate_chomper снимает его только при фазе ≥ 6 и ушедшем Киде) — в 11/15 Кид стоит в (0,2), чомпер в (0,3), ряд один.

Что сделано:

  1. пометка только при фазе < 6, и на фазе 5 — на ОБЕ страницы дабл-буфера (она последняя рисуемая, её поза обязана лечь на обе; вторую страницу пометка догоняет в кадре фазы 6, где поза та же самая);
  2. пометка от факела (set_redraw_anim_right) переведена на новый вид POP_RD_CHOMP_ANIMpop_chomp_anim_draw: три блита графики чомпера поверх свежего пламени, без heal и без остальных слоёв — порт ветки redraw_frames_anim (seg008:0211);
  3. приоритет полной перерисовки над anim в pop_set_redraw — у оригинала это два независимых счётчика, и full побеждает.

Результат (замер, 552 кадра): полная перерисовка теперь в 40 % кадров, лёгкий возврат челюстей — в 60 %. Зелёная фаза: 291 888 в дорогом кадре против 183 414 в дешёвом.

работа зелёная
до P1 768 684 294 510
после P1, медиана 657 882 183 420
после P1, дорогой кадр (40 %) 765 936 291 888

110 802 на медиане при ожидании −160 000. Разница в том, что 40 % кадров по-прежнему платят полную цену: там поза реально меняется, и это уже не лишняя работа, а честная. Дальше её можно резать только раскладом draw_tile на части (P7) или сужением heal (P8).

5. Журнал правок по этой сцене

дата правка работа синяя зелёная циан
2026-08-19 базовый замер (09f32ce) 801 768 293 238 320 916 187 758
2026-08-19 P5: гейты холостого хода в pop_loose_tick 767 928 285 864 294 384 187 764
33 840 7 374 26 532 +6
2026-08-19 зонды для замера P2/P6 (временные) 768 684 286 518 294 510 187 761
2026-08-19 P1: чомпер — перерисовка только при фазе < 6 (медиана) 657 882 286 503 183 420 187 761
110 802 15 111 090 0

Оснастка P2/P6 стоит 756 тактов на кадр — замеры до и после сопоставимы.

Циан не изменился (+6 тактов — шум), и это ожидаемо: loose_tick целиком лежит в зелёной. Синяя просела на 7 374 без прямой причины в правке — скорее всего перераскладка кода банка 3 компилятором; проверять отдельно не стали, знак верный.

7. ТЯЖЁЛАЯ позиция: Кид на шаг правее [замер 2026-08-19]

Поставлена пользователем: один осторожный шаг вправо (x = 106 вместо 99, колонка та же). Спрайт Кида начинает пересекаться с тайлом (0,3), где одновременно чомпер и пламя факела — и skip_mask перестаёт его пропускать.

фаза лёгкая тяжёлая разница
синяя 259 500 257 520 1 980
зелёная (медиана) 181 068 180 870 198
циан 187 761 319 842 +132 081
работа (медиана) 628 542 758 358 +129 816
работа (максимум) 744 384 876 612

Период кадра: 4 растра в 335 кадрах, 5 растров в 123 (27 %). Это уже не «стабильно медленно», а рывки: каждый четвёртый кадр длиннее соседних.

Разбор циана показывает, куда ушли 132 тысячи:

участок лёгкая тяжёлая
check_mirror 3 198 3 198
mob_draw + guard_over_kid + skip_mask 34 374 18 672
pop_char_draw(KID) 204 54 738
соперник: char_draw + char_fore 145 896 149 748
fore_needed + char_fore(KID) + борта 4 356 93 486

То есть Кид из «пропущен за 204 такта» превращается в полноценного персонажа за ~144 000 — ровно столько же, сколько стоит страж.

Главный вывод замера: самая дорогая единичная статья кадра — это fore-проход персонажа. 62 778 у стража и ~89 000 у Кида, вместе около 152 000, то есть 20 % работы кадра. У Кида он дороже потому, что в его футпринте лежит чомпер, а у чомпера есть собственный передний слой (POP_CHOMP_FRAM_FOR), который перерисовывается поверх персонажа каждый кадр.

8. После P15 (точная метка «фон трогали»)

фаза лёгкая до лёгкая после тяжёлая до тяжёлая после
синяя 259 050 223 902 257 520 245 808
зелёная 181 494 181 494 180 870 181 761
циан 194 262 59 406 319 842 192 090
работа 628 542 464 796 758 358 617 487

В лёгкой позиции не рисуется НИ ОДИН персонаж (циан 59 406 — это уже только check_mirror, проверки и передний слой по пометкам). В тяжёлой рисуется один Кид: он действительно стоит под пламенем, а страж — нет.

Пятирастровые кадры в тяжёлой позиции исчезли (было 27 %), период стал ровно 4.

Полная очередь оптимизаций с оценками — perf_registry.md. Там же разложена цена одного блита фона по этапам (замер 2026-08-19, 1603 блита) и модель зелёной фазы этой сцены.