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>
92 lines
8.0 KiB
Markdown
92 lines
8.0 KiB
Markdown
# Идеи и вопросы «на подумать» (PoP)
|
||
|
||
Не план работ, а список того, что осознанно отложено: каждая запись —
|
||
гипотеза с причиной, по которой её стоит проверить, и с тем, что мешает
|
||
сделать это прямо сейчас.
|
||
|
||
## Зелье «переворот экрана» (upside-down)
|
||
|
||
**Вопрос пользователя (2026-08-01).** Тайлы фона у нас лежат строками, а
|
||
кадры Кида/стражей — КОЛОНКАМИ (`transpose_cols` в `pop_pack_kid.py`, ради
|
||
бесплатного горизонтального зеркала). Значит вертикальный переворот для
|
||
персонажей заметно сложнее, чем для фона. Верно; но прежде чем это чинить,
|
||
надо знать три факта.
|
||
|
||
**Факт 1 — когда оно вообще нужно.** Зелье переворота — тип 4
|
||
(`proc_get_object`, `seg006.c:1885` → `toggle_upside()`). Скан всех уровней
|
||
по данным (`res200N.bin`, тайл 10 = зелье, тип в backtable): тип 4
|
||
встречается **впервые на уровне 9** (две склянки), и больше нигде. Тип 3
|
||
(перо, медленное падение) — уровень 7. То есть **до уровня 9 механика не
|
||
нужна вообще**, и «на первом этапе просто не реализовывать» — не компромисс,
|
||
а точное соответствие данным уровней 1..8.
|
||
|
||
**Факт 2 — что именно делает оригинал.** НЕ переворачивает спрайты.
|
||
`flip_screen` (`seg009.c:1042`) → `flip_not_ega` (`seg009.c:1023`) меняет
|
||
местами СТРОКИ готового offscreen-буфера (top↔bottom, порядок пикселей
|
||
внутри строки не трогает — это вертикальное зеркало, не поворот на 180°).
|
||
Вызывается вокруг отрисовки кадра целиком (`seg003.c:296..301`): перевернул
|
||
буфер → дорисовал → перевернул обратно. Так что в оригинале это
|
||
post-process всего экрана, и вопрос «как перевернуть колоночный спрайт»
|
||
там просто не возникает.
|
||
|
||
**Факт 3 — почему нам этот приём не подходит как есть.** У нас нет шага
|
||
«готовый offscreen → экран»: рисуем прямо в видеостраницу, а heal берёт фон
|
||
из ОЗУ-копии этой же страницы. Переворот всей страницы построчно — это
|
||
320×192 Б копирования КАЖДЫЙ кадр, что мимо бюджета на порядок.
|
||
|
||
**Варианты, которые надо будет взвесить (не сейчас):**
|
||
1. **Предпечённые перевёрнутые атласы.** Второй набор кадров
|
||
Кида/стража, перевёрнутый по вертикали ещё в `pop_pack_kid.py` (там уже
|
||
есть транспонирование — добавляется одной строкой). Рантайм: выбор
|
||
набора + зеркальная арифметика Y. Память: ещё ~28 страниц EMM при
|
||
бюджете ~3.3 МБ — не проблема. Похоже, самый дешёвый по тактам путь.
|
||
2. **Фон рисовать с обратным Y** — для row-major тайлов строка остаётся
|
||
непрерывным accel-прогоном, меняется только адрес назначения; цена —
|
||
вызов на строку вместо вызова на тайл. Померить, прежде чем закладывать.
|
||
3. **Аппаратная помощь** — до проектирования проверить, есть ли у
|
||
акселератора направление копирования «вниз» (обратный инкремент адреса);
|
||
если есть, вариант 1 может и не понадобиться. Смотреть
|
||
`docs/new/06-accel.md` и `docs/reference/accel_r.txt`.
|
||
|
||
**Почему не сейчас.** Уровни 1..8 этого не требуют, а к уровню 9 у нас уже
|
||
будет ответ на вопрос «сколько стоит кадр» (задачи CLIP-1/T-2) — без него
|
||
выбирать между вариантами выше бессмысленно.
|
||
|
||
## Заменить генератор псевдослучайных чисел
|
||
|
||
Сейчас стоит LCG оригинала, шаг на ассемблере (~1 020 тактов), бит-в-бит
|
||
совместимый с SDLPoP. Есть более дешёвые Z80-генераторы (86–148 тактов),
|
||
но потолок выигрыша — 2 814 тактов за кадр, 0.65 %, и он растворяется в
|
||
обёртках вызова. Тексты процедур, разбор качества и порядок действий —
|
||
`prng_alternatives.md`. Первый шаг там не про генератор: слить приведение
|
||
к диапазону в ту же asm-процедуру, чтобы на вызов был один `call`, а не три.
|
||
|
||
## Отключать мышь на время игры
|
||
|
||
**Гипотеза.** Мышь на Sprinter — источник прерываний (обёртки RST 30h,
|
||
см. memory `mouse_api`). Игре она не нужна вообще: управление —
|
||
raw-клавиатура (`<kbd_raw.h>`), которую мы и так забираем у DSS целиком.
|
||
Значит каждое мышиное прерывание за кадр — украденные такты в бюджете,
|
||
который у нас и без того занят на 86 %.
|
||
|
||
**Откуда взялось (2026-07-30).** При замере бюджета по 100 кадрам три
|
||
кадра выбились до 552–647 К тактов (1.28–1.51 кадра) при типичных 371 К.
|
||
Причиной оказалось движение мыши на ХОСТЕ: при неподвижной мыши 225
|
||
кадров подряд прошли без единого превышения. То есть эффект реальный и
|
||
измеримый, просто в тесте он был наведён извне.
|
||
|
||
**Что проверить.**
|
||
1. Есть ли у драйвера мыши (RST 30h) функция «выключить/включить» —
|
||
разобрать список из 14 обёрток; если нет явной, посмотреть, что делает
|
||
«hide cursor» и снимает ли она обработчик.
|
||
2. Сколько тактов реально стоит одно мышиное прерывание на нашем железе
|
||
(замер: breakpoint на входе ISR + totalcycles, при движении мыши).
|
||
3. Не ломает ли отключение выход в DSS: состояние обязано
|
||
восстанавливаться при `exit`, включая аварийный (atexit).
|
||
|
||
**Почему не сейчас.** Выигрыш проявляется только когда игрок реально
|
||
двигает мышью, то есть в норме его нет; а риск оставить систему без мыши
|
||
после выхода — заметный. Делать после того, как закроем стражей и
|
||
займёмся бюджетом всерьёз (там же, где батчинг кроссбанковых вызовов и
|
||
возможный возврат `pop_bg` в резидент `--w3`).
|