# От roomtest к полноценной игре — сценарий и оболочка Статус: **частично реализовано; аудит обновлён 2026-08-24**. Ранее пометка «завершены FG0–FG12» была неверной: для многих этапов уже есть код и host-тесты, но их критерии приёмки на Sprinter ещё не выполнены. Фактический статус каждого FG приведён в [§14](#14-этапы-реализации). Этот документ описывает превращение текущего игрового цикла `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, demo уровень 0, global timer, сценарный интерпретатор, level-flow, ending и Hall of Fame. Полный маршрут также собирается в HDD-образ. Однако это **не означает готовность оболочки**. На момент аудита остаются существенные незакрытые места: - lifecycle палитр: gameplay-переходы используют чёрный барьер без fade; cold start и полный набор dungeon/palace переходов ещё не прошли приёмку; - PV intro Princess/Jaffar уже покадровый (актёры, факелы, звёзды, часы, молния и foreground-колонна); сцены перед 2/4/6 и длинной веткой 12 анимируют факелы, звёзды и песок, а сцены 8/9 и короткая ветка 12 пока используют статические позы с исходной длительностью; - demo отображается с игровой палитрой, проходит второй разворот/зацеп и доходит до боя; после смерти Кида корректно завершает цикл; - time-expired, ending и Hall of Fame имеют маршрут и реализацию UI, но не прошли сквозную MAME-проверку вместе с ресурсами и возвратом к title; - нет полного регресса EMM/FD для каждого перехода состояния. ## 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; сохранение/загрузка не добавляет и не отнимает тики. Реализация FG4 живёт одним модулем `roomtest/pop_timer.c` в bank 9: `60:719`, 720 тиков на минуту, счёт только в живом игровом кадре. Settings хранит `TIME LIMIT: 60 MIN / UNLIMITED` в `POP.CFG`; старый семибайтный v1 payload по-прежнему читается как `60 MIN`. Читы таймера повторяют SDLPoP, но из-за занятого `+/-` используют F7 (−1 минута, не ниже одной) и F8 (+1 минута). Состояние входит в QuickSave v4. ## 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. Этапы реализации Легенда аудита: **✓** — критерий этапа закрыт; **~** — код существует, но критерий приёмки ещё не закрыт; **○** — не начат. Статус отражает состояние исходников и последней MAME-проверки на 2026-08-24, а не только наличие модуля в bank 9. | этап | статус | результат и фактическое состояние | критерий приёмки | |---|---|---|---| | **FG0** | ✓ | `POP_LEVEL_LAST=14`, HDD содержит `res2000..res2014`; `t_flow` отвергает 15 | HDD не содержит res2015; переход выше 14 невозможен | | **FG1** | ~ | автомат `pop_app` и `t_app` реализованы; сквозной ресурсный lifecycle и контроль EMM/FD ещё не измерены | старт/рестарт/выход проходят без рекурсии и утечки EMM | | **FG2** | ✓ | QuickSave/QuickLoad с `POP.SAV` и `POP.BAK`; отдельно проверен в MAME 2026-08-22 | критерии `quicksave_plan.md`, включая POP.BAK | | **FG3** | ✓ | pause menu, Settings, подтверждения и двойной буфер реализованы; меню проверялось в MAME; добавлены SDLPoP-звуки навигации и защита CBL вокруг полного redraw/файловых операций | Resume/Save/Load/Restart/Settings/Quit работают | | **FG4** | ~ | `pop_timer`, настройка unlimited, F7/F8 и состояние QuickSave реализованы; есть host-тест, но нет буквального сравнения темпа со SDLPoP на всех переходах | совпадение с SDLPoP и корректный save/load | | **FG5** | ~ | text/full-screen/fade примитивы есть; для входа в первый уровень и границ уровней выбран мгновенный чёрный барьер без fade: CBL и яркая новая палитра включаются только после подготовки обеих страниц; Level 1 проверен в MAME | тестовые экраны и переходы на Sprinter | | **FG6** | ~ | title-ресурсы и порядок кадров реализованы; Enter на title и Esc на первом story в MAME переводят прямо в `FIRST_LEVEL`, минуя demo; полная cold-boot приёмка fade остаётся в FG5 | основной титул/Presents/название/Mechner идут в точном порядке `show_title()`; Enter/Space/Esc/стрелки прерывают ожидание; story/intro продолжит FG8 | | **FG7** | ✓ | level 0, исходная таблица `demo_moves`, demo HP=4 и блокировка игрового UI реализованы; исправлены зеркалирование auto-control, боевой AI Кида и завершение после смерти; в MAME demo проходит разворот/зацеп, доходит до боя и возвращается в attract-цикл без повторного убийства | `res2000.bin`, исходная `demo_moves`, demo HP=4; бесконечный attract loop, любой ввод начинает чистую новую игру; Pause/QuickSave/читы/таймер отключены | | **FG8** | ~ | data-driven interpreter и покадровый PV intro работают; в MAME проверены актёры, факелы, звёзды 1x1, часы/песок, palette-0 lightning и foreground-колонна; Enter/Esc переводят прямо в `FIRST_LEVEL`; временный темп 12,5 FPS и TODO точного pacing записаны в `impl_diff.md` | story/PV intro проходит, любой raw-ввод пропускает его без удержания EMM-страниц | | **FG9** | ~ | `pop_flow` корректно маршрутизирует 2/4/6/8/9/12 и ветку <=5 минут (`t_flow`); 2/4/6 и длинная 12 уже обновляют часы, песок, факелы и звёзды каждые 5 кадров Sprinter; длительности всех веток сверены с SDLPoP: 2/4/6/12 — 2,6 с, 8 — 6,0 с, 9 — 7,2 с; входная клавиша gameplay/Shift+L поглощается до сцены, а новое нажатие делает skip; анимации мыши/Princess в 8/9 и разворот Princess в короткой 12 ещё статичны | таблица flow переводит в CUTSCENE ровно перед 2/4/6/8/9/12; scene 12 выбирает короткий вариант при <=5 минутах | | **FG10** | ~ | переход TIME_EXPIRED и экран существуют, но это ещё статическая PV-стадия; сквозной MAME-маршрут не принят | PV-сцена истечения с пропуском, затем возврат на title/attract; новая игра сбрасывает таймер | | **FG11** | ~ | room 5 уровня 14 переводит в ENDING (`t_flow`); объятие/мышь заменены статической стадией, полный маршрут не принят | room 5 уровня 14 переводит в ENDING; PV-финал и Hail-экран возвращают управление оболочке | | **FG12** | ~ | версионированный `POP.HOF`, ввод имени и восстановление после повреждённого файла реализованы; нужна сквозная MAME-проверка ending → HOF → title | версионированный `POP.HOF`, ввод имени raw-клавиатурой, повреждённый файл = пустая таблица, затем title/attract | ## 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.