Files
Sprinter-SDCC/applications/PoP/docs/full_game_plan.md
T
snark13 f4b4852d51 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
2026-08-22 11:53:10 +03:00

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