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