Files
Sprinter-SDCC/applications/SprPoP/docs/resident_budget.md
T
snark13 31b82661eb SprPoP: автономное приложение, выделенное из roomtest
Порт 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>
2026-08-27 12:12:28 +03:00

112 lines
7.1 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.
# Бюджет резидента 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`.