31b82661eb
Порт PoP переехал в applications/SprPoP — приложение, которое собирается само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной папки. Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT, по умолчанию ../..). applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся архивом закрытых задач, багов и исполненных планов. Скопировано из applications/PoP/roomtest@4b74478. Перенос проверен побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита, все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host- тесты зелёные (15/15). Раскладка: src/ рукописный C (roomtest.c -> sprpop.c) gen/ генерируемые заголовки, в репозитории assets/orig/ оригинальные данные игры, вне репозитория (копирайт) assets/packed/ то, что ложится на диск, в раскладке диска tools/ конверторы; все пути — в одном tools/paths.py build/ выход: exe, каталоги ресурсов, hdd/, промежуточные atl/ Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что пересчитывается каждым make. Автоматика построена на ОТСУТСТВИИ файла, а не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по времени превращалось бы в лотерею. Недостающий ресурс или заголовок чинится сам, рекурсивным вызовом в ветку генерации. Музыка собирается из любого из четырёх наборов записей (make music-mp3, music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама делает музыку устаревшей. Длины реплик больше не захардкожены: упаковщик печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них — иначе mt32 (реплики на 6% длиннее) молча ломал катсцену. Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR), HDD_IMG стал ?=; команда сборки roomtest не изменилась. Корневой make host-tests переключён на SprPoP. Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики приходила раньше молнии. Это обход, а не лечение; разбор с замерами — docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
173 lines
11 KiB
Markdown
173 lines
11 KiB
Markdown
# ЦИАН фаза (персонажи + передний слой) — анализ и оптимизация
|
||
|
||
Рабочий документ: живёт между сессиями. Внизу **журнал правок** — каждая
|
||
запись с замером до/после. Сцена, рецепт воспроизведения и зонды —
|
||
[`perf_l13_room23.md`](perf_l13_room23.md); там же ответ про 8-битные
|
||
габариты. Парная фаза — [`perf_green_phase.md`](perf_green_phase.md).
|
||
|
||
Границы фазы в `sprpop.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`](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`** — белый экран
|
||
(memory `gfx_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` |
|