Files
Sprinter-SDCC/applications/PoP/docs/menu_settings_plan.md
T
snark13 7fd7f28ffc Доки: план меню (рендер, restart без подтверждения), QSAVE закрыт
menu_settings_plan.md:
- §10 переписан: выбран Вариант A — текстовые строки + собственный
  растровый рендерер в новом банке; референс SDLPoP (hc_small_font /
  hc_font — один рендерер, два шрифта); шрифт как ассет из паковщика,
  прототип MS4 — системный CP866 ZG; двуязычность eng/rus через пару
  (таблица строк CP866, файл шрифта)
- Restart Level / Restart Game выполняются сразу, без подтверждения
  (§3, §8, из §11 убраны диалоги RESTART *?)
- §13 MS4: текстовый рендерер + два шрифта; §14: host-тест рендерера

quicksave_plan.md: статус «РЕАЛИЗОВАНО и проверено в MAME»
(v0.6-pop-quicksave), документ оставлен справочником по формату 'POPQ'

TASKS_OPEN/TASKS_CLOSED: запись QSAVE переехала в закрытые с полным
протоколом; docs/README.md аннотации обновлены
2026-08-22 12:45:01 +03:00

427 lines
24 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.
# Pause menu и Settings для Sprinter PoP
Статус: **согласованный план, код не начат** (2026-08-21; обновлён
2026-08-22 — решение о рендеринге UI см. §10, Restart Level/Game без
подтверждения).
Связанные документы:
- [`full_game_plan.md`](full_game_plan.md) — автомат состояний, title,
demo, cutscenes и ending;
- [`quicksave_plan.md`](quicksave_plan.md) — состав и восстановление снимка.
## 1. Решения
- Программа работает только с HDD; настройки, QuickSave и Hall of Fame
всегда могут быть постоянными файлами.
- Основной pause menu обязательно содержит QuickSave и QuickLoad.
- QuickSave имеет один слот `POP.SAV`; предыдущая корректная запись хранится
как `POP.BAK`.
- Первая версия имеет один профиль `VANILLA`. Под этим именем пока понимается
**текущее поведение roomtest**, включая уже встроенные исправления.
- Дизайн файла и API предусматривает будущий `ENHANCED`, но аудит и
переключение fixes сейчас не выполняются.
- Уровень 15/copy protection отсутствует.
- Моды, levelsets и меню Mods отложены.
## 2. Что есть в SDLPoP
`src/menu.c` содержит:
- Resume, QuickSave, QuickLoad, Restart Level, Settings, Restart Game, Quit;
- General, Gameplay, Visuals, Mods, Controls;
- toggle/number/key controls, пояснения, scroll и confirmation dialogs;
- большой список fixes/enhancements и custom level options.
На Sprinter не переносятся SDL-специфичные параметры: fullscreen, hardware
acceleration, scaling, aspect ratio, rumble. UI берёт структуру SDLPoP, но
набор настроек соответствует платформе.
## 3. Pause menu первой версии
```text
RESUME
QUICKSAVE
QUICKLOAD
RESTART LEVEL
SETTINGS
RESTART GAME
QUIT
```
Поведение:
- `Esc` в `PLAYING` открывает меню; повторный Esc или Resume возвращает игру;
- игра, логический таймер и звуковой насос ставятся на паузу согласованно;
- QuickSave/QuickLoad только взводят запрос, фактическая операция идёт на
безопасной границе кадра;
- QuickLoad disabled/показывает `NO QUICKLOAD`, если нет валидных SAV/BAK;
- Restart Level и Restart Game выполняются сразу, БЕЗ подтверждения
(2026-08-22): обе операции дёшево обратимы — Restart Level перечитывает
уровень, после Restart Game можно тут же сделать QuickLoad из POP.SAV;
- Quit требует подтверждения и закрывает файлы/каналы штатным путём;
- меню недоступно в demo, cutscene, time-expired и ending;
- отдельная debug-комбинация немедленного выхода может остаться только в
отладочной сборке.
## 4. Settings первой версии
```text
GENERAL
Sound ON / OFF
Show Sprinter screen ON / OFF
Restore defaults...
GAMEPLAY
Speed NORMAL / FAST / FASTEST
Gameplay profile VANILLA
Cheats ON / OFF
CONTROLS
Show key bindings
BACK
```
`Gameplay profile: VANILLA` показывается read-only: место в модели уже есть,
но пользователь не может выбрать ещё не реализованный ENHANCED.
Отладочные параметры `ROOMNAV`, border profiling, stop-frame и переключение
double buffering не являются пользовательскими Settings. Они остаются
compile-time/debug функциями и скрываются из release UI.
## 5. Модель настроек
Игровой код не должен читать UI-структуры. Единственный runtime-контракт:
```c
typedef enum {
POP_PROFILE_VANILLA = 0,
POP_PROFILE_ENHANCED = 1
} pop_gameplay_profile_t;
typedef struct {
uint8_t sound_enabled;
uint8_t speed_mode;
uint8_t gameplay_profile;
uint8_t cheats_enabled;
uint8_t show_build_info;
uint16_t enhancement_flags;
} pop_settings_t;
```
В первой версии загрузчик принимает только `POP_PROFILE_VANILLA`. Значение
ENHANCED из более нового/ручного файла заменяется на VANILLA с диагностикой,
а не включает частично реализованный режим.
Будущий профиль задаёт маску возможностей централизованно:
```text
VANILLA -> текущий согласованный набор
ENHANCED -> будущий рекомендуемый набор fixes
CUSTOM -> только если позже действительно понадобится
```
До отдельного аудита существующие `fix_exit_door`, feather guard behavior,
jump grab и sound priorities не переключаются и считаются частью текущего
VANILLA.
## 6. Файл POP.CFG
Бинарный, компактный, версионированный формат:
```text
+0 "PCFG" magic, 4 Б
+4 format_version 1 Б
+5 payload_size 2 Б
+7 payload фиксированные поля little-endian
.. checksum 2 Б
```
Требования:
- путь рядом с exe/в выделенном каталоге игры на HDD;
- неизвестная версия, неверная длина или checksum -> defaults;
- неизвестные будущие хвостовые поля можно пропустить по `payload_size`;
- запись только после Apply/выхода из Settings, не на каждый шаг курсора;
- ошибка записи не завершает игру: показать сообщение и оставить runtime
значения;
- Restore defaults меняет RAM только после подтверждения и затем сохраняет.
CFG не содержит состояние уровня, QuickSave или Hall of Fame.
## 7. QuickSave / QuickLoad в меню
Детальный состав снимка и порядок восстановления — в
[`quicksave_plan.md`](quicksave_plan.md). Здесь фиксируется UI и файловая
транзакция.
### Один слот и backup
Файлы:
```text
POP.SAV текущий слот
POP.BAK предыдущий валидный слот
POP.NEW временный файл во время записи
```
Безопасная запись:
1. записать полный снимок в `POP.NEW`;
2. закрыть файл;
3. повторно открыть/прочитать заголовок и checksum;
4. старый валидный `POP.SAV` перенести/скопировать в `POP.BAK`;
5. `POP.NEW` сделать новым `POP.SAV`;
6. при любой ошибке сохранить прежний `POP.SAV`.
Точную последовательность rename/copy выбрать после характеризации DSS.
Если атомарный rename не гарантирован, использовать copy + fsync/close и
никогда не удалять единственную валидную копию до проверки новой.
### Загрузка
1. проверить `POP.SAV`;
2. если он отсутствует/повреждён/несовместим — проверить `POP.BAK`;
3. при валидном BAK показать `LOAD BACKUP?`;
4. несовместимая версия — `INCOMPATIBLE SAVE`, без частичной загрузки;
5. после успеха закрыть menu, перерисовать обе страницы, перезапустить звук.
### Сообщения
Минимальный набор:
```text
QUICKSAVED
QUICKLOADED
NO QUICKLOAD
SAVE ERROR
INCOMPATIBLE SAVE
LOAD BACKUP?
```
Сообщение показывается UI-слоем, но операция завершается до возврата в
игровой кадр.
## 8. Restart Level / Restart Game
Restart Level:
- использует существующий штатный reset текущего уровня;
- не перечитывает CFG;
- не меняет `POP.SAV`;
- сбрасывает состояние, которое сбрасывает текущая реализация roomtest.
Restart Game:
- выполняется сразу, без подтверждения;
- завершить текущий gameplay session;
- освободить level/atlas/temporary EMM;
- создать новую игру с уровня 1 и новым глобальным таймером;
- настройки оставить;
- QuickSave не удалять.
## 9. Controls
Первая версия только показывает активную раскладку. Переназначение клавиш
откладывается: raw PS/2 канал имеет особенности Shift и расширенных кодов,
поэтому generic key-binding UI требует отдельного проекта.
Экран должен перечислить минимум:
- движение и Shift/action;
- Esc/menu;
- F6/F9 QuickSave/QuickLoad;
- Ctrl+S sound;
- P speed;
- доступные cheats, только если они включены.
## 10. UI renderer и ввод
### 10.1. Выбор способа отрисовки: текст против спрайт-атласов
Ограничение платформы: стандартный текстовый вывод libbgi (`outtextxy`)
не годится — он тянет системный знакогенератор в `_gfx_font_buf` (2 КБ
статики в W2) плюс жирный резидентный код, а W1/W2 забиты игрой
(тот же вывод зафиксирован комментарием в `roomtest_cold.c`, где отладочный
борд рисуется палочками именно поэтому). Значит, любой вариант требует
СВОЕЙ реализации вывода меню, живущей в отдельном банке (память на банк
есть; скорость не критична — меню работает на паузе).
Рассматривались два подхода.
**Вариант A — текстовые строки + собственный растровый рендерер.**
Плюсы:
- минимальные данные: шрифт 2–4 КБ + таблицы строк по сотни байт на язык;
- весь динамический текст бесплатно: значения опций (ON/OFF,
NORMAL/FAST/FASTEST), сообщения (`QUICKSAVED`, `INCOMPATIBLE SAVE`),
диалоги (`LOAD BACKUP?`), экран Controls, будущий ввод инициалов
Hall of Fame — без текстового движка HoF вообще не сделать;
- правка формулировки = правка C-строки, мгновенные итерации;
- локализация = вторая таблица строк (+ вторая половина глифов);
- **решающий аргумент: так сделано в самом SDLPoP** — см. §10.2.
Минусы:
- надо написать рендерер (блиттер глифа + строка + центрирование +
подсветка) — небольшой, но свой;
- вид определяется качеством шрифта-ассета.
**Вариант B — готовые спрайт-атласы** (атлас главного меню с активными/
неактивными пунктами, атлас вложенного меню, атлас каждой опции
On/Off и т.д.).
Плюсы:
- аутентичный вид: любая типографика/декор запекаются при упаковке;
- вывод = существующий блит атласов, текстовый движок не нужен;
- язык = другой файл атласа с диска, ноль логики.
Минусы:
- комбинаторика ассетов: 7 пунктов × состояния + вложенные меню + значения
всех опций + все сообщения + все диалоги ≈ десятки КБ raw на язык до RLE;
второй язык удваивает;
- любая правка текста = перегенерация ассетов + перекладка ресурсов;
- динамический текст (HoF initials) всё равно потребует шрифтового движка —
получили бы ОБЕ системы сразу.
**Решение (2026-08-22): Вариант A**, шрифт — ассет. Спрайты остаются только
для нетекстового декора (рамка/фон меню, маркер выделения — как arrowheads
в SDLPoP). Титульный экран — полноэкранная картинка, тема `full_game_plan.md`.
### 10.2. Референс: как устроено меню в SDLPoP
`SDLPoP/src/menu.c` + текстовый движок `seg009` — источник структуры:
- **Текстовые строки + встроенный пропорциональный bitmap-шрифт**
`hc_small_font_data[]` (menu.c:2488): символы 32..126, каждый глиф —
монохромное изображение переменной ширины; `font_type`
{first_char, last_char, space_between_chars, height_above_baseline, chtab}.
Никаких per-item атласов, хотя SDL_ttf доступен.
- Вывод — портированный движок оригинального DOS PoP (seg009):
`draw_text_character``method_3_blit_mono(image, x, y, textblit,
textcolor)`; `get_line_width` для центрирования; перенос по словам.
Тем же движком рисуются in-game тексты и copy protection.
- Пункты меню — data-driven C-структуры `{id, previous, next, required,
char text[32]}` + таблицы `pause_menu_items[]` / `settings_menu_items[]`;
`required` — указатель на флаг disabled, такие пункты пропускаются при
навигации (prev/next пересчитываются).
- Выделенный пункт = смена цвета текста (bright-white против обычного) +
рамка-контур `draw_rect_contours(selection_box, lightgray)`; НЕ отдельный
спрайт «активного пункта».
- Фон меню — затемнение замороженного игрового кадра:
`draw_rect_with_alpha(black, alpha=120)`, внизу просвечивает «GAME PAUSED».
- Settings — декларативная таблица `setting_type` со стилями TOGGLE / NUMBER /
TEXT_ONLY / KEY, геттером/сеттером/increase/decrease значения, строкой-
explanation внизу экрана, скроллом длинных списков и фокусом «левая половина
(список) / правая половина (значения)».
- Диалоги — один общий `draw_confirmation_dialog(text)` + обработчик
результата; диалог возвращает решение автомату меню.
- Мини-спрайты только для декора значений (arrowheads up/down/left/right).
- Навигация озвучена (menu tick), ввод клавиатура+мышь, hover по прямоугольникам.
### 10.3. Наша реализация
- Новый банк (свободный номер, автонумерация sprinter-cc): код рендерера,
шрифт, таблицы строк, автомат меню. Резидентно — только 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.
- Двуязычность (eng/rus): строки храним в CP866 — латиница и кириллица одним
байтовым порядком, одна кодировка на оба алфавита. Локаль = пара
(указатель на таблицу строк, файл шрифта); переключатель — одна настройка.
Русские строки длиннее английских ~10–15% — раскладку экранов и ширину
колонок закладывать по русской. Второй язык можно добавить позже без
переделки: сначала eng.
- Подсветка выделенного пункта: инверсия прямоугольника или контур + цвет,
как в SDLPoP. Цвета текста брать из стабильного диапазона палитры
0x3A..0x3F (его никто не перезаписывает, проверено на борде-индикаторе).
- Фон открытого меню: затемнение замороженного кадра (запечь тёмный прямоуг.
в теневую копию страницы поверх сохранённого фона) — двойную перерисовку
игры под меню делать не нужно, игра стоит.
- Навигация: вверх/вниз по пунктам, left/right для значения, Enter/Esc;
edge-triggered поверх существующего `kbd_raw`. Звук навигации — menu tick
из имеющихся сэмплов.
Первый UI может быть визуально простым. Критично отсутствие потери клавиш,
предсказуемая пауза и отсутствие повреждения игрового back buffer.
## 11. Диалоги
Общий диалог подтверждения:
```text
QUIT GAME?
RESTORE DEFAULTS?
LOAD BACKUP?
YES / NO
```
Диалог не выполняет действие напрямую: он возвращает решение автомату меню,
который формирует команду приложению. Так UI не зависит от gameplay-модулей.
## 12. Будущий ENHANCED
Не реализуется сейчас, но дизайн обязан позволять:
- добавить второй профиль без смены всего UI;
- хранить `enhancement_flags` в CFG;
- отличать технические исправления порта (всегда включены) от изменений
оригинальной механики;
- провести аудит уже встроенных исправлений;
- покрыть каждый переключаемый fix host/MAME тестом;
- при необходимости добавить Advanced screen, не раздувая основной menu.
До этого момента нельзя рассыпать проверки `if (enhanced)` по горячему коду.
Сначала составляется реестр и выбирается минимальная битовая модель.
## 13. Этапы реализации
| этап | результат | критерий приёмки |
|---|---|---|
| **MS0** | определить команды app/menu и структуру settings | UI не вызывает gameplay internals напрямую |
| **MS1** | проверить запись/rename/copy на HDD DSS | crash/power-loss сценарий не теряет обе копии save |
| **MS2** | `POP.CFG`: defaults, load, validate, save | повреждённый 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 применяется до первого экрана |
QuickSave (`MS1/MS3`) можно реализовать раньше визуального menu: сначала
F6/F9 и сообщения, затем подключить те же команды к пунктам UI.
## 14. Тесты
- Host: CFG round-trip, defaults, bad magic/version/size/checksum.
- Host: меню navigation, disabled items, confirmations, команды приложению.
- Host: рендерер строк — вывод глифов обеих локалей, центрирование,
ширина строки для малого и крупного шрифта.
- Host: SAV invalid -> BAK valid; оба invalid -> NO QUICKLOAD.
- MAME: F6, изменение сцены, F9; затем рестарт программы и повторный F9.
- MAME: прервать запись/испортить SAV — BAK остаётся загружаемым.
- MAME: pause на бое/падении, Resume не меняет состояние и таймер.
- MAME: Settings сохраняются после полного выхода и запуска с HDD.
- Проверка лимита 8 DSS handles на каждом error path.
- `make size-check`; menu/text строки не должны съесть резидентный бюджет.
## 15. Не входит в план
- Mods и выбор levelset;
- уровень 15/copy protection;
- несколько save slots;
- replay/recording;
- key rebinding;
- SDL visual/controller options;
- фактическая реализация ENHANCED и individual fix switches.