Порт PoP переехал в applications/SprPoP — приложение, которое собирается само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной папки. Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT, по умолчанию ../..). applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся архивом закрытых задач, багов и исполненных планов. Скопировано из applications/PoP/roomtest@4b74478. Перенос проверен побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита, все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host- тесты зелёные (15/15). Раскладка: src/ рукописный C (roomtest.c -> sprpop.c) gen/ генерируемые заголовки, в репозитории assets/orig/ оригинальные данные игры, вне репозитория (копирайт) assets/packed/ то, что ложится на диск, в раскладке диска tools/ конверторы; все пути — в одном tools/paths.py build/ выход: exe, каталоги ресурсов, hdd/, промежуточные atl/ Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что пересчитывается каждым make. Автоматика построена на ОТСУТСТВИИ файла, а не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по времени превращалось бы в лотерею. Недостающий ресурс или заголовок чинится сам, рекурсивным вызовом в ветку генерации. Музыка собирается из любого из четырёх наборов записей (make music-mp3, music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама делает музыку устаревшей. Длины реплик больше не захардкожены: упаковщик печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них — иначе mt32 (реплики на 6% длиннее) молча ломал катсцену. Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR), HDD_IMG стал ?=; команда сборки roomtest не изменилась. Корневой make host-tests переключён на SprPoP. Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики приходила раньше молнии. Это обход, а не лечение; разбор с замерами — docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
23 KiB
От SprPoP к полноценной игре — сценарий и оболочка
Статус: частично реализовано; аудит обновлён 2026-08-24. Ранее пометка «завершены FG0–FG12» была неверной: для многих этапов уже есть код и host-тесты, но их критерии приёмки на Sprinter ещё не выполнены. Фактический статус каждого FG приведён в §14.
Этот документ описывает превращение текущего игрового цикла
SprPoP в законченную игру: заставка, интро, демонстрационный уровень,
сцены между уровнями, таймер, финал и Hall of Fame. План меню и постоянных
настроек вынесен в menu_settings_plan.md,
детальный план QuickSave — в 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. Сейчас это означает существующую реализацию SprPoP, включая уже встроенные исправления. Аудит и разведение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: ресурсы оболочки.
Штатный маршрут:
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. Текущее состояние SprPoP
Уже реализованы игровой кадр, комнаты, уровни, тайлсеты, 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:
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
Минимальный контекст оболочки:
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:
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:
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
Отдельное состояние перед оригинальной заставкой:
PRINCE OF PERSIA
SPRINTER SP2000 BUILD
version / date / build id
Требования:
- пропускается любой клавишей;
- выключается в Settings;
- не запускает музыку оригинального title и не меняет её тайминги;
- данные версии генерируются сборкой, а не правятся вручную в C;
- отсутствие экрана приводит прямо к
TITLE.
7. Title и текстовая подсистема
Порядок переносится из show_title():
- основной титульный экран;
- Presents;
- название игры и Jordan Mechner;
- story frame / “In the absence…”;
- intro Princess + Jaffar;
- story “Marry Jaffar…”;
- credits;
- Hall of Fame, если таблица непуста;
- 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 живёт одним модулем src/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-функций нужен компактный интерпретатор:
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. Переходы между уровнями
Таблица сценария должна быть отдельна от таблиц механики уровня:
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:
- загрузить PV-набор;
- встреча Kid и Princess;
- объятие;
- появление мыши;
- ending music;
- финальные story/title экраны;
- переход в 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.