Files
Sprinter-SDCC/applications/PoP/docs/perf_l13_room23.md
T
snark13 babd40bc84 Профиль каскада плит ур.13 к.23: три рабочих документа по фазам
Контрольный замер перед оптимизацией (задача OPT-BLIT требовала начать с
него, а не с правок).  Сцена: старт уровня 13, комната 23, шесть плит
сыплются с потолка вразнобой.

Пик логического кадра — 1 437 150 тактов при растровом кадре 430 000, то
есть 5-6 растровых кадров вместо трёх.  По секциям (бюджет 400 000):
синяя 142 830 (в норме), зелёная до 805 000, циан до 632 000.

Что нашлось (всё подтверждено зондами, не гипотезы):

  RDA_CEIL      дрожащая плита-потолок     48 658 x до 6 = 292 000
  RDA_CEIL_GONE запечь колодец            251 023 x до 2 = 619 000
  RD_FLOOR      щебень на месте посадки   198 259 x до 2 = 397 000

Внутри RDA_CEIL: heal 8 491 + draw_tile 39 029, а блитов в этом draw_tile
всего 1,17 на тайл (15-16 тыс.) — то есть ~23 700 тактов чистых накладных.
Подсчёт asm сходится до процентов: у draw_tile 26-байтовый стековый кадр и
513 обращений (ix), при ~46 замеренных тактах на обращение это те же
~23 600.  У blit_b_clip — 22 Б кадра и 211 (ix).

Разбор одного pop_blit_b (157 замеров быстрого пути): atlas_image+w0_map
1 086, ядро libbgi 10 422, pop_cd_touch 4 502 (28%!), unmap+возврат 264.
В mob_render эти три pop_cd_touch к тому же ИЗБЫТОЧНЫ — коридор куска уже
помечен одним вызовом в pop_loose_mob_tick: 81 000 тактов в кадре впустую.

Итог по пиковому кадру: блиты 488 100 (34%), из них «железный» минимум
пикселей ~150 000; остальные ~1 150 000 (80% кадра) — наши накладные.
Узкое место не передача пикселей, а 16-битная арифметика в IX-кадрах.

Ответ на вопрос про uint8_t: просканированы все 109 .atl и наборы
TITLE/PV — максимум по игровому кадру 56x63 (kid3), фон 48x63, спрайты
комнаты принцессы 49x60.  Шире 255 только восемь полноэкранных подложек
титров/сюжета (три 320x200 и пять 256..272), и все рисуются раз на экран.
Значит горячий путь переводится на байтовые габариты целиком, а подложкам —
отдельный банк / прямой libbgi (решение пользователя).

Документы (решение пользователя — вести работу по фазам, чтобы результаты
не теряись между сессиями):

  docs/perf_l13_room23.md  сцена, рецепт воспроизведения, зонды, канал clog,
                           сводка по кадрам, габариты спрайтов
  docs/perf_green_phase.md зелёная фаза: раскладка, позиции G1..G6, журнал
  docs/perf_cyan_phase.md  циан: раскладка, позиции C1..C7, журнал

Отдельно записан рецепт воспроизведения: сцена повторяется ТОЛЬКО
перезапуском программы (ESC → зонды → roomtest).  Возврат в комнату не
годится — упавшая плита уходит в страницу уровня насовсем; рестарт уровня
не годится — гряда доваливается заочно через trob комнаты 17, пока телепорт
уносит Кида; запись отладчиком на бегущей машине затирается тем же trob.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 12:22:21 +03:00

