Документы по фазам: результаты дня и оставшийся план
Зелёная и циан — раскладка ПОСЛЕ правок, журнал правок с коммитами, отрицательные результаты с объяснением причины (окно клипа в pop_floor_bake — три попытки), и что осталось: раскол draw_tile на девять узких частей по образцу seg008:01C7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,169 +13,137 @@
|
||||
|
||||
**Бюджет: ≤ 400 000 тактов (растровый кадр = 430 000).**
|
||||
|
||||
| состояние | циан |
|
||||
|---|---:|
|
||||
| покой, Кид пропущен (`pop_char_skip_mask`) | 20 550 … 28 700 |
|
||||
| Кид перерисовывается, кусков нет | 107 000 … 193 000 |
|
||||
| **пик каскада (6 кусков в воздухе + Кид)** | **632 000 (1,6× бюджета)** |
|
||||
| состояние | было (2026-08-17, `c312e4a`) | стало (`af189a1`) |
|
||||
|---|---:|---:|
|
||||
| покой, Кид пропущен | 20 550 | 20 550 |
|
||||
| Кид перерисовывается, кусков нет | 193 000 | 192 000 |
|
||||
| **пик каскада (6 кусков + Кид)** | **631 800** | **270 510 ✔** |
|
||||
|
||||
**ЦЕЛЬ ФАЗЫ ВЫПОЛНЕНА** (270 510 при бюджете 400 000, запас 32 %).
|
||||
|
||||
---
|
||||
|
||||
## 1. Раскладка (замер 2026-08-17, `c312e4a`)
|
||||
## 1. Что решило дело
|
||||
|
||||
### 1.1 Подфазы (три готовых `PROF(6)`: 0x4BCE / 0x4C26 / 0x4C93)
|
||||
### C1. Пометки «фон трогали» в `mob_render` подавлены — −55 000
|
||||
|
||||
| участок | покой | пик (кадр 20) | максимум |
|
||||
|---|---:|---:|---:|
|
||||
| `pop_check_mirror` + `pop_loose_mob_draw` (куски ПОД Кидом) + соперник | 23 250 | 34 950 | **154 512** |
|
||||
| `pop_char_draw(KID)` + `pop_loose_mob_draw_over` + `pop_fore_needed` + HP | 3 726 | **445 284** | 445 284 |
|
||||
| `pop_char_fore(KID)` + `pop_cd_clear` + чистка бортов | 1 722 | **150 978** | 150 978 |
|
||||
`pop_loose_mob_tick` помечает **весь коридор** куска одним вызовом, а три
|
||||
блита внутри `mob_render` метили подмножества того же прямоугольника по
|
||||
**4 502 такта** каждый. Механизм — `pop_cd_mute()`/`pop_cd_unmute()`
|
||||
(в `pop_tile.c`; отдельное значение того же флага `pop_cd_batch`, чтобы у
|
||||
`pop_cd_touch` на общем пути осталась ОДНА проверка).
|
||||
|
||||
### 1.2 Блиты кусков
|
||||
Добавлена пометка в `mob_spawn_copy`: кусок, рождённый ВНУТРИ тика
|
||||
(`loose_fall` сбил плиту), получает слот с начала таблицы, то есть уже
|
||||
пройденный циклом, — своей пометки в этом кадре он бы не получил, а нарисован
|
||||
был бы. Без этого пропущенная пометка = стёртый и не перерисованный
|
||||
персонаж.
|
||||
|
||||
Блитов в циане — **19 на кадр**: шесть кусков × три части спрайта
|
||||
(`env 74` = 32×3, `env 70` = 32×13, `env 72` = 26×16, порт `draw_mob`
|
||||
seg007:13E5 / индексы `loose_fram_*[10]`).
|
||||
### C4. Кусок клипуется САМ, вместо чистки бортов после — −138 000
|
||||
|
||||
| | такты |
|
||||
|---|---:|
|
||||
| Σ 19 блитов | **313 758** |
|
||||
| на вызов | **16 273** |
|
||||
Самая крупная и самая неожиданная статья. В `mob_render` стоял
|
||||
`pop_clip_sprite`, то есть кусок рисовался в борт целиком и взводил
|
||||
`border_dirty`; `pop_room_clip_borders` потом стирал ДВЕ полосы во всю ширину
|
||||
экрана (320×28 и 320×28) — **150 978 тактов в КАЖДОМ кадре**, пока хоть один
|
||||
кусок торчит выше поля. А гряда 13-го уровня рождается ровно у потолка
|
||||
(`y = 2`), то есть почти весь каскад. Стало 1 722.
|
||||
|
||||
То есть **половина фазы — это девятнадцать вызовов `pop_blit_b`.**
|
||||
Теперь окно клипа (`pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H)`)
|
||||
ставится ТОЛЬКО когда кусок реально задевает борт: внутри поля блиты идут
|
||||
быстрым путём.
|
||||
|
||||
### 1.3 Внутри одного `pop_blit_b` (157 замеров быстрого пути, зонды b1..b5)
|
||||
### Композит куска: три блита → один — −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 %** |
|
||||
| `pop_cd_touch` — пометка «фон тронут» | 4 502 | 28 % |
|
||||
| `gfx_w0_unmap` + возврат | 264 | 2 % |
|
||||
| ИТОГО | 16 273 | |
|
||||
|
||||
Для сравнения, клипованный путь (227 замеров, полоса у потолка): ядро
|
||||
`blit_b_clip` **14 088**, итого на вызов **16 409**.
|
||||
|
||||
По модели `blit_cost_model` «железный» минимум передачи для этих трёх
|
||||
спрайтов — 5 000 … 6 200 тактов. Остальные ~10 000 на вызов — накладные.
|
||||
|
||||
### 1.4 `pop_char_fore(KID)` вырастает в каскаде на два порядка
|
||||
|
||||
В покое 1 722, в кадрах каскада **150 978**. Кид стоит у правого края и НЕ
|
||||
двигается, плиты падают в колонках 2..7 — то есть либо пропуск персонажа
|
||||
(`pop_char_skip_mask`) гасится пометками `pop_cd_touch` от кусков, либо окно
|
||||
fore-клипа расширяется и в него попадают лишние тайлы. **Не разобрано** —
|
||||
см. C4.
|
||||
|
||||
### 1.5 Кодоген
|
||||
|
||||
| функция | стековый кадр | обращений `(ix)` | ожидаемо тактов |
|
||||
|---|---:|---:|---:|
|
||||
| `blit_b_clip` (резидент) | **22 Б** | **211** | ~9 700 |
|
||||
| `pop_blit_b` (резидент) | 6 Б | 78 | ~3 600 |
|
||||
| `pop_cd_touch` (резидент) | — | 30 | ~1 380 |
|
||||
| `mob_draw_pass` (bank 7) | 18 Б | 20 | ~900 |
|
||||
| `mob_render` (bank 7) | — | 1 | — |
|
||||
|
||||
Одно `-N(ix)` ≈ 19 номинальных тактов ≈ 46 замеренных
|
||||
(memory `sprinter_wait_states_2x`).
|
||||
Клипованный путь тогда же: ядро `blit_b_clip` 14 088, итого 16 409.
|
||||
После правок: клипованный блит 11 848, быстрый ~13 900 (у него больше
|
||||
пикселей).
|
||||
|
||||
---
|
||||
|
||||
## 2. Способы ускорения
|
||||
## 3. Что осталось в запасе (если понадобится ещё)
|
||||
|
||||
По убыванию отдачи.
|
||||
|
||||
### C1. Убрать избыточный `pop_cd_touch` в `mob_render`
|
||||
|
||||
**Отдача: −81 000 (13 % фазы). Самая дешёвая правка из всех.**
|
||||
|
||||
`pop_loose_mob_tick` уже помечает **весь коридор куска** одним вызовом
|
||||
`pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64)` — с запасом на
|
||||
путь, пройденный за кадр. А потом три блита внутри `mob_render` помечают
|
||||
подмножества этого же прямоугольника, по 4 502 такта каждый: 3 × 4 502 × 6
|
||||
кусков = **81 000 тактов впустую**.
|
||||
|
||||
Механизм уже есть: `pop_cd_batch_begin()` / `pop_cd_batch_end()` (в
|
||||
`pop_tile.c`, применяется в `draw_tile`) — в пакете `pop_cd_touch` только
|
||||
копит общий прямоугольник четырьмя сравнениями. Обернуть `mob_render` в
|
||||
скобку.
|
||||
|
||||
Осторожно: пометка обязана покрывать **весь коридор heal'а**, а не габарит
|
||||
спрайта — иначе персонаж, попавший в расширенную часть коридора, стирается,
|
||||
но перерисовать себя не просит и исчезает с экрана (ровно этот баг ловили
|
||||
2026-08-13: в комнате 23 после падения гряды пропадал Кид). Поскольку
|
||||
коридорную пометку из тика мы НЕ трогаем, инвариант сохраняется.
|
||||
|
||||
### C2. `pop_cd_touch` — байтовые аргументы
|
||||
|
||||
**Отдача: часть от 4 502 на вызов; после C1 в циане останется 6 вызовов
|
||||
(по одному на кусок), но в зелёной фазе и в fore-проходе их больше.**
|
||||
|
||||
Четыре `int`-аргумента на вызов, 30 `(ix)` внутри. `w`/`h` заведомо ≤ 255
|
||||
(габарит спрайта ≤ 56×63), `x`/`y` — экранные, 16-битные. Это позиция (а)
|
||||
задачи OPT-BLIT.
|
||||
|
||||
### C3. `blit_b_clip` — 22 Б кадра, 211 `(ix)`
|
||||
|
||||
**Отдача: 14 088 → ~10 500 на клипованный блит.** В циане клипованный путь
|
||||
берут спрайты у краёв поля и весь fore-проход; в зелёной фазе — все блиты
|
||||
полосы у потолка. Подробнее — [`perf_green_phase.md`](perf_green_phase.md) §G3.
|
||||
|
||||
`sx/sy/dw/dh` → `uint8_t`, `dx/dy` оставить 16-битными. Прецедент: тот же
|
||||
приём в `pop_blit_b` (коммит `c312e4a`) дал −19 % на клипованном пути.
|
||||
|
||||
### C4. Разобрать `pop_char_fore(KID)`: 1 722 → 150 978
|
||||
|
||||
**Отдача неизвестна, но это 24 % фазы — мерить обязательно.**
|
||||
|
||||
Гипотезы (проверять зондами, не рассуждением — memory
|
||||
`defer_unexplained_quirks`):
|
||||
|
||||
1. пометки `pop_cd_touch` от кусков гасят `pop_char_skip_mask` для Кида, хотя
|
||||
куски падают в других колонках — проверить `pop_cd_dmask` в кадре;
|
||||
2. `mob_mark_neighbour` ставит `pop_set_redraw_fore` на 1–2 тайла на каждый
|
||||
кусок каждый кадр (порт `draw_mob`, seg007:1147), и `pop_fore_needed`
|
||||
перерисовывает передние части 6–12 тайлов;
|
||||
3. окно fore-клипа Кида расширено под клинок и брызги, и в него попадает
|
||||
больше тайлов, чем нужно — известный пункт 1 в `perf_backlog.md`
|
||||
(«футпринт персонажа — брать из физики»): оригинал расширяет футпринт
|
||||
только на одну колонку при вынутом мече и объединяет с футпринтом прошлого
|
||||
кадра, а мы расширяем окном клипа.
|
||||
|
||||
Зондов на подфазы циана больше нет — понадобятся временные `pop_dbg_*`
|
||||
вокруг `pop_fore_needed` и `pop_char_draw`, то есть пересборка (после неё
|
||||
ВСЕ адреса зондов меняются, брать заново из `roomtest.map`).
|
||||
Фаза в бюджете, поэтому это задел, а не план.
|
||||
|
||||
### C5. Один `gfx_w0_map`/`unmap` на группу блитов
|
||||
|
||||
**Отдача: 1 086 + 264 = 1 350 на вызов; три части одного куска лежат в одной
|
||||
странице атласа → −2 700 на кусок, −16 000 на кадр.**
|
||||
|
||||
Позиция 3 старого `perf_backlog.md`. Нужна форма «открыть страницу, N
|
||||
блитов, закрыть»; мешает то, что `pop_blit_b` — общий лист для всех
|
||||
вызывающих. Для `mob_render` (три блита из одного атласа `pop_env`) частный
|
||||
случай тривиален.
|
||||
1 086 + 264 на вызов. Для композита куска уже не нужно (он в обычной
|
||||
памяти), но остаётся для тайлов фона: куски одного тайла часто лежат в одной
|
||||
странице атласа. Мешает то, что `pop_blit_b` — общий лист для всех
|
||||
вызывающих; нужна форма «открыть страницу, N блитов, закрыть».
|
||||
|
||||
### C6. Размеры ленты — из каталога атласа
|
||||
|
||||
**Отдача: часть от 1 086 на вызов.** Позиция 2 старого `perf_backlog.md`:
|
||||
`fw`/`fh` уже лежат в записи каталога (8 байт: `offset u16, fw u8, fh u8,
|
||||
nx u8, ny u8, резерв u16`), и у всех фоновых лент `nx = ny = 1`, то есть они
|
||||
в точности равны `w`/`h` из шапки. `atlas_image` их читает и выбрасывает.
|
||||
равны `w`/`h` из шапки. `atlas_image` их читает и выбрасывает.
|
||||
|
||||
### C7. objtable вместо отдельного fore-прохода на персонажа
|
||||
|
||||
Позиции 5 и 6 старого `perf_backlog.md` — большой рефакторинг, браться только
|
||||
если после C1–C5 бюджет всё ещё не выполняется. Оригинал кладёт персонажей и
|
||||
куски в `objtable` и рисует их при обходе тайлов
|
||||
(`draw_objtable_items_at_tile`), а порядок окклюзии получается сам; у нас
|
||||
Позиции 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`.
|
||||
Снимать ПОСЛЕ того, как оптимизация закончена: без них не мерить.
|
||||
|
||||
---
|
||||
|
||||
## 3. Что НЕ делать
|
||||
## 4. Что НЕ делать
|
||||
|
||||
- **Не ставить W3-скобку из кода с `--w3`** — белый экран
|
||||
(memory `gfx_blit_noclip_fast`).
|
||||
@@ -185,11 +153,18 @@ nx u8, ny u8, резерв u16`), и у всех фоновых лент `nx = n
|
||||
(`add_mob_to_objtable`, seg007:1161): единицы этого поля не выяснены,
|
||||
буквальные 40 экранных пикселей срезают правый задний угол плиты
|
||||
(прогон 2026-08-13).
|
||||
- **Не сужать коридор heal «по палаццовому следу»** — габариты частей у
|
||||
тайлсетов разные; брать высоту СОБРАННОГО композита (так и сделано).
|
||||
|
||||
---
|
||||
|
||||
## 4. Журнал правок
|
||||
## 5. Журнал правок
|
||||
|
||||
| дата | что сделано | циан: покой / Кид / пик | коммит |
|
||||
|---|---|---|---|
|
||||
| 2026-08-17 | базовый замер (до правок этой серии) | 20 550 / 193 000 / **632 000** | `c312e4a` |
|
||||
| 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` |
|
||||
|
||||
Reference in New Issue
Block a user