Замер 418 кадров, зонды A/C/D/E (база модуля roomtest 0x42AD — совпала с
прошлой сборкой, фикс ушёл в банк).
работа синяя зелёная циан
af189a1 916 458 142 830 546 900 270 510
40f0d46 873 930 158 874 417 630 379 482
ec1f384 878 550 158 880 419 526 379 488
Фиксы второго уровня на бюджет не повлияли: +4 620 работы и +1 896 зелёной
— шум прогона. Период: 3 растра в 392 кадрах, 4 в 24, 5 в одном, то есть за
бюджет вылезает только сам каскад.
Синяя и циан в бюджете 400 000; зелёная 419 526 — 1,05x цели и ниже
растрового кадра 430 000. Остаток на потом: раскол draw_tile на узкие части
и идея G8 (инвалидация соседнего тайла полосой 28 px вместо целых 60).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 KiB
ЦИАН фаза (персонажи + передний слой) — анализ и оптимизация
Рабочий документ: живёт между сессиями. Внизу журнал правок — каждая
запись с замером до/после. Сцена, рецепт воспроизведения и зонды —
perf_l13_room23.md; там же ответ про 8-битные
габариты. Парная фаза — perf_green_phase.md.
Границы фазы в roomtest.c: от PROF(6) (строка 620) до PROF(0)
(строка 677). Содержимое: pop_check_mirror, pop_loose_mob_draw,
соперник, pop_char_draw(KID), pop_loose_mob_draw_over, pop_fore_needed,
pop_hp_draw, pop_char_fore(KID), pop_cd_clear,
pop_room_clip_borders.
Бюджет: ≤ 400 000 тактов (растровый кадр = 430 000).
| состояние | было (2026-08-17, c312e4a) |
стало (af189a1) |
|---|---|---|
| покой, Кид пропущен | 20 550 | 20 550 |
| Кид перерисовывается, кусков нет | 193 000 | 192 000 |
| пик каскада (6 кусков + Кид) | 631 800 | 270 510 ✔ |
ЦЕЛЬ ФАЗЫ ВЫПОЛНЕНА (270 510 при бюджете 400 000, запас 32 %).
1. Что решило дело
C1. Пометки «фон трогали» в mob_render подавлены — −55 000
pop_loose_mob_tick помечает весь коридор куска одним вызовом, а три
блита внутри mob_render метили подмножества того же прямоугольника по
4 502 такта каждый. Механизм — pop_cd_mute()/pop_cd_unmute()
(в pop_tile.c; отдельное значение того же флага pop_cd_batch, чтобы у
pop_cd_touch на общем пути осталась ОДНА проверка).
Добавлена пометка в mob_spawn_copy: кусок, рождённый ВНУТРИ тика
(loose_fall сбил плиту), получает слот с начала таблицы, то есть уже
пройденный циклом, — своей пометки в этом кадре он бы не получил, а нарисован
был бы. Без этого пропущенная пометка = стёртый и не перерисованный
персонаж.
C4. Кусок клипуется САМ, вместо чистки бортов после — −138 000
Самая крупная и самая неожиданная статья. В mob_render стоял
pop_clip_sprite, то есть кусок рисовался в борт целиком и взводил
border_dirty; pop_room_clip_borders потом стирал ДВЕ полосы во всю ширину
экрана (320×28 и 320×28) — 150 978 тактов в КАЖДОМ кадре, пока хоть один
кусок торчит выше поля. А гряда 13-го уровня рождается ровно у потолка
(y = 2), то есть почти весь каскад. Стало 1 722.
Теперь окно клипа (pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H))
ставится ТОЛЬКО когда кусок реально задевает борт: внутри поля блиты идут
быстрым путём.
Композит куска: три блита → один — −163 000
Части env 70 / 74 / 72 складываются в ОДИН getimage-блоб при загрузке
тайлсета (mob_spr_build в pop_room.c). Мотив прямо из
blit_cost_model: у блита ~8 800 такта постоянных накладных против ~5 000
на пиксели, а шесть кусков в воздухе давали 18 вызовов = 258 708 такта,
больше половины фазы.
Тонкости, которые пришлось соблюсти:
- части перекрываются (74 и 70 обе идут от
mob_x), поэтому композит собирается попиксельно с пропуском0xFF— ровно как три прозрачных блита друг поверх друга; - габариты частей читаются, а не берутся константами: у тайлсетов правая часть разная (26 px в подземелье, 25 во дворце);
- блоб лежит в обычной памяти (W2), поэтому блит идёт мимо
atlas_imageиgfx_w0_map/unmap— ещё ~1 350 такта на вызов. Новый резидентный листpop_mem_b(pop_tile.c); - страйд блоба = его ширина; сначала считается точный габарит, потом копирование. Промежуточная версия объявляла блоб шириной 63 при фактических 58 и переносила пять прозрачных колонок на каждом кадре;
- собирается на КАЖДУЮ смену тайлсета; резервный путь на три блита остался
(
mob_spr_ok).
Общие правки, попавшие и в эту фазу
blit_b_clip: байтовый габарит + file-scope вместо локалей (кадр 22 → 12 Б, обращений(ix)211 → 51). Подробности — вperf_green_phase.md§G3.pop_blit_b: аргументы в file-scope (76 → 11 обращений(ix)).
2. Раскладка на 2026-08-17 (до правок) — для истории
Подфазы (три готовых PROF(6): 0x4BCE / 0x4C26 / 0x4C93), пик:
| участок | покой | пик |
|---|---|---|
pop_check_mirror + pop_loose_mob_draw + соперник |
23 250 | 154 512 |
pop_char_draw(KID) + pop_loose_mob_draw_over + pop_fore_needed + HP |
3 726 | 445 284 |
pop_char_fore(KID) + pop_cd_clear + чистка бортов |
1 722 | 150 978 |
Разбор одного pop_blit_b (157 замеров быстрого пути, зонды b1..b5):
| участок | такты | доля |
|---|---|---|
atlas_image + gfx_w0_map + чтение габарита |
1 086 | 7 % |
ядро блита (libbgi, gfx_blit_noclip) |
10 422 | 64 % |
pop_cd_touch — пометка «фон тронут» |
4 502 | 28 % |
gfx_w0_unmap + возврат |
264 | 2 % |
| ИТОГО | 16 273 |
Клипованный путь тогда же: ядро blit_b_clip 14 088, итого 16 409.
После правок: клипованный блит 11 848, быстрый ~13 900 (у него больше
пикселей).
3. Что осталось в запасе (если понадобится ещё)
Фаза в бюджете, поэтому это задел, а не план.
C5. Один gfx_w0_map/unmap на группу блитов
1 086 + 264 на вызов. Для композита куска уже не нужно (он в обычной
памяти), но остаётся для тайлов фона: куски одного тайла часто лежат в одной
странице атласа. Мешает то, что pop_blit_b — общий лист для всех
вызывающих; нужна форма «открыть страницу, N блитов, закрыть».
C6. Размеры ленты — из каталога атласа
fw/fh уже лежат в записи каталога (8 байт: offset u16, fw u8, fh u8, nx u8, ny u8, резерв u16), и у всех фоновых лент nx = ny = 1, то есть они
равны w/h из шапки. atlas_image их читает и выбрасывает.
C7. objtable вместо отдельного fore-прохода на персонажа
Позиции 5 и 6 старого perf_backlog.md — большой рефакторинг. Оригинал
кладёт персонажей и куски в objtable и рисует их при обходе тайлов
(draw_objtable_items_at_tile), порядок окклюзии получается сам; у нас
отдельный fore-проход НА КАЖДОГО персонажа.
Снять временную оснастку
Шесть вызовов pop_dbg_b1..b6 внутри pop_blit_b — ~400 такта на блит
(call + ret × 6), плюс pop_dbg_kind/m16 в pop_redraw_needed.
Снимать ПОСЛЕ того, как оптимизация закончена: без них не мерить.
4. Что НЕ делать
- Не ставить W3-скобку из кода с
--w3— белый экран (memorygfx_blit_noclip_fast). - Не ускорять передачу пикселей — предел железа
(memory
blit_cost_model). - Не возвращать клип куска по
clip.right = 40оригинала (add_mob_to_objtable, seg007:1161): единицы этого поля не выяснены, буквальные 40 экранных пикселей срезают правый задний угол плиты (прогон 2026-08-13). - Не сужать коридор heal «по палаццовому следу» — габариты частей у тайлсетов разные; брать высоту СОБРАННОГО композита (так и сделано).
5. Журнал правок
| дата | что сделано | циан: покой / Кид / пик | коммит |
|---|---|---|---|
| 2026-08-17 | базовый замер | 20 550 / 193 000 / 631 800 | c312e4a |
| 2026-08-17 | C1 подавление пометок в mob_render + пометка в mob_spawn_copy |
— / — / 577 050 | a3c473d |
| 2026-08-17 | C4 кусок клипуется сам вместо чистки бортов; blit_b_clip байты+file-scope |
— / — / 438 546 | a3c473d |
| 2026-08-17 | pop_blit_b аргументы в file-scope |
— / — / 435 180 | b2da0b8 |
| 2026-08-17 | композит куска: один блит вместо трёх | — / — / 273 078 | b2da0b8 |
| 2026-08-17 | точный габарит композита (было 63 при 58) | 20 550 / 192 000 / 270 510 ✔ | af189a1 |
| 2026-08-17 | замер после фиксов уровня 1 | — / — / 379 482 | 40f0d46 |
| 2026-08-17 | регресс после фиксов уровня 2 — без изменений | — / — / 379 488 ✔ | ec1f384 |