QuickSave F6/F9 в roomtest; bank_load_file/bank_save_file/gfx_w0_page_prepare; sprinter-cc: авто n_banks
roomtest: - QuickSave/QuickLoad (F6/F9): снапшот 'POPQ' v3 в POP.SAV/POP.BAK на HDD, транзакционная запись (POP.NEW -> rename, откат при ошибке), XOR-контрольная сумма payload'а; сериализация всех игровых переменных через W0-примитивы pop_qs_*; pop_qsave_process() на границе кадра вне Char-окон - pop_qsave_restore_room(): полная перезагрузка комнаты после загрузки (карта/края/швы, сброс bake-кэша, перерисовка обеих страниц, инвалидация кэшей спрайтов и HP) - сериализаторы в pop_map/pop_loose_mob/pop_trob/pop_guard_ai (+ восстановление инвариантов: mobs_live, trob_drawn, redraw) - immortal-чит 2 уровня: уровень 2 поглощает только малый урон Kid'а libc/libbgi: - bank_load_file()/bank_save_file() — резидентное файловое I/O в банк, без правила W3 (путь читается до переключения страницы) - gfx_w0_page_prepare(page) — подготовка W0-окна (IRQ/NMI-стабы) одной функцией; atlas_load.c и roomtest переведены на новые примитивы; ручные ISR-стабы удалены sprinter-cc / сборка: - --bank N=FILE.c: автогенерация n_banks (_n_banks_auto.c), ручные const n_banks удалены из тестов - roomtest/app.mk: ресурсы через stamp-файлы (.resource-stamps/) — один запуск упаковщика на группу вместо N под -B; HDD_PACK_ARGS
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
| [`perf_cyan_phase.md`](perf_cyan_phase.md) | **ЦИАН фаза (персонажи + передний слой)**: раскладка тактов, способы ускорения (C1..C7), журнал правок — рабочий документ между сессиями. 2026-08-17 |
|
||||
| [`perf_backlog.md`](perf_backlog.md) | Отложенная оптимизация отрисовки с замерами 2026-08-10 + **как мерить** (wait-state'ы, границы кадра). Позиции 1–7 переехали в фазовые документы выше |
|
||||
| [`quicksave_plan.md`](quicksave_plan.md) | **QuickSave/QuickLoad**: разбор (это enhancement SDLPoP, в оригинале 1989 его НЕТ), инвентаризация нашего состояния, формат снимка, шаги QS1..QS6. План, код не начат. 2026-08-17 |
|
||||
| [`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. 2026-08-21 |
|
||||
| [`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 |
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
# От roomtest к полноценной игре — сценарий и оболочка
|
||||
|
||||
Статус: **согласованный план, код не начат** (2026-08-21).
|
||||
|
||||
Этот документ описывает превращение текущего игрового цикла
|
||||
`roomtest` в законченную игру: заставка, интро, демонстрационный уровень,
|
||||
сцены между уровнями, таймер, финал и Hall of Fame. План меню и постоянных
|
||||
настроек вынесен в [`menu_settings_plan.md`](menu_settings_plan.md),
|
||||
детальный план QuickSave — в [`quicksave_plan.md`](quicksave_plan.md).
|
||||
|
||||
## 1. Зафиксированный scope
|
||||
|
||||
- Целевая последовательность — оригинальная SDLPoP/DOS PoP с уровнями
|
||||
**1..14**. Уровень 14 — скрытая финальная часть после Джаффара: его номер
|
||||
игроку не показывается, победа наступает в комнате 5.
|
||||
- Уровень **15 удаляется полностью**: не пакуется на HDD, не загружается,
|
||||
отсутствует в переходах, читах и UI; специальная логика potions/copy
|
||||
protection level удаляется.
|
||||
- Уровень **0** остаётся только демонстрационным (attract mode), а не частью
|
||||
новой игры.
|
||||
- Title sequence повторяет SDLPoP. Перед ней допускается отдельный
|
||||
пропускаемый экран с информацией о Sprinter-сборке.
|
||||
- Первая версия использует текущий профиль поведения `VANILLA`. Сейчас это
|
||||
означает **существующую реализацию roomtest**, включая уже встроенные
|
||||
исправления. Аудит и разведение `VANILLA/ENHANCED` — будущая задача.
|
||||
- Программа работает **только с HDD**. Варианты без сохранения для floppy не
|
||||
проектируются.
|
||||
- Моды и выбор levelset в этот план не входят.
|
||||
|
||||
## 2. Что делает SDLPoP
|
||||
|
||||
Источники истины в локальном SDLPoP:
|
||||
|
||||
- `src/seg000.c`: `start_game()`, `show_title()`, demo mode, общий кадр,
|
||||
проверка финала;
|
||||
- `src/seg003.c`: `init_game()`, `play_level()`, `play_level_2()`;
|
||||
- `src/seg001.c`: cutscene engine, `pv_scene()`, сцены 2/4/6/8/9/12,
|
||||
`time_expired()`, `end_sequence()` и Hall of Fame;
|
||||
- `src/data.h`: `tbl_cutscenes`, параметры уровня 0, win level/room;
|
||||
- `data/TITLE`, `data/PV`, `data/LEVELS/res2000.bin`: ресурсы оболочки.
|
||||
|
||||
Штатный маршрут:
|
||||
|
||||
```text
|
||||
boot
|
||||
-> title / story screens
|
||||
-> Princess + Jaffar intro
|
||||
-> credits / Hall of Fame
|
||||
-> demo level 0
|
||||
-> title или новая игра
|
||||
-> levels 1..14
|
||||
before 2 -> princess cutscene
|
||||
before 4 -> princess cutscene
|
||||
before 6 -> princess cutscene
|
||||
before 8 -> princess + mouse
|
||||
before 9 -> princess + mouse
|
||||
before 12 -> scene selected by remaining time
|
||||
-> level 14, room 5
|
||||
-> embrace + mouse
|
||||
-> ending text/music
|
||||
-> Hall of Fame
|
||||
-> title
|
||||
```
|
||||
|
||||
Кроме уровней, здесь есть глобальный 60-минутный таймер, сцена истечения
|
||||
времени, пропуск сцен клавишей, fade/flash, ожидание музыки и возврат в
|
||||
attract loop после демо или финала.
|
||||
|
||||
## 3. Текущее состояние roomtest
|
||||
|
||||
Уже реализованы игровой кадр, комнаты, уровни, тайлсеты, Kid/Guard/Shadow,
|
||||
Джаффар, специальные события, checkpoint, переходы уровней, бесшовный
|
||||
выход 12-го уровня, перенос максимального HP и звуковые эффекты.
|
||||
|
||||
Отсутствуют:
|
||||
|
||||
- верхнеуровневая оболочка приложения;
|
||||
- title/story sequence и текстовые экраны;
|
||||
- уровень 0 и записанное управление демо;
|
||||
- общий cutscene engine и PV-ресурсы;
|
||||
- сцены между уровнями;
|
||||
- глобальный таймер и time-expired sequence;
|
||||
- корректный переход из уровня 14 в ending;
|
||||
- финальная сцена и Hall of Fame;
|
||||
- возврат к title без перезапуска программы.
|
||||
|
||||
## 4. Архитектура: автомат состояний приложения
|
||||
|
||||
Нельзя наращивать все режимы условиями внутри кадрового цикла. Текущий
|
||||
цикл должен стать реализацией одного состояния `PLAYING`:
|
||||
|
||||
```text
|
||||
BOOT -> BUILD_INFO -> TITLE -> INTRO -> DEMO
|
||||
| |
|
||||
+---- NEW_GAME <-+
|
||||
|
||||
NEW_GAME -> LEVEL_LOAD -> PLAYING <-> PAUSE_MENU
|
||||
|
|
||||
+-> CUTSCENE -> LEVEL_LOAD
|
||||
+-> TIME_EXPIRED -> TITLE
|
||||
+-> ENDING -> HALL_OF_FAME -> TITLE
|
||||
```
|
||||
|
||||
Минимальный контекст оболочки:
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
POP_APP_BOOT,
|
||||
POP_APP_BUILD_INFO,
|
||||
POP_APP_TITLE,
|
||||
POP_APP_INTRO,
|
||||
POP_APP_DEMO,
|
||||
POP_APP_LEVEL_LOAD,
|
||||
POP_APP_PLAYING,
|
||||
POP_APP_PAUSE_MENU,
|
||||
POP_APP_CUTSCENE,
|
||||
POP_APP_TIME_EXPIRED,
|
||||
POP_APP_ENDING,
|
||||
POP_APP_HALL_OF_FAME,
|
||||
POP_APP_QUIT
|
||||
} pop_app_state_t;
|
||||
```
|
||||
|
||||
Переходы задаются результатом состояния, а не прямыми рекурсивными
|
||||
вызовами наподобие SDLPoP `start_game()`/`longjmp()`. На Z80 это проще для
|
||||
стека и позволяет освобождать ресурсы каждого режима в одном месте.
|
||||
|
||||
## 5. Ресурсная модель
|
||||
|
||||
Title и cutscene-ресурсы нельзя постоянно держать рядом с игровыми
|
||||
атласами. Для каждого состояния нужен явный lifecycle:
|
||||
|
||||
```text
|
||||
enter: pause sound -> unload incompatible set -> load set -> apply palette
|
||||
run: process input/timer/animation
|
||||
leave: stop sound -> release EMM pages -> clear transient state
|
||||
```
|
||||
|
||||
Новые группы HDD:
|
||||
|
||||
```text
|
||||
TITLE\ title/story images, palette, optional build-screen assets
|
||||
PV\ princess room, Princess/Jaffar/mouse frames, palettes
|
||||
MUSIC\ intro, cutscene and ending tracks/samples
|
||||
LEVELS\ res2000..res2014.bin
|
||||
```
|
||||
|
||||
Конкретный формат атласов выбирает упаковщик. Runtime не должен разбирать
|
||||
PNG/DAT: как и игровые спрайты, он получает подготовленные `.atl`/`.bin`.
|
||||
|
||||
## 6. Экран Sprinter build
|
||||
|
||||
Отдельное состояние перед оригинальной заставкой:
|
||||
|
||||
```text
|
||||
PRINCE OF PERSIA
|
||||
SPRINTER SP2000 BUILD
|
||||
version / date / build id
|
||||
```
|
||||
|
||||
Требования:
|
||||
|
||||
- пропускается любой клавишей;
|
||||
- выключается в Settings;
|
||||
- не запускает музыку оригинального title и не меняет её тайминги;
|
||||
- данные версии генерируются сборкой, а не правятся вручную в C;
|
||||
- отсутствие экрана приводит прямо к `TITLE`.
|
||||
|
||||
## 7. Title и текстовая подсистема
|
||||
|
||||
Порядок переносится из `show_title()`:
|
||||
|
||||
1. основной титульный экран;
|
||||
2. Presents;
|
||||
3. название игры и Jordan Mechner;
|
||||
4. story frame / “In the absence…”;
|
||||
5. intro Princess + Jaffar;
|
||||
6. story “Marry Jaffar…”;
|
||||
7. credits;
|
||||
8. Hall of Fame, если таблица непуста;
|
||||
9. demo level 0.
|
||||
|
||||
Нужны общие примитивы: загрузить full-screen image, вывести строку,
|
||||
показать экран заданное время, transition left-to-right, fade in/out,
|
||||
прервать ожидание клавишей. Текст и меню должны использовать один renderer.
|
||||
|
||||
Критерий: последовательность и музыкальные точки совпадают с SDLPoP;
|
||||
Sprinter build screen не сдвигает оригинальный soundtrack.
|
||||
|
||||
## 8. Demo level 0
|
||||
|
||||
- Добавить на HDD `res2000.bin`.
|
||||
- Загружать уровень обычным loader, но выставлять demo HP и demo mode.
|
||||
- Воспроизводить `demo_moves` как синтетический источник `control_*`.
|
||||
- Пользовательский ввод прерывает демо и начинает новую игру.
|
||||
- Достижение demo end room (у SDLPoP — 24), смерть или конец скрипта
|
||||
возвращают в `TITLE`.
|
||||
- Pause menu, QuickSave и cheats в demo недоступны.
|
||||
- RNG демо и начальное состояние должны быть детерминированы.
|
||||
|
||||
Критерий: без ввода attract loop не требует перезапуска процесса;
|
||||
title -> demo -> title повторяется неограниченно.
|
||||
|
||||
## 9. Глобальный таймер
|
||||
|
||||
Состояние: минуты, тики и флаг показа. Таймер создаётся при New Game,
|
||||
переносится между уровнями и входит в QuickSave.
|
||||
|
||||
Правила `VANILLA`:
|
||||
|
||||
- на pause menu, загрузке HDD, QuickSave/QuickLoad время не идёт;
|
||||
- игровые тики следуют темпу логического кадра, а не частоте render loop;
|
||||
- поведение во время level-end sound и cutscenes сверяется буквально с
|
||||
SDLPoP;
|
||||
- после Джаффара/на финальном уровне время не должно вызвать поражение;
|
||||
- ноль времени переводит приложение в `TIME_EXPIRED`.
|
||||
|
||||
Критерий: одинаковый игровой отрезок в NORMAL даёт то же уменьшение времени,
|
||||
что SDLPoP; сохранение/загрузка не добавляет и не отнимает тики.
|
||||
|
||||
## 10. Cutscene engine
|
||||
|
||||
Сцены SDLPoP состоят из небольшого набора повторяемых команд. Вместо набора
|
||||
крупных C-функций нужен компактный интерпретатор:
|
||||
|
||||
```text
|
||||
SET_ACTOR actor
|
||||
SET_POS x,y,dir
|
||||
START_SEQ seq
|
||||
WAIT_FRAMES n
|
||||
PLAY_SOUND id
|
||||
WAIT_SOUND
|
||||
SET_HOURGLASS frame
|
||||
SET_SAND state
|
||||
FLASH color,frames
|
||||
FADE_IN / FADE_OUT
|
||||
CLEAR_ACTOR actor
|
||||
END
|
||||
```
|
||||
|
||||
Скрипты — `const` в холодном банке или подготовленный бинарный ресурс.
|
||||
Interpreter обязан:
|
||||
|
||||
- исполнять один шаг/кадр без блокирующих длинных циклов;
|
||||
- поддерживать пропуск сцены;
|
||||
- при пропуске выполнять cleanup и выходить в заранее заданное состояние;
|
||||
- освобождать PV-ресурсы перед загрузкой игрового тайлсета;
|
||||
- не разрешать pause menu/QuickSave внутри сцены.
|
||||
|
||||
Порядок переноса: intro, 2/6, 4, 8, 9, 12, time expired, ending. Сцена 12
|
||||
выбирает короткий или обычный вариант по остатку времени.
|
||||
|
||||
## 11. Переходы между уровнями
|
||||
|
||||
Таблица сценария должна быть отдельна от таблиц механики уровня:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint8_t level;
|
||||
uint8_t pre_cutscene;
|
||||
uint8_t show_level_number;
|
||||
uint8_t ending_rule;
|
||||
} pop_level_flow_t;
|
||||
```
|
||||
|
||||
Особые правила:
|
||||
|
||||
- New Game начинает уровень 1;
|
||||
- перед 2/4/6/8/9/12 запускается сцена;
|
||||
- 12 -> 13 остаётся бесшовным;
|
||||
- после победы над Джаффаром переход идёт в 14;
|
||||
- номер 14 не показывается;
|
||||
- вход в комнату 5 уровня 14 переводит в `ENDING`;
|
||||
- значения больше 14 недопустимы и дают диагностическую ошибку, а не
|
||||
попытку открыть файл.
|
||||
|
||||
## 12. Ending и Hall of Fame
|
||||
|
||||
Ending:
|
||||
|
||||
1. загрузить PV-набор;
|
||||
2. встреча Kid и Princess;
|
||||
3. объятие;
|
||||
4. появление мыши;
|
||||
5. ending music;
|
||||
6. финальные story/title экраны;
|
||||
7. переход в Hall of Fame.
|
||||
|
||||
Hall of Fame хранится на HDD в отдельном версионированном `POP.HOF`.
|
||||
Сохраняются имя и результат; ввод имени использует тот же текстовый/UI слой.
|
||||
Повреждённый или неизвестный формат означает пустую таблицу, но не мешает
|
||||
запуску игры. После показа — возврат в `TITLE`.
|
||||
|
||||
## 13. Удаление уровня 15
|
||||
|
||||
Отдельный ранний этап, чтобы новый flow не наследовал лишний маршрут:
|
||||
|
||||
- убрать `res2015.bin` из `LVL_NUMS` и HDD image;
|
||||
- заменить последний игровой уровень на 14;
|
||||
- остановить Shift+L и прочую навигацию на 14;
|
||||
- удалить `POP_POTIONS_LEVEL` и специальный половинный урон синих зелий;
|
||||
- исключить copy protection из конфигурации и меню;
|
||||
- добавить тест: после уровня 14 приложение входит в ending и никогда не
|
||||
запрашивает `res2015.bin`.
|
||||
|
||||
## 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 |
|
||||
|
||||
QuickSave допускается реализовать до остальных частей оболочки: FG2 — одна
|
||||
из ближайших самостоятельных задач.
|
||||
|
||||
## 15. Проверки
|
||||
|
||||
- Host-тест автомата: все допустимые переходы и отсутствие уровня 15.
|
||||
- Host-тест cutscene interpreter на синтетическом скрипте и skip в каждой
|
||||
ожидающей команде.
|
||||
- Host-тест demo input: одинаковый seed даёт одинаковый поток управления.
|
||||
- MAME: cold boot -> build info -> title -> demo -> title.
|
||||
- MAME: новая игра -> принудительный переход по всем pre-level scenes.
|
||||
- MAME: time expired и пропуск сцены.
|
||||
- MAME: 13 -> 14 -> room 5 -> ending -> HOF -> title.
|
||||
- Проверка EMM/FD до и после каждого состояния: число страниц и открытых
|
||||
файлов возвращается к базовому.
|
||||
- `make size-check`; крупный cold-код размещать в банках и отдельно следить
|
||||
за лимитом 16 КБ каждого банка.
|
||||
|
||||
## 16. Не входит в план
|
||||
|
||||
- уровень 15 и copy protection;
|
||||
- моды и выбор levelset;
|
||||
- replay/recording;
|
||||
- точная эмуляция SDL video/controller options;
|
||||
- профиль ENHANCED и индивидуальные switches fixes.
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
# Pause menu и Settings для Sprinter PoP
|
||||
|
||||
Статус: **согласованный план, код не начат** (2026-08-21).
|
||||
|
||||
Связанные документы:
|
||||
|
||||
- [`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 требуют подтверждения;
|
||||
- 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 и ввод
|
||||
|
||||
Меню использует общую с title текстовую подсистему:
|
||||
|
||||
- фиксированный bitmap font без `_gfx_font_buf` на 2 КБ в W2;
|
||||
- фон/рамка и выделенная строка;
|
||||
- вертикальная навигация, left/right для значения, Enter, Esc;
|
||||
- edge-triggered клавиши поверх существующего `kbd_raw`;
|
||||
- двойная буферизация либо один заранее сохранённый фон menu;
|
||||
- строки и холодный код — в отдельном банке, постоянное состояние — в W2.
|
||||
|
||||
Первый UI может быть визуально простым. Критично отсутствие потери клавиш,
|
||||
предсказуемая пауза и отсутствие повреждения игрового back buffer.
|
||||
|
||||
## 11. Диалоги
|
||||
|
||||
Общий диалог подтверждения:
|
||||
|
||||
```text
|
||||
RESTART LEVEL?
|
||||
RESTART GAME?
|
||||
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: menu navigation, disabled items, confirmations, команды приложению.
|
||||
- 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.
|
||||
|
||||
@@ -180,12 +180,14 @@ SDLPoP, сохраняются.
|
||||
|
||||
---
|
||||
|
||||
## 4. Куда писать снимок: файл, а не EMM-страница
|
||||
## 4. Куда писать снимок: HDD-файл, а не EMM-страница
|
||||
|
||||
Рекомендация: **основной путь — файл `QUICKSAVE.SAV`; EMM-страница —
|
||||
необязательный второй слот.**
|
||||
Решение: **один основной слот `POP.SAV` на HDD; предыдущая
|
||||
валидная запись хранится в `POP.BAK`.** EMM-слота нет: программа
|
||||
работает только с HDD, а главный сценарий QuickSave обязан переживать
|
||||
перезапуск игры.
|
||||
|
||||
> Пересмотрено 2026-08-17 по вопросу пользователя «почему EMM, а не файл».
|
||||
> Пересмотрено 2026-08-21 по вопросу пользователя «почему EMM, а не файл».
|
||||
> Первая редакция плана рекомендовала EMM — это была ошибка: она взвешивала
|
||||
> скорость и недооценивала главный сценарий использования. Разбор оставлен
|
||||
> целиком, потому что довод переносится и на другие «положить в память
|
||||
@@ -214,10 +216,8 @@ MAME, обязательный после каждой пересборки об
|
||||
`_fd_guard` в libc и так стоит), а «не нужен путь и права» — экономия одной
|
||||
строки.
|
||||
|
||||
Что остаётся за EMM: мгновенный слот для «переиграть» без обращения к диску.
|
||||
Делается тем же сериализатором и добавляется, если понадобится. Поэтому
|
||||
обход состояния писать сразу так, чтобы «куда» было параметром — как у
|
||||
SDLPoP через `process_func`.
|
||||
Обход состояния всё равно писать с абстракцией чтения/записи, как у SDLPoP
|
||||
через `process_func`, но второй EMM-слот в scope не входит.
|
||||
|
||||
**Проверить ДО кодинга:** пишется ли `test_hdd.chd` из-под MAME. Если образ
|
||||
только на чтение, файловый путь упрётся в это на первом же шаге и порядок
|
||||
@@ -234,6 +234,7 @@ roomtest.
|
||||
+5 pop_current_level 1 Б
|
||||
+6 длина полезной части 2 Б (контроль, что обход совпал)
|
||||
+8 ... поля встык, ОДИН порядок на запись и на чтение ...
|
||||
.. checksum 2 Б (заголовок + payload)
|
||||
```
|
||||
|
||||
Версия проверяется первой; несовпадение — отказ, как в SDLPoP. Никаких
|
||||
@@ -282,13 +283,12 @@ static void qs_walk(qs_io_t io) /* io = запись или чтение */
|
||||
|
||||
| шаг | что | критерий готовности |
|
||||
|---|---|---|
|
||||
| **QS1** | Аксессоры/сериализаторы для `static`-состояния банковых модулей: `pop_trob.c` (`room_modif`, `room_seen`, `trobs`, `trob_seed`), `pop_room.c` (`mobs_live`), страница уровня (чтение `fg`) | хост-тест `tests-host/t_qsave.c`: обход туда-обратно на синтетическом состоянии даёт байт-в-байт исходное |
|
||||
| **QS0** | Проверить, что `D:` пишется из-под MAME (пробный файл из roomtest) | файл создался и читается обратно после рестарта программы |
|
||||
| **QS2** | Ядро: `qs_walk` + запись/чтение файла `QUICKSAVE.SAV`, магия и версия, отказ при несовпадении | сохранение и загрузка **в той же комнате, без движения** — картинка и состояние не изменились |
|
||||
| **QS1** | Аксессоры/сериализаторы для `static`-состояния банковых модулей: `pop_trob.c` (`room_modif`, `room_seen`, `trobs`, `trob_seed`), `pop_room.c` (`mobs_live`), страница уровня (чтение `fg`) | хост-тест `tests-host/t_qsave.c`: обход туда-обратно на синтетическом состоянии даёт байт-в-байт исходное |
|
||||
| **QS2** | Ядро: `qs_walk` + `POP.SAV`, магия/версия/checksum, безопасная замена с предыдущей валидной копией в `POP.BAK` | сохранение и загрузка **в той же комнате, без движения**; порча SAV не портит BAK |
|
||||
| **QS3** | Восстановление отрисовки (§6), включая обе страницы дабл-буфера | загрузка после перехода в другую комнату; нет мерцания через кадр |
|
||||
| **QS4** | Клавиши **F6/F9** (или свободные из `pop_cheat.h`) через `<kbd_raw.h>`, флаги `need_quick_save/load`, обработка **между кадрами** | загрузка посреди боя/падения не ломает `play_seq` |
|
||||
| **QS5** | Загрузка с **другого уровня** (перезагрузка уровня и атласов) | сохранить на ур. 2, уйти на ур. 12, загрузить — тайлсет и стражи верные |
|
||||
| **QS6** | Опционально: второй слот в EMM-странице тем же сериализатором | мгновенное «переиграть» без обращения к диску |
|
||||
|
||||
Порядок не переставлять: QS0 первым (он может изменить весь план), QS3 без
|
||||
QS2 нечего проверять, а QS5 обязан идти после QS3 — иначе смена тайлсета
|
||||
@@ -320,5 +320,7 @@ QS2 нечего проверять, а QS5 обязан идти после QS3
|
||||
5. **Дабл-буфер.** Самый вероятный источник «почти работает»: забыть вторую
|
||||
страницу. Симптом — мерцание через кадр
|
||||
(см. `roomtest/CLAUDE.md`, раздел про дабл-буфер).
|
||||
6. **Открытый вопрос:** нужен ли снимок в файле вообще, или EMM-страницы
|
||||
достаточно. Решать после QS3, по факту использования.
|
||||
6. **Транзакция SAV/BAK.** До кодинга проверить на DSS семантику
|
||||
rename/replace. Если атомарная замена не гарантирована, писать через
|
||||
`POP.NEW`, проверять его после close и не удалять единственную валидную копию
|
||||
до завершения новой.
|
||||
|
||||
Reference in New Issue
Block a user