Files
Sprinter-SDCC/applications/SprPoP/docs/full_game_plan.md
T
snark13 31b82661eb SprPoP: автономное приложение, выделенное из roomtest
Порт 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>
2026-08-27 12:12:28 +03:00

23 KiB
Raw Blame History

От 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():

  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 живёт одним модулем 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:

  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.