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>
112 lines
7.1 KiB
Markdown
112 lines
7.1 KiB
Markdown
# Бюджет резидента 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.c` 2 508 Б и `pop_kid.c` 2 418 Б — следующие по величине, но оба
|
||
горячие (главный цикл и `play_seq`).
|
||
- `pop_level.c` 956 Б кода + 660 Б данных (из них `pop_dl1`/`pop_dl2` по
|
||
256 Б — таблицы дверных связей).
|
||
- BANK7 на 77 %: если понадобится место в нём — выносить `pop_redraw.c`.
|