Files
Sprinter-SDCC/applications/PoP/docs/perf_l13_room23.md
T
snark13 f44663040c Регресс тактов на 23/13 после обхода уровней 1-2: изменений нет
Замер 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>
2026-08-17 22:49:02 +03:00

13 KiB
Raw Blame History

Сцена и метод замера: каскад плит, уровень 13 комната 23

Общий документ для двух фазовых: perf_green_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 — цикл «bpclearESC → зонды → roomtestclog»; parse*.py — разбор трассы по кадрам.

Форма зонда: bpset <addr>,1,{printf "<метка> %d",totalcycles; g}. Для pop_dbg_kindprintf "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. Сводка по кадрам

Базовый замер (c312e4a, ДО оптимизации):

фаза каскада работа синяя зелёная циан период (растр.)
покой в комнате 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

После оптимизации (af189a1, 2026-08-17):

максимум по секции работа синяя зелёная циан
было 1 437 150 142 830 805 000 631 800
стало 916 458 142 830 546 900 270 510
36 % 32 % 57 %

Регресс после обхода уровней 1-2 (ec1f384, 2026-08-17), 418 кадров:

максимум по секции работа синяя зелёная циан
после оптимизации (af189a1) 916 458 142 830 546 900 270 510
после фиксов ур. 1 (40f0d46) 873 930 158 874 417 630 379 482
после фиксов ур. 2 (ec1f384) 878 550 158 880 419 526 379 488

Фиксы второго уровня (чёрные бары, чит бессмертия) на бюджет не повлияли: разница с предыдущим замером +4 620 работы и +1 896 зелёной — шум прогона. Скачок циан на фиксах ПЕРВОГО уровня (270 510 → 379 482) объяснён там же: восстановлены потерянные половины слоёв (set_redraw2, ряд 1 foretable), то есть это плата за корректность, а не регрессия.

Период кадра по прогону: 3 растра в 392 кадрах, 4 в 24, 5 в одном — то есть за бюджет вылезает только сам каскад.

Цель — каждая секция ≤ 400 000. Синяя и циан в бюджете; зелёная 419 526, то есть 1,05× цели (и ниже растрового кадра 430 000), остаток разобран в perf_green_phase.md §3 (нужен раскол draw_tile на узкие части, как в оригинале) и в идее G8 (сузить инвалидацию соседнего тайла до 28-пиксельной полосы).

Где что расходуется и как это чинить — в фазовых документах: зелёная, циан.

Общий вывод по пиковому кадру базового замера (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.