Меню, статус-строка и оболочка игры: title/intro/cutscene/HoF, POP.CFG, палитры
Эта сессия (меню + текст в служебных полосах):
* Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и
снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках
(UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space —
равноправные клавиши действия (ui_action_down).
* Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и
ABOUT. About показывает тот же текст, что стартовый Sprinter screen,
минус строка про клавиши — общий about_text(), чтобы экраны не
разъехались. CONTROLS собирается таблицей и центрируется по
фактическому числу строк.
* GAME PAUSED переехала в нижнюю статус-строку, как в оригинале
(SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы,
а не пункт меню. POP_HP_Y вынесен в pop_cdraw.h — полосу делят два
модуля.
* pop_status.c (банк 9) — текст в обеих служебных полосах. Нижняя:
порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF,
24 тика). Верхняя отладочная переведена с палочек на текст
«Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с
своим форматированием чисел (без printf и без деления).
Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2
не растёт, весь рендер в банке. Бюджет после правок не изменился
(_CODE 23981, куча 267 Б).
* Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому
всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы
не блитятся вовсе, а вход в комнату заливает только игровое поле
(pop_screen_fill_field) — борта от комнаты к комнате не меняются.
* Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие
ворот (4) и открытие двери покоев (51), из которой входит Джафар.
* docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в
статус-строке: геометрия, семантика text_time_total как идентификатора
сообщения, мигание, рестарт по истечении 36/288.
Вместе с этим выкладывается накопленная работа по оболочке полной игры:
автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo,
Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и
fade, звуковой набор, host-тесты на новые швы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
| [`quicksave_plan.md`](quicksave_plan.md) | **QuickSave/QuickLoad** (✅ реализовано, F6/F9, POP.SAV+BAK): разбор (это enhancement SDLPoP, в оригинале 1989 его НЕТ), инвентаризация нашего состояния, формат снимка 'POPQ' v3, шаги QS1..QS6. Справочник. 2026-08-22 |
|
||||
| [`full_game_plan.md`](full_game_plan.md) | **Полноценная игра**: app state machine, title/intro, demo level 0, таймер, cutscenes, уровни 1..14, ending и Hall of Fame. Уровень 15 исключён. 2026-08-21 |
|
||||
| [`menu_settings_plan.md`](menu_settings_plan.md) | **Pause menu и Settings**: QuickSave/QuickLoad в основном menu, `POP.CFG`, один `POP.SAV` + `POP.BAK`, текущий VANILLA и задел под ENHANCED; §10 — выбор UI-рендера (текстовые строки + свой растровый рендерер, референс SDLPoP: два шрифта), restart без подтверждения. 2026-08-22 |
|
||||
| [`palette_plan.md`](palette_plan.md) | **Палитры и fade**: карта всех 256 слотов (kid.pal/title/story, тайлсеты dungeon/palace, стражи), механика fade (4 ступени vs ~64 у SDLPoP), план модуля `pop_pal.c` (API load/apply/black) + переход уровня через fade. 2026-08-23 |
|
||||
| [`status_line_text.md`](status_line_text.md) | **Строка HP как статус-строка**: полная инвентаризация ВСЕХ текстов SDLPoP в `rect_bottom_text` (геометрия, семантика `text_time_total`, мигание, рестарт по истечении) + что из этого уже есть у нас. 2026-08-25 |
|
||||
| [`levels_plan.md`](levels_plan.md) | Следующий этап: уровни 2+, второй тайлсет, читы SDLPoP |
|
||||
| [`levels_12_15_plan.md`](levels_12_15_plan.md) | **Уровни 12/13** (тень, Джафар, падающие плиты) + что такое 14/15 и 0. 2026-08-13 |
|
||||
| [`midtable_analysis.md`](midtable_analysis.md) | **Слои отрисовки**: как устроены back/mid/fore и objtable в оригинале, чего стоит порт, развилки. 2026-08-13 |
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# От roomtest к полноценной игре — сценарий и оболочка
|
||||
|
||||
Статус: **согласованный план, код не начат** (2026-08-21).
|
||||
Статус: **частично реализовано; аудит обновлён 2026-08-24**. Ранее пометка
|
||||
«завершены FG0–FG12» была неверной: для многих этапов уже есть код и
|
||||
host-тесты, но их критерии приёмки на Sprinter ещё не выполнены. Фактический
|
||||
статус каждого FG приведён в [§14](#14-этапы-реализации).
|
||||
|
||||
Этот документ описывает превращение текущего игрового цикла
|
||||
`roomtest` в законченную игру: заставка, интро, демонстрационный уровень,
|
||||
@@ -72,17 +75,24 @@ attract loop после демо или финала.
|
||||
Джаффар, специальные события, checkpoint, переходы уровней, бесшовный
|
||||
выход 12-го уровня, перенос максимального HP и звуковые эффекты.
|
||||
|
||||
Отсутствуют:
|
||||
Поверх игрового цикла уже добавлены автомат оболочки, title/story, demo
|
||||
уровень 0, global timer, сценарный интерпретатор, level-flow, ending и Hall
|
||||
of Fame. Полный маршрут также собирается в HDD-образ.
|
||||
|
||||
- верхнеуровневая оболочка приложения;
|
||||
- title/story sequence и текстовые экраны;
|
||||
- уровень 0 и записанное управление демо;
|
||||
- общий cutscene engine и PV-ресурсы;
|
||||
- сцены между уровнями;
|
||||
- глобальный таймер и time-expired sequence;
|
||||
- корректный переход из уровня 14 в ending;
|
||||
- финальная сцена и Hall of Fame;
|
||||
- возврат к title без перезапуска программы.
|
||||
Однако это **не означает готовность оболочки**. На момент аудита остаются
|
||||
существенные незакрытые места:
|
||||
|
||||
- lifecycle палитр: gameplay-переходы используют чёрный барьер без fade;
|
||||
cold start и полный набор dungeon/palace переходов ещё не прошли приёмку;
|
||||
- PV intro Princess/Jaffar уже покадровый (актёры, факелы, звёзды, часы,
|
||||
молния и foreground-колонна); сцены перед 2/4/6 и длинной веткой 12
|
||||
анимируют факелы, звёзды и песок, а сцены 8/9 и короткая ветка 12 пока
|
||||
используют статические позы с исходной длительностью;
|
||||
- demo отображается с игровой палитрой, проходит второй разворот/зацеп и
|
||||
доходит до боя; после смерти Кида корректно завершает цикл;
|
||||
- time-expired, ending и Hall of Fame имеют маршрут и реализацию UI, но не
|
||||
прошли сквозную MAME-проверку вместе с ресурсами и возвратом к title;
|
||||
- нет полного регресса EMM/FD для каждого перехода состояния.
|
||||
|
||||
## 4. Архитектура: автомат состояний приложения
|
||||
|
||||
@@ -218,6 +228,13 @@ title -> demo -> title повторяется неограниченно.
|
||||
Критерий: одинаковый игровой отрезок в NORMAL даёт то же уменьшение времени,
|
||||
что SDLPoP; сохранение/загрузка не добавляет и не отнимает тики.
|
||||
|
||||
Реализация FG4 живёт одним модулем `roomtest/pop_timer.c` в bank 9:
|
||||
`60:719`, 720 тиков на минуту, счёт только в живом игровом кадре. Settings
|
||||
хранит `TIME LIMIT: 60 MIN / UNLIMITED` в `POP.CFG`; старый семибайтный v1
|
||||
payload по-прежнему читается как `60 MIN`. Читы таймера повторяют SDLPoP,
|
||||
но из-за занятого `+/-` используют F7 (−1 минута, не ниже одной) и F8
|
||||
(+1 минута). Состояние входит в QuickSave v4.
|
||||
|
||||
## 10. Cutscene engine
|
||||
|
||||
Сцены SDLPoP состоят из небольшого набора повторяемых команд. Вместо набора
|
||||
@@ -305,24 +322,26 @@ Hall of Fame хранится на HDD в отдельном версионир
|
||||
|
||||
## 14. Этапы реализации
|
||||
|
||||
| этап | результат | критерий приёмки |
|
||||
|---|---|---|
|
||||
| **FG0** | удалить уровень 15, формализовать 1..14 | HDD не содержит res2015; переход выше 14 невозможен |
|
||||
| **FG1** | автомат состояний, текущая игра = PLAYING | старт/рестарт/выход проходят без рекурсии и утечки EMM |
|
||||
| **FG2** | QuickSave/QuickLoad | критерии `quicksave_plan.md`, включая POP.BAK |
|
||||
| **FG3** | минимальный pause menu | Resume/Save/Load/Restart/Settings/Quit работают |
|
||||
| **FG4** | глобальный таймер | совпадение с SDLPoP и корректный save/load |
|
||||
| **FG5** | text/full-screen/fade primitives | тестовые экраны и переходы на Sprinter |
|
||||
| **FG6** | build info + оригинальный title | точный порядок, корректный пропуск |
|
||||
| **FG7** | demo level 0 | бесконечный attract loop, ввод начинает игру |
|
||||
| **FG8** | cutscene interpreter + intro | интро проходит и пропускается без утечек |
|
||||
| **FG9** | сцены 2/4/6/8/9/12 | правильный вызов один раз перед уровнем |
|
||||
| **FG10** | time expired | отдельная сцена и возврат на title |
|
||||
| **FG11** | level 14 -> ending | полный маршрут после Джаффара |
|
||||
| **FG12** | Hall of Fame | запись HDD, ввод имени, возврат к attract loop |
|
||||
Легенда аудита: **✓** — критерий этапа закрыт; **~** — код существует, но
|
||||
критерий приёмки ещё не закрыт; **○** — не начат. Статус отражает состояние
|
||||
исходников и последней MAME-проверки на 2026-08-24, а не только наличие
|
||||
модуля в bank 9.
|
||||
|
||||
QuickSave допускается реализовать до остальных частей оболочки: FG2 — одна
|
||||
из ближайших самостоятельных задач.
|
||||
| этап | статус | результат и фактическое состояние | критерий приёмки |
|
||||
|---|---|---|---|
|
||||
| **FG0** | ✓ | `POP_LEVEL_LAST=14`, HDD содержит `res2000..res2014`; `t_flow` отвергает 15 | HDD не содержит res2015; переход выше 14 невозможен |
|
||||
| **FG1** | ~ | автомат `pop_app` и `t_app` реализованы; сквозной ресурсный lifecycle и контроль EMM/FD ещё не измерены | старт/рестарт/выход проходят без рекурсии и утечки EMM |
|
||||
| **FG2** | ✓ | QuickSave/QuickLoad с `POP.SAV` и `POP.BAK`; отдельно проверен в MAME 2026-08-22 | критерии `quicksave_plan.md`, включая POP.BAK |
|
||||
| **FG3** | ✓ | pause menu, Settings, подтверждения и двойной буфер реализованы; меню проверялось в MAME; добавлены SDLPoP-звуки навигации и защита CBL вокруг полного redraw/файловых операций | Resume/Save/Load/Restart/Settings/Quit работают |
|
||||
| **FG4** | ~ | `pop_timer`, настройка unlimited, F7/F8 и состояние QuickSave реализованы; есть host-тест, но нет буквального сравнения темпа со SDLPoP на всех переходах | совпадение с SDLPoP и корректный save/load |
|
||||
| **FG5** | ~ | text/full-screen/fade примитивы есть; для входа в первый уровень и границ уровней выбран мгновенный чёрный барьер без fade: CBL и яркая новая палитра включаются только после подготовки обеих страниц; Level 1 проверен в MAME | тестовые экраны и переходы на Sprinter |
|
||||
| **FG6** | ~ | title-ресурсы и порядок кадров реализованы; Enter на title и Esc на первом story в MAME переводят прямо в `FIRST_LEVEL`, минуя demo; полная cold-boot приёмка fade остаётся в FG5 | основной титул/Presents/название/Mechner идут в точном порядке `show_title()`; Enter/Space/Esc/стрелки прерывают ожидание; story/intro продолжит FG8 |
|
||||
| **FG7** | ✓ | level 0, исходная таблица `demo_moves`, demo HP=4 и блокировка игрового UI реализованы; исправлены зеркалирование auto-control, боевой AI Кида и завершение после смерти; в MAME demo проходит разворот/зацеп, доходит до боя и возвращается в attract-цикл без повторного убийства | `res2000.bin`, исходная `demo_moves`, demo HP=4; бесконечный attract loop, любой ввод начинает чистую новую игру; Pause/QuickSave/читы/таймер отключены |
|
||||
| **FG8** | ~ | data-driven interpreter и покадровый PV intro работают; в MAME проверены актёры, факелы, звёзды 1x1, часы/песок, palette-0 lightning и foreground-колонна; Enter/Esc переводят прямо в `FIRST_LEVEL`; временный темп 12,5 FPS и TODO точного pacing записаны в `impl_diff.md` | story/PV intro проходит, любой raw-ввод пропускает его без удержания EMM-страниц |
|
||||
| **FG9** | ~ | `pop_flow` корректно маршрутизирует 2/4/6/8/9/12 и ветку <=5 минут (`t_flow`); 2/4/6 и длинная 12 уже обновляют часы, песок, факелы и звёзды каждые 5 кадров Sprinter; длительности всех веток сверены с SDLPoP: 2/4/6/12 — 2,6 с, 8 — 6,0 с, 9 — 7,2 с; входная клавиша gameplay/Shift+L поглощается до сцены, а новое нажатие делает skip; анимации мыши/Princess в 8/9 и разворот Princess в короткой 12 ещё статичны | таблица flow переводит в CUTSCENE ровно перед 2/4/6/8/9/12; scene 12 выбирает короткий вариант при <=5 минутах |
|
||||
| **FG10** | ~ | переход TIME_EXPIRED и экран существуют, но это ещё статическая PV-стадия; сквозной MAME-маршрут не принят | PV-сцена истечения с пропуском, затем возврат на title/attract; новая игра сбрасывает таймер |
|
||||
| **FG11** | ~ | room 5 уровня 14 переводит в ENDING (`t_flow`); объятие/мышь заменены статической стадией, полный маршрут не принят | room 5 уровня 14 переводит в ENDING; PV-финал и Hail-экран возвращают управление оболочке |
|
||||
| **FG12** | ~ | версионированный `POP.HOF`, ввод имени и восстановление после повреждённого файла реализованы; нужна сквозная MAME-проверка ending → HOF → title | версионированный `POP.HOF`, ввод имени raw-клавиатурой, повреждённый файл = пустая таблица, затем title/attract |
|
||||
|
||||
## 15. Проверки
|
||||
|
||||
@@ -346,4 +365,3 @@ QuickSave допускается реализовать до остальных
|
||||
- replay/recording;
|
||||
- точная эмуляция SDL video/controller options;
|
||||
- профиль ENHANCED и индивидуальные switches fixes.
|
||||
|
||||
|
||||
@@ -505,6 +505,53 @@ BUG-CHEAT-FIGHT-1 (выход из боя), и лечится там же.
|
||||
«страж давит сильнее». Режим NORMAL (дефолт) даёт 102,4 мс — см.
|
||||
`frame_pacing_plan.md`.
|
||||
|
||||
## PV intro: единые 12,5 FPS вместо переменных 10/7,5/8,57 FPS
|
||||
|
||||
**Оригинал.** `proc_cutscene_frame()` двигает последовательности через
|
||||
`cutscene_frame_time`: 6 тиков 60 Гц в начале, 8 после первой речи и 7 во
|
||||
время заклинания. Это соответственно 10, 7,5 и примерно 8,57 FPS.
|
||||
|
||||
**У нас (осознанное временное отличие).** Один логический кадр PV держится
|
||||
четыре физических кадра Sprinter: номинально 50/4 = 12,5 FPS. Молния живёт
|
||||
на отдельной физической шкале и не растягивается этим делителем. Если полная
|
||||
отрисовка пересечёт дополнительный фронт, реальная частота может упасть до
|
||||
10 FPS — это допустимо на текущем этапе, но должно быть измерено.
|
||||
|
||||
**TODO.** Перевести PV-сцену на тот же anchor-based механизм точного темпа,
|
||||
который gameplay использует через `pop_beam_sample/pop_pace_end`: измерять
|
||||
число реально прошедших фронтов во время сборки кадра, держать период ровно
|
||||
четыре фронта при укладывании в бюджет и явно учитывать overrun. После замера
|
||||
можно вернуть точные переменные интервалы SDLPoP без накопления фазы.
|
||||
|
||||
## Межуровневые PV-сцены: сохранён реальный период 100 мс
|
||||
|
||||
Это правило не относится к временному темпу основного Princess/Jaffar intro
|
||||
выше. `reset_cutscene()` SDLPoP задаёт для сцен перед уровнями период
|
||||
6 кадров при 60 Гц, то есть 100 мс. На Sprinter тот же период получается
|
||||
ровно как 5 кадров при 50 Гц.
|
||||
|
||||
Суммы вызовов `proc_cutscene_frame()` перенесены без изменения реального
|
||||
времени: сцены 2/4/6 и обе ветки 12 содержат 26 логических кадров (130
|
||||
физических, 2,6 с), сцена 8 — 60 (300, 6,0 с), сцена 9 — 72 (360, 7,2 с).
|
||||
Fade in/out в эти числа не входят, как и в оригинале.
|
||||
|
||||
## Gameplay: загрузка уровней через чёрный cut, без fade
|
||||
|
||||
**Оригинал.** На границах игровых уровней использует fade out/in.
|
||||
|
||||
**У нас (решение пользователя 2026-08-24).** Вход в первый уровень и
|
||||
переход между уровнями выполняются как `старый кадр -> чёрная палитра ->
|
||||
подготовка -> новый кадр с новой палитрой`. Fade на этих двух маршрутах
|
||||
отсутствует. Сюжетные title/story/PV переходы сохраняют собственные fade и
|
||||
left-to-right эффекты.
|
||||
|
||||
Чёрная палитра устанавливается до любого HDD I/O. Загрузчики guard и
|
||||
tileset сами физически правят отдельные цветовые слоты, поэтому после них
|
||||
чёрный экран подтверждается повторно. Зеркальные атласы уровня 9 готовятся
|
||||
до финального источника палитры. CBL открывается последним: старый порядок
|
||||
`level_switch -> CBL open -> BIOS fade` давал скрежет повторяющейся половины
|
||||
аппаратного буфера на входе в Level 1; после перестановки баг исчез в MAME.
|
||||
|
||||
## Тень: кайма силуэта не подкрашивается фоном
|
||||
|
||||
**Оригинал.** Спрайт Тени не хранится — он кладётся ДВАЖДЫ: обычным
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Pause menu и Settings для Sprinter PoP
|
||||
|
||||
Статус: **согласованный план, код не начат** (2026-08-21; обновлён
|
||||
2026-08-22 — решение о рендеринге UI см. §10, Restart Level/Game без
|
||||
подтверждения).
|
||||
Статус: **MS0, MS2 и MS4–MS8 выполнены** (2026-08-23). Pause menu, CFG,
|
||||
Settings, диалоги, Controls и build screen находятся в bank 9. Решение о
|
||||
рендеринге и затемнении — §10.
|
||||
|
||||
Связанные документы:
|
||||
|
||||
@@ -41,12 +41,12 @@ acceleration, scaling, aspect ratio, rumble. UI берёт структуру SD
|
||||
|
||||
```text
|
||||
RESUME
|
||||
QUICKSAVE
|
||||
QUICKLOAD
|
||||
QUICKSAVE (F6)
|
||||
QUICKLOAD (F9)
|
||||
RESTART LEVEL
|
||||
SETTINGS
|
||||
RESTART GAME
|
||||
QUIT
|
||||
QUIT GAME
|
||||
```
|
||||
|
||||
Поведение:
|
||||
@@ -56,9 +56,13 @@ QUIT
|
||||
- QuickSave/QuickLoad только взводят запрос, фактическая операция идёт на
|
||||
безопасной границе кадра;
|
||||
- QuickLoad disabled/показывает `NO QUICKLOAD`, если нет валидных SAV/BAK;
|
||||
- перед QuickLoad из меню лёгкий probe проверяет заголовок и checksum обоих
|
||||
файлов: валидный `POP.BAK` при отсутствующем/битом `POP.SAV` требует
|
||||
отдельного `LOAD BACKUP?`, а не загружается молча;
|
||||
- Restart Level и Restart Game выполняются сразу, БЕЗ подтверждения
|
||||
(2026-08-22): обе операции дёшево обратимы — Restart Level перечитывает
|
||||
уровень, после Restart Game можно тут же сделать QuickLoad из POP.SAV;
|
||||
(2026-08-22): Restart Level перечитывает уровень, Restart Game завершает
|
||||
gameplay и возвращает к первому экрану title/intro; новая игра создаётся
|
||||
общим LEVEL_LOAD только после skip/attract;
|
||||
- Quit требует подтверждения и закрывает файлы/каналы штатным путём;
|
||||
- меню недоступно в demo, cutscene, time-expired и ending;
|
||||
- отдельная debug-комбинация немедленного выхода может остаться только в
|
||||
@@ -86,6 +90,10 @@ BACK
|
||||
`Gameplay profile: VANILLA` показывается read-only: место в модели уже есть,
|
||||
но пользователь не может выбрать ещё не реализованный ENHANCED.
|
||||
|
||||
Изменения применяются немедленно к скорости, читам и звуку, но `POP.CFG`
|
||||
записывается один раз при Back/Esc. На экране есть итог `SETTINGS SAVED` или
|
||||
`SAVE ERROR`; во втором случае runtime-значения остаются рабочими.
|
||||
|
||||
Отладочные параметры `ROOMNAV`, border profiling, stop-frame и переключение
|
||||
double buffering не являются пользовательскими Settings. Они остаются
|
||||
compile-time/debug функциями и скрываются из release UI.
|
||||
@@ -215,9 +223,10 @@ Restart Level:
|
||||
Restart Game:
|
||||
|
||||
- выполняется сразу, без подтверждения;
|
||||
- завершить текущий gameplay session;
|
||||
- освободить level/atlas/temporary EMM;
|
||||
- создать новую игру с уровня 1 и новым глобальным таймером;
|
||||
- завершить текущий gameplay session и вернуть автомат в TITLE;
|
||||
- начать title/intro с самого первого экрана;
|
||||
- создать новую игру с `FIRST_LEVEL` и новым глобальным таймером только
|
||||
после пользовательского skip либо ввода в attract-demo;
|
||||
- настройки оставить;
|
||||
- QuickSave не удалять.
|
||||
|
||||
@@ -234,7 +243,9 @@ Restart Game:
|
||||
- F6/F9 QuickSave/QuickLoad;
|
||||
- Ctrl+S sound;
|
||||
- P speed;
|
||||
- доступные cheats, только если они включены.
|
||||
- доступные cheats, только если они включены: K/Kill Guard, I/Immortal,
|
||||
Shift+L/Next Level, U/Flip Screen и F7/F8/Time −/+ на отдельных понятных
|
||||
строках. Нижней подсказки `Esc or Enter: Back` нет.
|
||||
|
||||
## 10. UI renderer и ввод
|
||||
|
||||
@@ -321,38 +332,123 @@ On/Off и т.д.).
|
||||
|
||||
### 10.3. Наша реализация
|
||||
|
||||
- Новый банк (свободный номер, автонумерация sprinter-cc): код рендерера,
|
||||
- Банк 9: код рендерера,
|
||||
шрифт, таблицы строк, автомат меню. Резидентно — только request-flag и
|
||||
вызов процесса на границе кадра (паттерн pop_qsave_io).
|
||||
- Рендерер портирует контракт seg009, упрощённо: `font_type` + массив
|
||||
глифов переменной ширины, блит монохромного глифа в теневую страницу
|
||||
через W0-окно (как весь остальной код рисования), `draw_string(x,y,color)`
|
||||
+ центрирование по сумме ширин. Пропорциональность — сразу, API не меняется
|
||||
от моноширинного.
|
||||
- Шрифт — АССЕТ, генерируемый паковщиком toolchain из TTF (красивый, сразу
|
||||
с кириллицей), а НЕ системный ZG. Быстрый прототип для MS4 — системный
|
||||
CP866 знакогенератор через существующий `bios_get_zg` в буфер банка
|
||||
(паттерн `gfx_load_default_font`, но буфер в банке, не `_gfx_font_buf`);
|
||||
потом файл шрифта заменяется без смены API.
|
||||
- Рендерер повторяет минимальный контракт seg009: пропорциональные глифы,
|
||||
baseline, `draw_string` и центрирование по сумме advance. Блит идёт через
|
||||
W0-атлас, в `GFX_BANK_SPRITE`: `0xFF` в атласе пропускается, а UI временный
|
||||
и не портит теневую копию игрового фона. Перед каждым кадром UI `gfx_copy_page` переносит чистый
|
||||
shadow видимой страницы в скрытую, затем готовый кадр показывается только
|
||||
на следующем фронте. При выходе чистый фон тем же способом возвращается на
|
||||
обе страницы и восстанавливается исходная visible-страница. Поэтому
|
||||
перемещение выделения не показывает поэтапную перерисовку и не оставляет
|
||||
следов на back buffer.
|
||||
- Шрифт — АССЕТ из **оригинальных** `hc_small_font_data[]` и
|
||||
`hc_font_data[]` SDLPoP, не системный ZG и не TTF. Паковщик
|
||||
`toolchain/pop_extract_font.py` делает `FONT\\font.atl`: 95 ASCII-глифов
|
||||
малого и 95 крупного шрифта (7667 Б). Номер ленты вычисляется из ASCII,
|
||||
поэтому это один текстовый движок, а не атлас готовых надписей.
|
||||
- Двуязычность (eng/rus): строки храним в CP866 — латиница и кириллица одним
|
||||
байтовым порядком, одна кодировка на оба алфавита. Локаль = пара
|
||||
(указатель на таблицу строк, файл шрифта); переключатель — одна настройка.
|
||||
Русские строки длиннее английских ~10–15% — раскладку экранов и ширину
|
||||
колонок закладывать по русской. Второй язык можно добавить позже без
|
||||
переделки: сначала eng.
|
||||
- Подсветка выделенного пункта: инверсия прямоугольника или контур + цвет,
|
||||
как в SDLPoP. Цвета текста брать из стабильного диапазона палитры
|
||||
0x3A..0x3F (его никто не перезаписывает, проверено на борде-индикаторе).
|
||||
- Фон открытого меню: затемнение замороженного кадра (запечь тёмный прямоуг.
|
||||
в теневую копию страницы поверх сохранённого фона) — двойную перерисовку
|
||||
игры под меню делать не нужно, игра стоит.
|
||||
- Навигация: вверх/вниз по пунктам, left/right для значения, Enter/Esc;
|
||||
edge-triggered поверх существующего `kbd_raw`. Звук навигации — menu tick
|
||||
из имеющихся сэмплов.
|
||||
- Визуальная композиция MS4 следует SDLPoP: замороженная сцена остаётся
|
||||
открытой, поверх неё компактный центрированный список без чёрной карточки,
|
||||
выбранная строка обведена тонким светло-серым контуром, а крупное
|
||||
`GAME PAUSED` лежит в нижнем борту. Цвета текста и контура берутся из
|
||||
стабильного диапазона палитры 0x37..0x3F.
|
||||
- Фон открытого меню: снимок текущей палитры, затемнение всех слотов кроме
|
||||
UI 0x37..0x3F и точное восстановление при выходе. Снимок хранится в
|
||||
свободном хвосте EMM-страницы шрифта, не в W2.
|
||||
- Навигация MS4: вверх/вниз, Enter/Esc, edge-triggered поверх `kbd_raw`.
|
||||
Left/right и menu tick добавляются вместе с настройками на MS5.
|
||||
|
||||
Первый UI может быть визуально простым. Критично отсутствие потери клавиш,
|
||||
предсказуемая пауза и отсутствие повреждения игрового back buffer.
|
||||
|
||||
### 10.4. Затенение экрана под меню — решение MS4
|
||||
|
||||
Режим меню виден сразу: bank 9 делает динамический снимок palette 0,
|
||||
затемняет RGB-каналы вдвое и пишет одинаковый результат в обе экранные
|
||||
палитры. Девять стабильных UI-слотов 0x37..0x3F не гасятся. При Resume/Enter
|
||||
палитра восстанавливается из EMM-снимка. Это выбранный вариант Б ниже;
|
||||
ступенчатый fade для роликов пока не нужен и остаётся отдельной будущей
|
||||
задачей, а не причиной раздувать MS4.
|
||||
|
||||
**Как сделано в SDLPoP** (`seg009.c`):
|
||||
|
||||
- Меню: `draw_rect_with_alpha(&screen_rect, color_0_black, pause_menu_alpha)`
|
||||
(menu.c:1364) — альфа-заливка чёрным поверх замороженного кадра средствами
|
||||
SDL; нижняя полоса рисуется с alpha=0, чтобы сквозь неё просвечивало
|
||||
«GAME PAUSED». Прямого аналога на Sprinter НЕТ (альфа-блендинг в железе
|
||||
отсутствует) — это SDL-специфика, переносить нечего.
|
||||
- Ролики/переходы: `fade_in_2/fade_out_2(rows)` (seg009.c:3947+, вызовы из
|
||||
seg000.c) — ПОШАГОВОЕ затухание ПАЛИТРЫ к чёрному и обратно: палитра
|
||||
копируется, каждая строка по 16 цветов гасится за несколько кадров
|
||||
(`which_rows` маской выбирает, какие строки участвуют: 0x800/0x1000/...).
|
||||
Вот этот механизм на Sprinter воспроизводим один в один.
|
||||
|
||||
Отсюда рабочая гипотеза: наш примитив = «снимок текущей палитры → ступенчатое
|
||||
приближение к затемнённой копии (кроме резервного блока для UI)», статично для
|
||||
меню и анимированно для роликов/переходов. Варианты:
|
||||
|
||||
**Вариант А — единая основная палитра (глобальный рефакторинг палитры).**
|
||||
|
||||
1. Собрать ВСЕ палитры игры (уровневые наборы `pal_env*`, kid.pal, палитра
|
||||
Тени и пр.) в одну общую 256-цветную; использовать её целиком всегда.
|
||||
Сейчас переиспользования цветов НЕТ — каждая загрузка ассетов перезаписывает
|
||||
слоты (см. pop_boot: kid.pal затирает тайловые цвета, приходится
|
||||
восстанавливать `pop_bg_pal_apply`/`pop_shadow_pal_apply`).
|
||||
2. Для затенения — затемнённая копия основной палитры, КРОМЕ зарезервированного
|
||||
блока из 16 цветов для самого меню (кандидат — стандартные 16 цветов VGA).
|
||||
3. Выход из меню — возврат к полной основной палитре.
|
||||
|
||||
Плюс: решает попутно существующую боль с перезаписью палитр при загрузках.
|
||||
Минус: большой разовый рефакторинг упаковщиков и всех загрузчиков атласов;
|
||||
нужен аудит, что все цвета всех уровней влезают в 256. **Против говорит
|
||||
план перевода камней подземелья на цвета VGA-версии PoP: там ряд уровней
|
||||
несёт ДРУГУЮ палитру, отличную от SDLPoP (VDUNGEON/VPALACE каскад,
|
||||
levels_plan.md), — единая палитра этому прямо противоречит.**
|
||||
|
||||
**Вариант Б — динамический снимок текущей палитры (сейчас выглядит
|
||||
предпочтительным).**
|
||||
|
||||
1. При открытии меню прочитать всю текущую палитру, сохранить.
|
||||
2. Записать затемнённую копию (кроме зарезервированного блока для меню).
|
||||
3. При выходе — восстановить сохранённую.
|
||||
|
||||
Плюс: локальная правка внутри меню, ничего в пайплайне ассетов не меняется;
|
||||
работает при любой текущей палитре автоматически — включая будущие
|
||||
уровне-специфичные палитры VGA-камней; тот же примитив ступенями даёт
|
||||
fade-out/fade-in для роликов и переходов между уровнями (как fade_*_2 в
|
||||
SDLPoP). Минус: чтение/запись 256 записей палитры при входе/выходе (раз на
|
||||
открытие — дёшево); затемнение «на глаз» может по-разному выглядеть на разных
|
||||
уровнях.
|
||||
|
||||
Резервный блок 16 цветов нужен в ОБОИХ вариантах; текущий диапазон 0x37..0x3F
|
||||
(стабильный, проверен) даёт 9 цветов — этого может не хватить на
|
||||
текст+подсветку+рамку, тогда резервировать отдельный блок.
|
||||
|
||||
**Следствие для архитектуры:** работа с цветом/палитрой должна собраться в
|
||||
ОДИН модуль (сейчас она разбросана: gfx_pal_* вызовы в boot, pop_bg_pal_apply,
|
||||
pop_shadow_pal_apply, вспышки урона в roomtest.c и т.д.). Модуль палитры —
|
||||
единственный владелец записи в палитру и предоставляет примитивы, которые
|
||||
понадобятся и меню, и роликам:
|
||||
|
||||
```text
|
||||
pal_snapshot()/pal_restore() — снимок/восстановление всей палитры
|
||||
pal_dim(step) / pal_undim(step) — ступени затемнения (кроме резервного блока)
|
||||
pal_fade_out(rows)/pal_fade_in(rows) — анимированное затухание по строкам
|
||||
(порт fade_out_2/fade_in_2, seg009)
|
||||
```
|
||||
|
||||
Меню уже использует snapshot+dim локально в bank 9. Когда появятся ролики,
|
||||
выделить из него общий palette/fade-модуль; вспышка урона сможет переехать
|
||||
туда же после отдельного аудита.
|
||||
|
||||
## 11. Диалоги
|
||||
|
||||
Общий диалог подтверждения:
|
||||
@@ -367,6 +463,10 @@ YES / NO
|
||||
|
||||
Диалог не выполняет действие напрямую: он возвращает решение автомату меню,
|
||||
который формирует команду приложению. Так UI не зависит от gameplay-модулей.
|
||||
По умолчанию выбран `NO`; Up/Down/Left/Right меняют ответ, Enter подтверждает,
|
||||
Esc отменяет. Реализованы все три вопроса: Quit, Restore defaults и backup
|
||||
QuickLoad. В Quit-dialog вопрос и `YES / [NO]` заключены в общую рамку;
|
||||
отдельная строка `Enter: Select Esc: Cancel` не выводится.
|
||||
|
||||
## 12. Будущий ENHANCED
|
||||
|
||||
@@ -387,15 +487,15 @@ YES / NO
|
||||
|
||||
| этап | результат | критерий приёмки |
|
||||
|---|---|---|
|
||||
| **MS0** | определить команды app/menu и структуру settings | UI не вызывает gameplay internals напрямую |
|
||||
| **MS0** ✓ | определить команды app/menu и структуру settings | UI возвращает команду главному циклу; прямых gameplay-вызовов нет |
|
||||
| **MS1** | проверить запись/rename/copy на HDD DSS | crash/power-loss сценарий не теряет обе копии save |
|
||||
| **MS2** | `POP.CFG`: defaults, load, validate, save | повреждённый CFG безопасно даёт defaults |
|
||||
| **MS2** ✓ | `POP.CFG`: defaults, load, validate, save | v1 codec, будущий хвост, checksum; повреждённый CFG даёт defaults |
|
||||
| **MS3** | QuickSave hotkeys + POP.SAV/BAK | полный критерий `quicksave_plan.md` |
|
||||
| **MS4** | текстовый рендерер + два шрифта (малый для пунктов, крупный для сообщений) + минимальный pause menu | рендерер строк (глифы, локали, центрирование); все семь пунктов доступны и корректно паузят игру |
|
||||
| **MS5** | General/Gameplay Settings | значения применяются и переживают рестарт |
|
||||
| **MS6** | dialogs + backup recovery | подтверждения и fallback на POP.BAK |
|
||||
| **MS7** | Controls help | полная актуальная раскладка на экране |
|
||||
| **MS8** | интеграция с title/build info | CFG применяется до первого экрана |
|
||||
| **MS4** ✓ | текстовый рендерер + два шрифта (малый для пунктов, крупный для сообщений) + минимальный pause menu | SDLPoP fonts в одном W0-atlas, центрирование, dim/restore palette и tear-free page flip; все семь пунктов видимы, навигация и Resume/QuickSave работают в MAME |
|
||||
| **MS5** ✓ | General/Gameplay Settings | значения применяются сразу и после Back/Esc записываются в POP.CFG |
|
||||
| **MS6** ✓ | dialogs + backup recovery | подтверждения default-NO; QuickLoad спрашивает перед валидным POP.BAK |
|
||||
| **MS7** ✓ | Controls help | показаны движение, action, menu, save/load, звук, speed и conditional cheats; MAME проверил отдельные K/I и Shift+L/U и возврат Esc ровно на один уровень |
|
||||
| **MS8** ✓ | build info | CFG читается до первого показа; включаемый build screen получает ID и дату из Make/git |
|
||||
|
||||
QuickSave (`MS1/MS3`) можно реализовать раньше визуального menu: сначала
|
||||
F6/F9 и сообщения, затем подключить те же команды к пунктам UI.
|
||||
@@ -409,8 +509,12 @@ F6/F9 и сообщения, затем подключить те же кома
|
||||
- Host: SAV invalid -> BAK valid; оба invalid -> NO QUICKLOAD.
|
||||
- MAME: F6, изменение сцены, F9; затем рестарт программы и повторный F9.
|
||||
- MAME: прервать запись/испортить SAV — BAK остаётся загружаемым.
|
||||
- MAME: pause на бое/падении, Resume не меняет состояние и таймер.
|
||||
- MAME: pause на бое/падении, Resume не меняет состояние и таймер; смена
|
||||
выбранного пункта не показывает промежуточный кадр и после закрытия не
|
||||
оставляет меню на второй странице.
|
||||
- MAME: Settings сохраняются после полного выхода и запуска с HDD.
|
||||
- MAME: включить Show Sprinter screen, перезапустить `roomtest`, увидеть
|
||||
build ID/date до первого игрового кадра и пропустить экран Esc/Enter/Space.
|
||||
- Проверка лимита 8 DSS handles на каждом error path.
|
||||
- `make size-check`; menu/text строки не должны съесть резидентный бюджет.
|
||||
|
||||
@@ -423,4 +527,3 @@ F6/F9 и сообщения, затем подключить те же кома
|
||||
- key rebinding;
|
||||
- SDL visual/controller options;
|
||||
- фактическая реализация ENHANCED и individual fix switches.
|
||||
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
# План: консолидация работы с палитрами + переход уровня через fade
|
||||
|
||||
Статус: **этапы A и B реализованы; визуальная приёмка полного маршрута ещё
|
||||
идёт** (2026-08-24). Палитры выделены в bank 10, а renderer cutscene/intro —
|
||||
в bank 11, чтобы не переполнять bank 9 оболочки.
|
||||
Обсуждение велось вокруг `roomtest/` (банк 9 — оболочка, fade из `pop_ui.c`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Текущее состояние: карта палитры
|
||||
|
||||
Палитра Sprinter — 256 записей по 4 байта (B, G, R, 0) = 1 КБ на страницу.
|
||||
У страниц дабл-буфера ДВЕ раздельные палитры (`gfx_pal_load(0,…)` и
|
||||
`gfx_pal_load(1,…)` — почти всегда парой). BIOS читает буферы только из
|
||||
#4000–#BFFF: банковую rodata напрямую отдавать нельзя (копия в стек/W2),
|
||||
см. грабли `pop_guard_set_palette` и `bg_load_tile_pal`.
|
||||
|
||||
### 1.1 Игровая палитра `KID\kid.pal` — раскладка слотов
|
||||
|
||||
Собирается `toolchain/pop_pack_kid.py build_palette()`, грузится одним
|
||||
`gfx_pal_fload` (перезаписывает все 256 записей). Атласы запекались под эти
|
||||
индексы — менять раскладку нельзя без перепаковки ассетов.
|
||||
|
||||
| Слоты | Назначение | Источник | Динамика |
|
||||
|---|---|---|---|
|
||||
| 0x00 | Цвет фона + **вспышка молнии** (подмена записи 0, `flash_bg` ← do_flash/set_bg_attr SDLPoP) | — | меняется в игре |
|
||||
| 0x01–0x2F | Не закреплены (нули) | — | свободно |
|
||||
| 0x30–0x3F | VGA16 — базовые 16 цветов для mono-блитов: пламя факелов, пузырьки зелья (+12 красный «лечение», +10 зелёный, +9 синий), кровь чомпера (12), дворцовая кладка mono (+6) | `VGA16[]` | статично |
|
||||
| ↳ 0x37–0x3F | Поддиапазон **UI**: текст/рамка меню; единственное, что `keep_ui` не затемняет (`MENU_BORDER`=0x37) | — | — |
|
||||
| 0x40–0x4F | chtab_1 пламя/зелья (`POT_PAL_BASE`) | VDUNGEON res150.pal | статично |
|
||||
| 0x50–0x5F | **ENV фон тайлсета** (`POP_PAL_ENV`) | res200.pal набора | **меняется при смене тайлсета** |
|
||||
| 0x60–0x6F | **WALL тайлсета** (`POP_PAL_WALL`) | res360.pal набора | **меняется при смене тайлсета** |
|
||||
| 0x70–0x7F | Kid (`PAL_BASE`) | KID res400.pal | статично |
|
||||
| 0x80–0x8F | Меч chtab_0 (`SWORD_PAL_BASE`) | POT res700.pal | статично |
|
||||
| 0x90–0x9F | Страж chtab_5 (`GUARD_PAL_BASE`) | res10.bin guard_palettes | **меняется по КОМНАТАМ** |
|
||||
| 0xA0–0xAF | Тень (`POP_SHADOW_PAL_BASE`) | RGB-сетка pop_pack_shadow.py | статично |
|
||||
| 0xB0–0xFF | Свободны (5 слотов) | — | — |
|
||||
|
||||
Итого динамических зон три: запись 0 (молния), env+wall (тип здания),
|
||||
стражи (per-room). Всё остальное одинаково всю игру.
|
||||
|
||||
### 1.2 Полноэкранные палитры заставок
|
||||
|
||||
Каждая перезаписывает ВСЕ 256 записей:
|
||||
|
||||
| Файл | Где используется |
|
||||
|---|---|
|
||||
| `KID\kid.pal` (+ fallback `a:\kid.pal`) | BOOT и возврат в игру после заставок |
|
||||
| `TITLE\title.pal` | экран TITLE |
|
||||
| `PV\story.pal` | INTRO и HALL_OF_FAME (одна палитра на обе фазы) |
|
||||
|
||||
### 1.3 Тайлсеты: подземелье ↔ дворец
|
||||
|
||||
Оба набора используют ОДНИ И ТЕ ЖЕ слоты 0x50–0x5F/0x60–0x6F, заполняя их
|
||||
разными цветами (атласы обоих наборов запекались под эти индексы).
|
||||
Переключение = загрузка 64 байт (32 записи env+wall) в обе страницы
|
||||
(`bg_load_tile_pal`); остальные 224 записи не трогаются.
|
||||
|
||||
Какие уровни дворец — `tbl_level_type` (`pop_level_cold.c:44`):
|
||||
**4, 5, 6, 10, 11, 14**; остальные подземелье.
|
||||
|
||||
Палитра дворца `pal_tile.pal` (расшифровка, формат записи B,G,R):
|
||||
|
||||
ENV 0x50–0x5F (пол, ковры, факелы, ворота, пики, арки):
|
||||
|
||||
| Слот | RGB | | Слот | RGB |
|
||||
|---|---|---|---|---|
|
||||
| 50 | 0,0,0 чёрный | | 58 | 202,190,178 серо-бежевый |
|
||||
| 51 | 121,89,60 коричневый | | 59 | 153,133,129 серо-лиловый |
|
||||
| 52 | 161,121,76 светло-коричневый | | 5A | 76,64,56 тёмный серо-бурый |
|
||||
| 53 | 194,149,89 песочный | | 5B | 153,97,89 кирпично-красный |
|
||||
| 54 | 230,178,113 яркий песок | | 5C | 137,80,72 тёмный кирпич |
|
||||
| 55 | 246,202,125 кремовый | | 5D | 48,125,125 бирюзовый |
|
||||
| 56 | 255,234,170 бледно-кремовый | | 5E | 12,56,89 тёмно-синий |
|
||||
| 57 | 255,255,255 белый | | 5F | 202,56,28 красно-оранжевый |
|
||||
|
||||
WALL 0x60–0x6F (вся палитра песочная): 61=(218,170,89), 62=(226,165,93),
|
||||
63=(226,170,97), 64=(218,161,85), 65=белый, 66=(226,165,93), 67=(218,165,89),
|
||||
68=(226,170,89), 69=(218,170,97), 6A=(255,210,137), 6B=(255,218,149),
|
||||
6C=(255,210,137), 6D=(255,218,145), 6E=(194,153,80 тёмный песок),
|
||||
6F=(238,186,117).
|
||||
|
||||
Чем рисуется во дворце:
|
||||
- **Тело стены — НЕ спрайты**, а сплошные заливки; цвет разыгрывается на
|
||||
комнату prandom'ом (`gen_palace_wall_colors`, `pop_bg.c:140`, порт
|
||||
seg000:1942): подряды 1 и 3 берут случайный из 0x61–0x64, подряды 0 и 2 —
|
||||
из 0x66–0x69; соседи по горизонтали не повторяются.
|
||||
- Декор стен id 3–17 — mono-силуэт цветом VGA16+6 (0x36).
|
||||
- Верх дверных проёмов дворца — спец-id 78–84 + полоса 145 («полоса под
|
||||
окнами», pop_room.c:478).
|
||||
- Остальное (пол, ковры, порталы-факелы, ворота, пики) — env-куски
|
||||
pal_env*.atl с ENV-таблицей выше.
|
||||
|
||||
### 1.4 Стражи (0x90–0x9F)
|
||||
|
||||
Цвет задаётся на КОМНАТУ (`level.guards_color[room-1]`), при входе в
|
||||
комнату зовётся `pop_guard_set_palette(color)` ДО отрисовки (слоты общие
|
||||
на экран — смена посреди кадра дала бы стража в новой палитре с полосой HP
|
||||
в старой). Только для обычных стражей (`tbl_guard_type == 0`): скелет и
|
||||
Джафар имеют собственную палитру, зашитую в kid.pal; им зовётся с color=0
|
||||
(не трогать — иначе Джафар на ур.13 покрасился бы в цвет стража своей
|
||||
комнаты). Внутри одного уровня слоты могут перезаписываться многократно.
|
||||
|
||||
## 2. Текущее состояние: механика fade
|
||||
|
||||
### 2.1 Наша реализация (`pop_ui.c`, банк 9)
|
||||
|
||||
- `pop_ui_palette_snapshot()` — снимок всех 256 записей через
|
||||
`gfx_pal_get` по 4 чанкам × 64; хранится в хвосте страницы шрифта
|
||||
FONT.ATL ([0x3C00,0x4000)), map/unmap W0. Требует `font_ready`.
|
||||
- `pop_ui_palette_dim(step, keep_ui)` — готовит ОБЕ экранные палитры из
|
||||
снимка. Шкала без умножений (только сдвиги):
|
||||
|
||||
| Шаг | Формула на канал | Яркость |
|
||||
|---|---|---|
|
||||
| 0 | x | оригинал |
|
||||
| 1 | `(x>>1)+(x>>2)` | ≈3/4 |
|
||||
| 2 | `x>>1` | 1/2 |
|
||||
| 3 | `x>>2` | 1/4 |
|
||||
| 4 | 0 | чёрный |
|
||||
|
||||
`keep_ui` пропускает 0x37–0x3F (меню остаётся ярким).
|
||||
- `pop_ui_fade_out/in(steps)` — проигрывание ступеней за `steps` кадров
|
||||
vsync (`step = i*4/steps`, целочисленно): steps=4 — канонический (по кадру
|
||||
на ступень), steps<4 — перескакивает ступени, steps>4 — повторяет (плавнее),
|
||||
steps=0 у fade_in — мгновенный restore.
|
||||
- Контракт map/unmap: обращения к EMM/W0 и BIOS-палитре строго после unmap.
|
||||
|
||||
Стоимость одного dim ≈ 15–25 тыс. тактов (~4–7 мс при 3.5 МГц) —
|
||||
укладывается в кадр vsync, на практике лагов нет.
|
||||
|
||||
### 2.2 Как сделано в SDLPoP (seg009.c, USE_FADE/gmMcgaVga)
|
||||
|
||||
- fade_out: каждый кадр КАЖДЫЙ ненулевой канал каждой записи −1; до нуля.
|
||||
- fade_in: `fade_pos` от 0x40 вниз; канал +1, пока меньше оригинала.
|
||||
- Уровней затемнения до 63–64 (VGA-канал 6 бит), полный фейд ~63 кадра ×
|
||||
wait_time=2 тика — медленно и кинематографично.
|
||||
- `which_rows` — битовая маска групп по 16 записей: можно фейдить часть
|
||||
палитры (в оригинале используется).
|
||||
- По завершении принудительно восстанавливается оригинал; после out экран
|
||||
заливается чёрным.
|
||||
|
||||
Это осознанное расхождение (скорость/такты vs плавность) — ЗАПИСАТЬ в
|
||||
`docs/impl_diff.md` (сейчас записи нет).
|
||||
|
||||
## 3. Зафиксированные решения
|
||||
|
||||
1. **Ступени затемнения: остаются 4.** Вариант 8 ступеней той же сдвиговой
|
||||
техникой — рассмотреть отдельно, сейчас не внедрять.
|
||||
2. **Предрасчёт fade-вариантов палитры отклонён.** Аргументы: чтение файла
|
||||
с диска на порядок дороже вычисления; 3–7 КБ постоянной RAM при
|
||||
MEMORY=small непозволительны; предрасчёт привязан к конкретным палитрам,
|
||||
а снимок работает с любой текущей автоматически; keep_ui удвоил бы набор.
|
||||
3. **Считать на лету**, хранить один снимок (уже есть, бесплатно в хвосте
|
||||
страницы шрифта).
|
||||
4. **Буферы на стеке**, не статика (W1/W2 мало) и не 1 КБ: обнулить 64/256
|
||||
байт дешевле, чем держать килобайт резидентно.
|
||||
5. **Контракт `gfx_pal_load(pal, start, count, data)`**: count — число
|
||||
СЛОТОВ, буфер обязан быть `count*4` байт; count=0 означает «все 256».
|
||||
6. **Leaf-applеры остаются на месте** (`pop_bg_pal_apply` — банк 7 со своими
|
||||
таблицами, `pop_shadow_pal_apply`, `pop_guard_set_palette`): банковая
|
||||
rodata чужого банка не видна, перенос сломал бы доступ к данным.
|
||||
7. **Молния (`flash_bg` в roomtest.c) не переносится** — игровой эффект
|
||||
записи 0; после вспышки восстановление записи 0 из снимка ложится на API.
|
||||
8. Модель состояния: разделены «какая палитра логически загружена» (load_*)
|
||||
и «с какой яркостью показана» (apply/fade). Любой load_* обновляет снимок;
|
||||
apply/fade показывает его с нужной глубиной. Это позволяет грузить новую
|
||||
палитру «в темноте» (экран остаётся чёрным, пока не позвали apply/fade_in).
|
||||
|
||||
## 4. Целевой API `pop_pal.c/.h` (банк 9)
|
||||
|
||||
```c
|
||||
/* сброс */
|
||||
void pop_pal_black(void) __banked;
|
||||
/* все 256 записей ОБЕИХ страниц = 0. Стековый buf[256], обнуление циклом,
|
||||
* 8 вызовов gfx_pal_load (4 чанка × 2 страницы, паттерн как в dim).
|
||||
* Зовётся СРАЗУ ПОСЛЕ initgraph в pop_boot (раньше нельзя — нет гарантий
|
||||
* состояния графического режима): закрывает кейс «мусор/палитра предыдущей
|
||||
* программы при включении графики». СНИМОК НЕ ТРОГАЕТ (контракт:
|
||||
* чёрный экран без изменения логической палитры). */
|
||||
|
||||
/* загрузка (пишет полную палитру в обе страницы + refresh снимка;
|
||||
* видимую яркость НЕ трогают — экран меняется только по apply/fade) */
|
||||
void pop_pal_file_load(const char *name) __banked;
|
||||
/* gfx_pal_fload + fallback "a:\" + gfx_pal_sync (fallback сегодня
|
||||
* скопирован в каждом из ~6 мест вызова) */
|
||||
|
||||
void pop_pal_game_load(void) __banked;
|
||||
/* file_load("KID\kid.pal") + pop_bg_pal_apply + pop_shadow_pal_apply.
|
||||
* Сегодня тройка скопирована 3 раза (roomtest_cold ~958, pop_title ~88,
|
||||
* pop_intro ~183). Единое место инварианта «kid.pal затирает слоты
|
||||
* тайлсета 0x50..0x6F и тени 0xA0..0xAF». */
|
||||
|
||||
void pop_pal_level_load(uint8_t full) __banked;
|
||||
/* палитра уровня: kid.pal/shadow + tileset 0x50..0x6F если набор сменился
|
||||
* (сравнение через pop_level_type()). full=1 — ПРИНУДИТЕЛЬНО перечитать
|
||||
* kid.pal/shadow (один экспорт с флагом, не две функции — меньше банковых
|
||||
* точек входа). СТРАЖЕЙ (0x90..0x9F) НЕ включает: это компетенция входа
|
||||
* в комнату (pop_guard_set_palette до первого draw). */
|
||||
|
||||
void pop_pal_story_load(void) __banked; /* PV\story.pal (INTRO и HOF — файл один, функция одна) */
|
||||
void pop_pal_title_load(void) __banked; /* TITLE\title.pal */
|
||||
|
||||
/* отображение */
|
||||
void pop_pal_snapshot(void) __banked; /* переезд из pop_ui, тело то же */
|
||||
void pop_pal_apply(uint8_t fade) __banked; /* = dim(fade, 0), 0..4 */
|
||||
void pop_pal_fade_in(uint8_t steps) __banked; /* переезд из pop_ui */
|
||||
void pop_pal_fade_out(uint8_t steps) __banked;
|
||||
|
||||
/* меню продолжает звать низкоуровневый dim(step, keep_ui=1) — отдельный
|
||||
* тонкий экспорт, чтобы не тащить флаг в горячий apply. Старые имена
|
||||
* pop_ui_palette_* / pop_ui_fade_* УДАЛЯЮТСЯ (без алиасов — меньше
|
||||
* экспорта банка). */
|
||||
```
|
||||
|
||||
Соответствие старое→новое: snapshot→snapshot, restore→apply(0),
|
||||
fade_out/in→fade_out/in, тройка kid.pal×3→game_load, fload+fallback+sync×6→file_load.
|
||||
|
||||
## 5. Этап A: рефакторинг — выполнен (2026-08-24)
|
||||
|
||||
1. Создан `roomtest/pop_pal.c/.h` в **bank 10**, добавлен в Makefile.
|
||||
Он владеет политикой `load logical palette → snapshot → apply brightness`.
|
||||
Низкоуровневые snapshot/dim/fade остаются физически в `pop_ui.c`: там
|
||||
владелец страницы FONT.ATL, где лежит снимок; наружу они доступны только
|
||||
через `pop_pal`.
|
||||
2. Заменены call-sites:
|
||||
- `roomtest_cold.c` ~958: black → game_load вместо тройки;
|
||||
- `pop_title.c` title_restore_game_palette → game_load; загрузка title.pal → title_load;
|
||||
- `pop_intro.c` intro_load/intro_restore → story_load/game_load;
|
||||
- `pop_hof.c` (2 × story.pal) → story_load;
|
||||
- `pop_menu.c`: fade/dim → новые имена (dim с keep_ui — низкоуровневый экспорт);
|
||||
- `roomtest.c` demo-start (snapshot+dim(4,0)+fade_in(4)) → новый API.
|
||||
3. Старые вызовы не остаются в коде приложения; внутренние функции `pop_ui`
|
||||
сохранены как реализации одного владельца памяти снимка.
|
||||
4. Сборка и host-тесты пройдены. `make size-check` неприменим: меняется
|
||||
приложение, а не libc/libbgi.
|
||||
5. MAME smoke-тест полного цикла смен палитр: boot → title (title.pal +
|
||||
fade) → intro (story/kid) → demo fade-in → игра → HOF (story.pal).
|
||||
Проверить: отсутствие мусора при включении графики (эффект black),
|
||||
меню с keep_ui остаётся ярким при затемнении, молния (запись 0)
|
||||
восстанавливается.
|
||||
|
||||
## 6. Этап B: переход уровня через fade — реализован, ждёт визуальной приёмки
|
||||
|
||||
Сценарий (обсуждён, детали уточнить по SDLPoP перед реализацией — как
|
||||
оригинал делает смену уровня, есть ли там fade в DOS-версии):
|
||||
|
||||
```
|
||||
fade_out // последний кадр уровня N темнеет
|
||||
рисуем комнату 1 уровня N+1 // во ВТОРУЮ страницу, в темноте
|
||||
pop_pal_level_load(full=0) // новая палитра: железо+снимок обновлены,
|
||||
// экран всё ещё чёрный
|
||||
флип + копия второй страницы обратно в первую
|
||||
fade_in // = анимированный apply 3→2→1→0
|
||||
```
|
||||
|
||||
Экономия: реально переезжают только 32 записи (env/wall) при смене набора
|
||||
dungeon↔palace; guards_color обновит вход в комнату. Kid/shadow не меняются
|
||||
— потому full=0.
|
||||
|
||||
Реализация находится в `roomtest.c` / `roomtest_cold.c`: последний кадр
|
||||
уровня N темнеет, `pop_level_switch()` подготавливает первый кадр N+1 и
|
||||
обновляет логический источник через `pop_pal_level_load(1)`, затем главный
|
||||
цикл показывает кадр только через fade-in. Восемь ступеней и отдельная
|
||||
анимация смерти не входят в этот этап.
|
||||
|
||||
**Этап B закрывает два открытых бага** (разборы — `roomtest/BUGS_OPEN.md`):
|
||||
- [PAL-L1-AFTER-INTRO] — вход в игру на уровень 1 после интро с чёрным
|
||||
экраном (маршрут demo_new_game; корень не установлен, воспроизведение
|
||||
нестабильно);
|
||||
- [PAL-DUNGEON-STALE] — переход 3→4 оставляет подземную палитру (корень
|
||||
ясен: fade_in восстанавливает из снимка, снятого ДО загрузки тайлсета
|
||||
дворца; быстрый фикс `fade_in_pending` 2026-08-23 сам же и проявляет этот
|
||||
дефект модели).
|
||||
|
||||
Быстрый фикс 2026-08-23 (маршрут CUTSCENE → LEVEL_LOAD → PLAYING,
|
||||
`fade_in_pending` + `pop_ui_fade_in(4)` после `pop_level_switch`) закрыл
|
||||
чёрный экран на переходах с pre-cutscene внутри подземелья (1→2), но модель
|
||||
«кто и когда меняет яркость» остаётся разношёрстной — её и приводит в
|
||||
порядок этап B.
|
||||
|
||||
## 7. Этап C: документирование
|
||||
|
||||
- Запись в `docs/impl_diff.md`: наши 4 ступени vs SDLPoP ~64 (что делает
|
||||
оригинал, что делаем мы — сдвиговая шкала ради тактов, чем платим —
|
||||
грубее градации, что проверять при регрессе).
|
||||
- После этапа B — дополнить запись про сам переход.
|
||||
|
||||
## 8. Не трогаем
|
||||
|
||||
- Молнию (`flash_bg`, roomtest.c) — включая обход SDCC-бага
|
||||
`gfx_pal_set(0,0,0,0,0)` → ручные `gfx_pal_set(0/1, 0, r,g,b)`;
|
||||
- leaf-applеры: `pop_bg_pal_apply` (банк 7), `pop_shadow_pal_apply`,
|
||||
`pop_guard_set_palette` (данные своих модулей);
|
||||
- хранилище снимка в хвосте страницы шрифта FONT.ATL (бесплатное место,
|
||||
guard `font_ready`);
|
||||
- раскладку слотов 0x00–0xAF (зафиксирована атласами).
|
||||
@@ -1,6 +1,6 @@
|
||||
# Звук в порте PoP — разбор и план
|
||||
|
||||
Дата: 2026-08-20. Статус: **разбор, кода нет.**
|
||||
Дата: 2026-08-20. Статус: **PCM-эффекты реализованы; музыка/AY не реализованы.**
|
||||
|
||||
Задача пользователя: добавить звук. Приоритет — эффекты; музыку, если
|
||||
найдётся способ. Эффекты — **обязательно WAV, а не PC-спикер**
|
||||
@@ -351,6 +351,7 @@ IBM PC 1989 года: один квадратный голос. Он у нас
|
||||
| 4/5/6/7 | ворота: закрываются / открываются / рухнули / стоп | `pop_trob.c` | seg007 |
|
||||
| 8 | удар о стену | `pop_map.c` bumped_fall/bumped_floor + seqtbl | seg004/seg006 |
|
||||
| 9 | зацеп за карниз | `pop_map.c` check_grab | seg006 |
|
||||
| 10 | клинок о клинок | `roomtest.c` после `check_sword_hurt`, если один из бойцов в кадре 167 | seg000:1353 |
|
||||
| 11 | свист клинка мимо | `guards.c` check_hurting | seg002:0DAE |
|
||||
| 12/13 | ранен соперник / Кид | `guards.c` hurt_by_sword | seg002:0C1F |
|
||||
| 13 | Кид ранен зельем | `pop_map.c` ветка «злого» зелья | seg006:1894 |
|
||||
@@ -368,7 +369,8 @@ IBM PC 1989 года: один квадратный голос. Он у нас
|
||||
| 48 | напоролся на пики | `pop_map.c` | seg005 |
|
||||
| 49 | пики пошли | `pop_map.c` start_anim_spike | seg007:08F6 |
|
||||
|
||||
Что осталось не разведено — только МУЗЫКА (25 презентация, 26 объятия,
|
||||
Что осталось не разведено — только МУЗЫКА (24/28 смерть, 25 презентация,
|
||||
26 объятия,
|
||||
27/35/40 заставки, 29 встреча Джафара, 30/33 зелья, 32/41 конец уровня,
|
||||
36 время вышло, 37 победа, 43 смерть Джафара, 50/52/53 сюжетные вставки)
|
||||
и 51 (дверь принцессы, тоже из заставки). Их черёд — фаза «музыка».
|
||||
@@ -641,3 +643,42 @@ play_next_sound() seg000:1304 — раз в кадр решает, зап
|
||||
нужно сходить Кидом на левую кнопку и вернуться. Это поведение оригинала
|
||||
(trob челюстей создаётся событием), а не наш дефект — учитывать при
|
||||
постановке автотестов.
|
||||
|
||||
## 13. Повторный аудит игрового звука (2026-08-24)
|
||||
|
||||
Проверены три независимых слоя: содержимое атласов, места вызова и живой
|
||||
тракт `play -> tick -> CBL`.
|
||||
|
||||
- В восьми `SND*.ATL` есть все 31 оцифрованных ресурса: `0..23`, `44..49`
|
||||
и `51`; ненулевая страница/длина есть у каждой записи таблицы.
|
||||
- Для всех 30 PCM-эффектов, которые могут возникать непосредственно в игре,
|
||||
есть место вызова. Последним пропуском был звук 10 при столкновении
|
||||
клинков; условие перенесено буквально из `check_sword_vs_sword` SDLPoP.
|
||||
Звук 51 относится к сцене с принцессой, а не к игровому циклу.
|
||||
- Звук 19 «Кид достал меч» проверен в MAME брейкпоинтами. В момент вызова
|
||||
предыдущий PCM уже закончился (`sfx_left=0`), номинация дошла до
|
||||
`pop_sfx_tick`, после чего курсор получил id 19, страницу 5, смещение
|
||||
`0x1500` и длину 2816 байт. В этом прогоне его не подавляли решётка,
|
||||
плиты, шаги или приоритеты. Если он всё ещё субъективно не слышен, искать
|
||||
надо после выбора эффекта — в непрерывности CBL/громкости самого сэмпла.
|
||||
- После продолжительного прогона title/intro/demo/menu CBL обслужил 3303
|
||||
блока и сообщил 0 программных недоливов (`cbl_requests=0x0CE7`,
|
||||
`cbl_underruns=0`). Это исключает возврат `fill=0`, но само по себе не
|
||||
измеряет запоздание прерывания внутри слишком длинной секции `DI`.
|
||||
- Регрессия full-game на входе в Level 1 оказалась именно запозданием CBL:
|
||||
`pop_level_switch` открывал его ДО блокирующего BIOS fade-in. Demo был
|
||||
чистым, потому что включал палитру без fade. Теперь загрузчик оставляет
|
||||
CBL закрытым, а caller открывает его после окончательной палитры/QuickLoad;
|
||||
скрежет на Level 1 исчез в MAME. Однократный щелчок самого первого
|
||||
`cbl_open` за всю MAME-сессию остаётся отдельной низкоприоритетной задачей.
|
||||
- Открытие pause menu у SDLPoP беззвучно; движение играет 21, вход/выход
|
||||
из подменю — 22, изменение настройки — 10. Эти вызовы перенесены. На
|
||||
время полного копирования страницы и файловых операций CBL закрывается,
|
||||
после flip открывается снова: аппаратная половина не должна повторять
|
||||
старые данные и давать «скрежет».
|
||||
|
||||
Отдельно остаётся игровая музыка и сигнальные мелодии без PCM: смерть
|
||||
(`24/28`), начало/появление Shadow (`25`), встреча Jaffar (`29`), большое
|
||||
и малое зелья (`30/33`), Shadow (`32`), победа/меч (`37`), перо (`39`),
|
||||
конец уровня (`41`) и победа над Jaffar (`43`). Вызовы и AY-проигрыватель
|
||||
для них ещё не реализованы; наличие всех PCM-эффектов эту задачу не закрывает.
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# Текст в нижней статус-строке (строке HP) — полная инвентаризация SDLPoP
|
||||
|
||||
Разбор `SDLPoP/src/` на 2026-08-25. Цель — знать ВЕСЬ набор сообщений,
|
||||
которые оригинал печатает в ту же полосу, где нарисованы деления HP,
|
||||
и правила их появления/исчезновения. Это входные данные для порта:
|
||||
у нас пока туда пишется только `GAME PAUSED`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Геометрия: одна полоса на HP и на текст
|
||||
|
||||
```
|
||||
rect_bottom_text = { top 193, left 70, bottom 202, right 250 } // data.h:217
|
||||
display_text_bottom: draw_rect(чёрным) + show_text(halign_center, valign_bottom)
|
||||
```
|
||||
|
||||
* Деления HP **Кида** — от `x = 0` вправо, шаг 7, максимум 10 → занимают `x 0..69`.
|
||||
* Деления HP **стража** — от `x = 314` влево, шаг 7, максимум 10 → занимают `x 245..320`.
|
||||
* Текст живёт РОВНО в промежутке `x 70..250` и по X с делениями не пересекается.
|
||||
* По Y деления на `y = 194..200`, текст (`valign_bottom` к 202) — на `y = 195..201`,
|
||||
то есть на строку ниже. Именно поэтому в оригинале текст выглядит «сидящим»
|
||||
чуть ниже стрелок HP.
|
||||
|
||||
**У нас**: `POP_HP_Y = 194` (`pop_cdraw.h`), экран сдвинут на `POP_YOFF = 28`,
|
||||
базовая линия крупного шрифта `POP_YOFF + POP_HP_Y + 8 = 230` — силуэт
|
||||
ложится на `223..229`, то есть та же картинка.
|
||||
|
||||
## 2. Два примитива и два таймера
|
||||
|
||||
| Имя | Что делает |
|
||||
|-----|------------|
|
||||
| `display_text_bottom(text)` (seg008:2644) | стереть прямоугольник цветом 0 и напечатать текст по центру |
|
||||
| `erase_bottom_text(arg)` (seg008:266D) | стереть прямоугольник; при `arg != 0` ещё и обнулить оба таймера |
|
||||
| `text_time_remaining` | сколько игровых тиков сообщение ещё висит; 0 — ничего не висит |
|
||||
| `text_time_total` | **идентификатор сообщения**, а не только его длительность |
|
||||
|
||||
Обработка тика — в `draw_game_frame`/`idle` (seg000:956). Комментарий в
|
||||
оригинале прямой: *«Note: texts are identified by their total time!»* Значения
|
||||
`text_time_total`, у которых есть особое поведение:
|
||||
|
||||
| `total` | Смысл | Что происходит по истечении |
|
||||
|---------|-------|------------------------------|
|
||||
| 12 | «1 SECOND LEFT» | обычное стирание |
|
||||
| 24 | обычное короткое сообщение | обычное стирание |
|
||||
| 36 | смерть на демо-уровне (0) или на уровне зелий (15) — **текста нет** | `start_game()` — рестарт игры |
|
||||
| 288 | «Press Button to Continue» | `start_game()` — рестарт игры |
|
||||
| 1188 | защита от копирования (уровень 15) | **не убывает и не исчезает** |
|
||||
|
||||
Мигание: при `total == 288` и `remaining < 72` сообщение мигает с периодом 12
|
||||
тиков — 4 тика видно (`blink_frame <= 3`), 8 нет; в кадре `blink_frame == 3`
|
||||
заново печатается текст и играет звук 38 (`sound_38_blink`).
|
||||
|
||||
Сброс: `init_game()` (seg003:32) обнуляет оба таймера и `is_show_time` — то есть
|
||||
любое сообщение умирает на старте уровня.
|
||||
|
||||
---
|
||||
|
||||
## 3. Полный список сообщений
|
||||
|
||||
### 3.1. Состояние программы
|
||||
|
||||
| Текст | Где | Таймер |
|
||||
|-------|-----|--------|
|
||||
| `GAME PAUSED` | seg000:1769, пока `is_paused` | **без таймера**: печатается на входе в паузу, `erase_bottom_text(1)` на выходе (seg000:1784) |
|
||||
|
||||
### 3.2. Уровень и оставшееся время (`show_level` / `show_time`, seg008)
|
||||
|
||||
| Текст | Условие | `total` |
|
||||
|-------|---------|---------|
|
||||
| `LEVEL %d` | `show_level()` при старте уровня; только `1..12` (`hide_level_number_from_level = 14`), не при `seamless`; уровень 13 показывается как **12** (`level_13_level_number`) | 24, дальше сразу `is_show_time = 1` |
|
||||
| `%d MINUTES LEFT` | каждая минута, кратная 5, и каждая из последних 5 | 24 |
|
||||
| `%d SECONDS LEFT` | последняя минута, раз в 12 тиков | 24 |
|
||||
| `1 SECOND LEFT` | остался 1 с | **12** |
|
||||
| `TIME HAS EXPIRED!` | `rem_min == 0` | 24 |
|
||||
| `%d MINUTES PASSED` / `1 MINUTE PASSED` | только SDLPoP (`ALLOW_INFINITE_TIME`), при отрицательном таймере | 24 |
|
||||
|
||||
Что взводит `is_show_time` (все → следующий кадр печатает время):
|
||||
|
||||
* **Space** — seg000:612, штатная клавиша оригинала «сколько осталось»;
|
||||
* читы **`-`/`+` numpad** (изменение времени) — seg000:762 / 777, при этом
|
||||
таймеры сообщения обнуляются, чтобы новое напечаталось немедленно;
|
||||
* **смерть Джафара** — `on_guard_killed()` seg006:1936, уровень 13
|
||||
(`jaffar_victory_level`): вспышка + показать время;
|
||||
* истечение очередной минуты — seg008:1796;
|
||||
* сразу после `show_level()`.
|
||||
|
||||
Обнуляет `is_show_time`: `play_kid()` при смерти (seg006:1365) и
|
||||
`show_copyprot(1)` (seg000:2385).
|
||||
|
||||
### 3.3. Смерть Кида
|
||||
|
||||
| Текст | Где | `total` |
|
||||
|-------|-----|---------|
|
||||
| `Press Button to Continue` | `play_kid()` seg006:1383 — умер на обычном уровне | **288** (мигает, затем рестарт игры) |
|
||||
| *(без текста)* | тот же код, но уровень 0 (демо) или 15 (зелья) | **36** (тихая пауза, затем рестарт игры) |
|
||||
|
||||
Стирается: `fell_out()` (seg006:1342, упал из комнаты 0) и чит **R**
|
||||
(воскрешение, seg000:783) — оба зовут `erase_bottom_text(1)`.
|
||||
|
||||
### 3.4. Сохранение и загрузка
|
||||
|
||||
| Текст | Клавиша | `total` |
|
||||
|-------|---------|---------|
|
||||
| `GAME SAVED` / `UNABLE TO SAVE GAME` | Ctrl+G (`save_game`, seg000:2211) | `total` не ставится, `remaining = 24` |
|
||||
| `QUICKSAVE` / `NO QUICKSAVE` | F6 (расширение SDLPoP, seg000:497) | 24 |
|
||||
| `QUICKLOAD` / `NO QUICKLOAD` | F9 (расширение SDLPoP, seg000:514) | 24 |
|
||||
|
||||
### 3.5. Ответы на клавиши (`answer_text` → `need_show_text`, все `total = 24`)
|
||||
|
||||
| Текст | Клавиша |
|
||||
|-------|---------|
|
||||
| `SOUND ON` / `SOUND OFF` | Ctrl+S |
|
||||
| `KEYBOARD MODE` | Ctrl+K |
|
||||
| `JOYSTICK MODE` / `JOYSTICK NOT FOUND` / `JOYSTICK UNAVAILABLE` | Ctrl+J |
|
||||
| `PRINCE OF PERSIA V1.0` (в SDLPoP заменено на `SDLPoP v%s`) | Ctrl+V |
|
||||
| `SDL COMP v… LINK v…` | Ctrl+C — только SDLPoP |
|
||||
|
||||
### 3.6. Отладочные читы (`cheats_enabled`, `total = 24`)
|
||||
|
||||
| Текст | Клавиша | Смысл |
|
||||
|-------|---------|-------|
|
||||
| `S%d L%d R%d A%d B%d` | `C` | номер отрисованной комнаты и её соседей L/R/A/B |
|
||||
| `AL%d AR%d BL%d BR%d` | Shift+`C` | диагональные соседи |
|
||||
|
||||
### 3.7. Защита от копирования (только уровень 15)
|
||||
|
||||
| Текст | Где | `total` |
|
||||
|-------|-----|---------|
|
||||
| `WORD %d LINE %d PAGE %d` | `show_copyprot(1)` seg000:2389 | **1188** — висит, пока не сменится уровень |
|
||||
|
||||
### 3.8. Только SDLPoP, в оригинале 1989 отсутствует
|
||||
|
||||
| Текст | Где |
|
||||
|-------|-----|
|
||||
| `RECORDING`, `REPLAY SAVED`, `REPLAY CANCELED` | replay.c:599/626/628 |
|
||||
| имя файла скриншота | screenshot.c:62 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Что из этого касается нашего порта
|
||||
|
||||
Реализовано:
|
||||
|
||||
* `GAME PAUSED` — в строке HP, без таймера (`pop_menu.c`).
|
||||
|
||||
Есть у нас, но сообщение пока НЕ печатается (показывается иначе или не
|
||||
показывается вовсе):
|
||||
|
||||
* `LEVEL %d` и вся группа времени — таймер уже есть (`pop_timer.c`), текста нет;
|
||||
* `QUICKSAVE`/`QUICKLOAD` — у нас вместо строки экран-уведомление (`ui_notice`);
|
||||
* `SOUND ON/OFF` — у нас индикатор в отладочной полосе, а не текст;
|
||||
* `Press Button to Continue` — у нас авто-респавн по таймеру/стрелке вверх;
|
||||
* номера комнат (`C`/Shift+`C`) — у нас палочки в отладочной полосе.
|
||||
|
||||
Нам не нужно: copy protection (уровень 15 исключён из порта — см.
|
||||
`full_game_plan.md`), joystick-режимы, replay, скриншоты.
|
||||
|
||||
Механика, которую придётся портировать целиком, если брать группу времени:
|
||||
пара таймеров `text_time_total`/`text_time_remaining` с семантикой
|
||||
«идентификатор сообщения» — иначе не воспроизвести ни мигание, ни рестарт по
|
||||
истечении 36/288.
|
||||
|
||||
---
|
||||
|
||||
## 5. Цена вывода и что делать, если упрёмся
|
||||
|
||||
Блит одного глифа стоит ~4,6 тыс. тактов почти независимо от размера — это
|
||||
цена вызова, а не пикселей (memory `blit_cost_model`). Полсотни символов =
|
||||
полкадра. Что уже сделано в `pop_status.c` / `pop_ui.c`:
|
||||
|
||||
* **change-driven**: пока показанное не изменилось, не рисуем вовсе;
|
||||
* **по полям**: смена комнаты — две цифры (~9 тыс. тактов, 2% кадра), а не
|
||||
вся строка; подписи рисуются только при полной инвалидации;
|
||||
* **пробелы не блитятся**: их глиф целиком прозрачен, а стоит как буква —
|
||||
на полной отладочной строке это девять сэкономленных блитов;
|
||||
* **заливка только поля** при входе в комнату (`pop_screen_fill_field`):
|
||||
борта от комнаты к комнате не меняются, это и четверть заливки, и то, что
|
||||
обе полосы вход переживают.
|
||||
|
||||
Запас, если бюджета всё же не хватит (идеи пользователя, 2026-08-25):
|
||||
|
||||
1. **Растянуть вывод на несколько кадров, не показывая полуготовую строку.**
|
||||
Печатать по нескольку букв за кадр, держа цвет шрифта чёрным (отдельный
|
||||
индекс палитры), а по готовности подменить этот индекс на белый — строка
|
||||
появится целиком и мгновенно. Стоит ноль байт памяти и укладывается в
|
||||
нашу же технику «два разных чёрных» (`POP_COL_OUTSIDE`).
|
||||
2. **Собирать строку в один спрайт** в свободном хвосте страницы шрифта и
|
||||
блитить одним вызовом. Дороже по подготовке (~35 тыс. тактов на
|
||||
копирование), но выгодно там, где строка ЦЕЛИКОМ меняется каждый раз.
|
||||
Для меню этот путь уже рассматривался и был отвергнут; для статус-строк
|
||||
он имеет смысл только вместе с п.1.
|
||||
|
||||
Про QuickSave/QuickLoad оптимизация не нужна вовсе: там игра и так стоит на
|
||||
время дисковой операции.
|
||||
|
||||
## 6. Ловушка: свисающие глифы
|
||||
|
||||
Зона стирания текста обязана захватывать строку НИЖЕ базовой линии. В малом
|
||||
шрифте `'p'` имеет высоту 7 при ascent 5, `','` — 6: они свисают под базовую
|
||||
линию. Стирание ровно до неё оставляло от хвоста «p» в «Speed:» одинокую
|
||||
точку (поймано в MAME 2026-08-25).
|
||||
Reference in New Issue
Block a user