Зелёная и циан — раскладка ПОСЛЕ правок, журнал правок с коммитами, отрицательные результаты с объяснением причины (окно клипа в pop_floor_bake — три попытки), и что осталось: раскол draw_tile на девять узких частей по образцу seg008:01C7. 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 |