Порт 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>
7.1 KiB
Бюджет резидента W1/W2: как мерить и как освобождать
Резидент huge-режима — окно 0x4100..0xBB00 (стек с 0xBB00): код в W1,
данные в W2, между концом данных и стеком остаётся куча. Всё, что туда не
влезло, живёт в банках.
Как СМОТРЕТЬ, а не гадать
Карта линкера врёт. File-static SDCC в неё не попадает, и «дырка» между
двумя именованными символами приписывается предыдущему целиком. По карте
выходило, что у pop_bg 1529 Б данных (на деле 289), а у pop_t_win_clear
1282 Б кода — при том, что это однострочник, а 1282 Б это два статических
помощника соседнего pop_blit_b.
Точный источник — объектные файлы: строки A <area> size <n> flags <f> в
.rel дают ровный размер каждой области модуля, а S <sym> Def/Ref — кто
символ определяет и кто на него ссылается.
Частоту вызовов мерить в MAME счётчиком, а не оценивать по смыслу:
bpset <frame_probe>,1,{printf "F %d ...",temp0,...; temp0=0;...; g}
bpset <func_addr>,1,{temp0=temp0+1; g}
Обязательна канарейка — счётчик заведомо горячей функции в том же
прогоне. Дважды спасала: один раз показала, что перехода комнаты в окне
замера не было (все нули), другой — что зонды вообще не встали (в zsh
set -- $pair НЕ разбивает строку на слова, и адрес уезжал в мусор).
Полную перерисовку комнаты форсировать читом +/-, ходьбой ненадёжно.
Сделано
1. malloc вон из резидента (−613 Б)
cbl_open держал malloc/free в мёртвой ветке CBL_UNDERRUN_SILENCE, а
линкер тянет .rel целиком — и куча приезжала каждому приложению. Разведены
две публичные точки входа (cbl_open / cbl_open_silence) поверх общего
_cbl_open_raw; cbl_close больше не зовёт free.
2. Разрез pop_tile: холодная половина в банк 5 (−1788 Б)
pop_tile.c был крупнейшим жильцом резидента (5 972 Б кода). Целиком он не
уедет: его const-таблицы (POP_TILE_DIV/MOD, pop_tile_table, таблицы
кадров) читают банки 2, 3, 7 и 8, а таблица в чужом банке не видна.
Отбирали ЗАМЕРОМ, на двух тайлсетах (подземелье ур. 1 и дворец ур. 4 —
pop_mem_b рисует композитный кусок и мог оказаться дворцовым). Порог —
пик не больше 3 вызовов на кадр.
| уехало в банк 5 | пик/кадр | осталось в резиденте | пик/кадр | |
|---|---|---|---|---|
pop_mem_b |
0 | pop_tile_code |
296 | |
pop_cd_hit (+hit_rect) |
0 | pop_cd_touch |
198 | |
pop_t_win_set/clear |
0..1 | pop_blit_b (+2 статика) |
184 | |
pop_heal_off |
0..1 | pop_wall_modifier |
101 | |
pop_potion_flask |
0..1 | pop_env_b |
73 | |
pop_room_set_above/below |
1 | pop_tile_mod |
70 | |
pop_cd_init/clear |
1 | pop_cd_batch_end |
40 | |
pop_bar_black |
3 | pop_fore_set_clip |
2 | |
pop_cd_hit_slot |
2..3 | все const-таблицы | — |
pop_fore_set_clip (88 Б) оставлен намеренно: не стоит отказа от прямого
вызова из банка 4, ради которого он и заводился.
Цена трамплина замерена: 252 такта пролог + 84 эпилог + ~50 на стороне вызывающего = ~410 тактов на вызов. Итого ~1 000 тактов на кадр покоя (0,2 % работы) и ~3 700 на кадр редрава (0,009 растра).
Ключ, почему это безопасно: вызов банк → резидент ПРЯМОЙ, трамплин не
нужен (W1/W2 замаплены всегда). Поэтому blit_b_clip просто перестал быть
static и объявлен в _pop_tile.h, а не переехал следом за pop_mem_b.
Итог
| было | стало | |
|---|---|---|
_CODE резидента |
24 329 | 21 928 |
| свободно до стека | 129 Б | 2 535 Б |
| BANK5 | 2 080 (13 %) | 3 954 (24 %) |
Проверено в MAME: уровень 1 (подземелье) и уровень 4 (дворец), переходы
комнат читом +, ходьба — фон, факелы, решётки, гобелены, колонны без
искажений.
ЛОВУШКА: данные банка в его страницу — НЕ ДЕЛАТЬ без разбора
Отдельная попытка (--bank-data=SRC, коммиты 3545826/9025573) откачена:
перенос писучих данных банкового модуля в его 16-КБ страницу давал цветной
мусор блоками и ронял DSS.
У pop_trob причина найдена: pop_trob_modif() ВОЗВРАЩАЕТ УКАЗАТЕЛЬ на
room_modif[24][30], а зовут её из банков 2, 3, 7 и резидента — после
переноса они пишут по 0xC000+ в СВОЮ страницу, поверх чужого кода.
Def/Ref-анализ такого не видит: снаружи ссылки на символ нет, есть ссылка на
функцию, отдающую его адрес. Но и pop_room, у которого утечки указателя
найти не удалось, ломался так же — механизм понят не до конца.
Нулевая инициализация при этом ни при чём: mkexe -p 0 был проверен по
образу (прогон нулей 14 304 Б, самый длинный прогон 0xFF — 14).
Перенос КОДА в банк — штатный путь, на нём стоят все наши банки. Ломался именно перенос ДАННЫХ.
Что осталось
sprpop.c2 508 Б иpop_kid.c2 418 Б — следующие по величине, но оба горячие (главный цикл иplay_seq).pop_level.c956 Б кода + 660 Б данных (из нихpop_dl1/pop_dl2по 256 Б — таблицы дверных связей).- BANK7 на 77 %: если понадобится место в нём — выносить
pop_redraw.c.