169 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Сцена и метод замера: каскад плит, уровень 13 комната 23
Общий документ для двух фазовых: [`perf_green_phase.md`](perf_green_phase.md)
(слой фона) и [`perf_cyan_phase.md`](perf_cyan_phase.md) (персонажи + передний
слой). Здесь — как воспроизвести сцену, чем мерить, сводка по кадрам и
разбор габаритов спрайтов (он общий для обеих фаз).
Сцена: старт уровня 13. Комната 23 стартовая, ряд 2 комнаты СВЕРХУ (17) —
шесть loose-плит в колонках 2..7 (`res2013.bin`: коды `11` в позициях 22..27),
`check_fall_flo` раздаёт им отложенный старт `0xF0..0xFF`, и они сыплются
вразнобой. Кид стоит у правого края и не двигается.
Все числа — такты `totalcycles` MAME (системный клок ~21,5 МГц, **НЕ** такты
Z80: у ОЗУ Sprinter wait-state'ы, ≈2,4× номинала — memory
`sprinter_wait_states_2x`). **Растровый кадр = 430 000.** Логический кадр
спейсится тремя `gfx_wait_vsync`, поэтому работа сверх 430 000 стоит сразу
целый лишний растровый кадр.
Сборка: `make LEVEL=13` на `c312e4a`, `_CODE = 0x4100`, база модуля
`roomtest.c` = **0x42AD**. **Адреса зондов меняются после КАЖДОЙ
пересборки** — брать заново из `.sprinter-cc-roomtest/roomtest.map` и
`roomtest.lst`.
---
## 1. Как воспроизвести сцену
**Только перезапуском программы.** Проверено и отвергнуто:
- **выход из комнаты и возврат** (чит `+`/`-`) — не работает: провалившаяся
плита-потолок уходит в страницу уровня насовсем (`pop_level_set_tile` в
`roomtest.c` по сигналу `pop_ceil_fell`, плюс `animate_loose` в
`pop_trob.c`), и при повторном входе `check_fall_flo` не находит ни одной
`TILE_LOOSE`;
- **рестарт уровня** (`pop_kid_dead = 1` + чит навигации) — не работает по
другой причине: `pop_start_level()` сам заходит в стартовую комнату 23,
взводит гряду, и она доваливается ЗАОЧНО (через `trob` комнаты 17), пока
телепорт уносит Кида в комнату 24;
- **поставить сцену руками** (записать `pop_ceil_modif[2..7]` и копию ряда
сверху `pop_t_above[2..7]` отладчиком) — записи ложатся, но пока машина
БЕЖИТ, их успевает обнулить тот же доваливающийся `trob`.
Рабочий рецепт (идея пользователя, самый чистый): **`ESC` → зонды →
`roomtest`**. `ESC` выходит в DSS, запуск заново стартует уровень 13 с нуля,
Кид сразу в комнате 23, каскад начинается через ~5 логических кадров после
отрисовки комнаты. Зонды обязаны стоять **ДО** набора `roomtest` — за время
набора (9 клавиш ≈ 1,8 с) и загрузки атласов каскад успевает пройти целиком.
## 2. Канал вывода замеров
`printf` из действия брейкпоинта в `error.log` **не** попадает. Читается
verb'ом **`clog N`** плагина `mamebridge` — а его нет в MCP-обёртке
(`mame_mcp.py` знает только `cmd`). Годится прямой файловый IPC:
положить `/tmp/mame_mcp/req_<ЧИСЛО>.txt` с телом команды и прочитать
`resp_<ЧИСЛО>.txt`. **Имя обязано содержать ЧИСЛО** (`init.lua`:
`entry:match("^req_(%d+)%.txt$")`) — с буквенным id запрос молча не
обслуживается.
Скрипты сессии (в scratchpad, при необходимости пересоздать): `mrpc.py`
клиент IPC; `run.sh` — цикл «`bpclear``ESC` → зонды → `roomtest``clog`»;
`parse*.py` — разбор трассы по кадрам.
Форма зонда: `bpset <addr>,1,{printf "<метка> %d",totalcycles; g}`.
Для `pop_dbg_kind``printf "K %d %d",a,totalcycles` (аргумент `uint8_t`
приходит в `A`, `__sdcccall(1)`).
## 3. Зонды
Адреса `out (_io_border), a` (полосы бордюра) из `roomtest.lst` плюс
однобайтовые пустышки `pop_dbg_*` из резидентного `pop_state.c`. Резидент
важен принципиально: у банковых функций один адрес 0xC000+ есть у восьми
модулей сразу, и брейкпоинт ловит все банки (так в прошлой сессии намерили
несуществующие 134 730 тактов).
| зонд | адрес | что |
|---|---|---|
| A | 0x43E8 | `PROF(2)` — начало кадра (ввод + heal) |
| — | 0x46AF | `PROF(2)` — начало логики |
| C | 0x47D1 | `PROF(4)` — начало слоя фона (**зелёная**) |
| D | 0x4BCE | `PROF(6)` — начало спрайтов (**циан**) |
| M | 0x4C26 | `PROF(6)` — кадр Кида |
| F | 0x4C93 | `PROF(6)` — fore поверх Кида |
| E | 0x4CB7 | `PROF(0)` — конец работы, ждём vsync |
| m5/m6/m7 | 0x4DC6 / C7 / C8 | границы внутри зелёной |
| m9..m12 | 0x4DCA..CD | внутренности `pop_loose_tick` |
| m13/m14/m15 | 0x4DCE / CF / D0 | `pop_ceil_shake_draw`: вход / heal / draw_tile |
| kind / m16 | 0x4DD1 / D2 | вид и цена одной перерисовки в `pop_redraw_needed` |
| b1..b5 | 0x4DD3..D7 | участки одного `pop_blit_b` |
Полезные адреса состояния (из `roomtest.map`): `pop_t_room` 0x95F2,
`pop_current_level` 0x9945, `pop_ceil_modif` 0x9C9E, `pop_t_above` 0x95EE
(указатель), `pop_kid_dead` 0x9C4E, `pop_dbg_rdmax` 0x95B1.
Запись в память через MCP — по адресу `0x10000 | addr` (логический вид Z80);
присваивание выражением дебаггера (`print b@... = 1`) **не работает**.
**Грабли:** проверять, что запущен РОВНО ОДИН MAME (`pgrep -f mame.arm | wc -l`).
Мост говорит с одним, замеры собираются с другого, и точки «не срабатывают».
---
## 4. Сводка по кадрам
| фаза каскада | работа | синяя | зелёная | циан | период (растр.) |
|---|---:|---:|---:|---:|---:|
| покой в комнате 23 | 190 860 | 134 550 | 35 760 | 20 550 | **3** |
| дрожат 6 плит | 537 400 | 142 700 | 366 000 | 28 700 | **4** |
| провалы + полёт, ПИК | **1 437 150** | 142 700 | **663 250** | **631 200** | **56** |
| максимум по секции | | 142 830 | **805 000** | **631 800** | |
Цель — каждая секция ≤ 400 000. **Синяя в норме**; зелёная 2,0× бюджета,
циан 1,6×. Логический кадр вместо 3 растровых занимает 5–6: в каскаде игра
идёт вдвое медленнее нормы.
Где что расходуется и как это чинить — в фазовых документах:
[зелёная](perf_green_phase.md), [циан](perf_cyan_phase.md).
### Общий вывод по пиковому кадру (1 437 150)
| | такты | доля |
|---|---:|---:|
| блиты (все 27–28 вызовов `pop_blit_b`) | 488 100 | 34 % |
| из них «железный» минимум пикселей (модель `198*h + 5,96*w*h`) | ~150 000 | 10 % |
| синяя (ввод + heal + логика) | 142 700 | 10 % |
| **наши накладные: `draw_tile`, IX-кадры, диспетчер, пометки** | **~1 150 000** | **~80 %** |
Узкое место — НЕ передача пикселей (она на пределе железа, 3+3 такта на байт,
memory `blit_cost_model`), а 16-битная арифметика в стековых кадрах.
---
## 5. Габариты спрайтов: можно ли всё перевести на `uint8_t`
Просканированы каталоги ВСЕХ `.atl` (109 файлов) и исходные PNG наборов
`TITLE`/`PV` — тех, что понадобятся для интро, финала и роликов между
уровнями.
**Игровой кадр — весь укладывается в байт:**
| набор | максимум |
|---|---|
| фон подземелья/дворца (`*_env*`, `*_wall`, `*_fore`, `pop_pot`) | **48 × 63** |
| Кид (`kid0..27`, `sword`) | **56 × 63** (kid3, idx 0) |
| страж / скелет / Джафар | **53 × 42** |
| спрайты комнаты принцессы (`PV.DAT`: персонажи, песочные часы, факел, звёзды) | **49 × 60** |
**Больше 255 — только полноэкранные подложки титров и сюжетных экранов.**
Их восемь, и все рисуются ОДИН раз при показе экрана:
| ресурс | размер | где (`data.h`, `full_image[]`) |
|---|---|---|
| `TITLE/res51` | 320 × 200 | `TITLE_MAIN`, xpos 0 ypos 0 |
| `TITLE/res41` | 320 × 200 | `STORY_FRAME`, xpos 0 ypos 0 |
| `PV/res951` | 320 × 200 | фон комнаты принцессы (`chtab_9_princessbed`) |
| `TITLE/res42..res45` | 272 / 267 / 264 / **256** × 134..142 | «presents», «Prince of Persia», «Mechner» |
| `TITLE/res54` | 272 × 65 | заголовок Hall of Fame |
Высота нигде не превышает 200 — в байт лезет. По ширине не лезут ровно эти
восемь, и ни одна из них не участвует в игровом кадре.
**Вывод: горячий путь можно переводить на 8-битные габариты целиком.**
Для подложек — решение пользователя (2026-08-17): работу с роликами вынести в
отдельный банк с версиями блита под большие спрайты либо звать libbgi напрямую
— клип и проверка выхода за экран им не нужны (рисуются в x = 0/24/48/96,
заведомо внутри 320×200). Ширина 320 всё равно потребует ДВУХ burst-скобок
акселератора на строку — как уже сделано в `pop_vflip`.
Существующая страховка уже есть и остаётся: `pop_blit_b` уводит кадр с
`img[1] | img[3] != 0` на общий путь `blit_b_oversize`.