f4b4852d51
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
350 lines
17 KiB
Markdown
350 lines
17 KiB
Markdown
# От 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.
|
||
|