Меню, статус-строка и оболочка игры: title/intro/cutscene/HoF, POP.CFG, палитры
Эта сессия (меню + текст в служебных полосах):
* Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и
снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках
(UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space —
равноправные клавиши действия (ui_action_down).
* Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и
ABOUT. About показывает тот же текст, что стартовый Sprinter screen,
минус строка про клавиши — общий about_text(), чтобы экраны не
разъехались. CONTROLS собирается таблицей и центрируется по
фактическому числу строк.
* GAME PAUSED переехала в нижнюю статус-строку, как в оригинале
(SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы,
а не пункт меню. POP_HP_Y вынесен в pop_cdraw.h — полосу делят два
модуля.
* pop_status.c (банк 9) — текст в обеих служебных полосах. Нижняя:
порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF,
24 тика). Верхняя отладочная переведена с палочек на текст
«Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с
своим форматированием чисел (без printf и без деления).
Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2
не растёт, весь рендер в банке. Бюджет после правок не изменился
(_CODE 23981, куча 267 Б).
* Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому
всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы
не блитятся вовсе, а вход в комнату заливает только игровое поле
(pop_screen_fill_field) — борта от комнаты к комнате не меняются.
* Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие
ворот (4) и открытие двери покоев (51), из которой входит Джафар.
* docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в
статус-строке: геометрия, семантика text_time_total как идентификатора
сообщения, мигание, рестарт по истечении 36/288.
Вместе с этим выкладывается накопленная работа по оболочке полной игры:
автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo,
Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и
fade, звуковой набор, host-тесты на новые швы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
| [`quicksave_plan.md`](quicksave_plan.md) | **QuickSave/QuickLoad** (✅ реализовано, F6/F9, POP.SAV+BAK): разбор (это enhancement SDLPoP, в оригинале 1989 его НЕТ), инвентаризация нашего состояния, формат снимка 'POPQ' v3, шаги QS1..QS6. Справочник. 2026-08-22 |
|
||||
| [`full_game_plan.md`](full_game_plan.md) | **Полноценная игра**: app state machine, title/intro, demo level 0, таймер, cutscenes, уровни 1..14, ending и Hall of Fame. Уровень 15 исключён. 2026-08-21 |
|
||||
| [`menu_settings_plan.md`](menu_settings_plan.md) | **Pause menu и Settings**: QuickSave/QuickLoad в основном menu, `POP.CFG`, один `POP.SAV` + `POP.BAK`, текущий VANILLA и задел под ENHANCED; §10 — выбор UI-рендера (текстовые строки + свой растровый рендерер, референс SDLPoP: два шрифта), restart без подтверждения. 2026-08-22 |
|
||||
| [`palette_plan.md`](palette_plan.md) | **Палитры и fade**: карта всех 256 слотов (kid.pal/title/story, тайлсеты dungeon/palace, стражи), механика fade (4 ступени vs ~64 у SDLPoP), план модуля `pop_pal.c` (API load/apply/black) + переход уровня через fade. 2026-08-23 |
|
||||
| [`status_line_text.md`](status_line_text.md) | **Строка HP как статус-строка**: полная инвентаризация ВСЕХ текстов SDLPoP в `rect_bottom_text` (геометрия, семантика `text_time_total`, мигание, рестарт по истечении) + что из этого уже есть у нас. 2026-08-25 |
|
||||
| [`levels_plan.md`](levels_plan.md) | Следующий этап: уровни 2+, второй тайлсет, читы SDLPoP |
|
||||
| [`levels_12_15_plan.md`](levels_12_15_plan.md) | **Уровни 12/13** (тень, Джафар, падающие плиты) + что такое 14/15 и 0. 2026-08-13 |
|
||||
| [`midtable_analysis.md`](midtable_analysis.md) | **Слои отрисовки**: как устроены back/mid/fore и objtable в оригинале, чего стоит порт, развилки. 2026-08-13 |
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# От roomtest к полноценной игре — сценарий и оболочка
|
||||
|
||||
Статус: **согласованный план, код не начат** (2026-08-21).
|
||||
Статус: **частично реализовано; аудит обновлён 2026-08-24**. Ранее пометка
|
||||
«завершены FG0–FG12» была неверной: для многих этапов уже есть код и
|
||||
host-тесты, но их критерии приёмки на Sprinter ещё не выполнены. Фактический
|
||||
статус каждого FG приведён в [§14](#14-этапы-реализации).
|
||||
|
||||
Этот документ описывает превращение текущего игрового цикла
|
||||
`roomtest` в законченную игру: заставка, интро, демонстрационный уровень,
|
||||
@@ -72,17 +75,24 @@ attract loop после демо или финала.
|
||||
Джаффар, специальные события, checkpoint, переходы уровней, бесшовный
|
||||
выход 12-го уровня, перенос максимального HP и звуковые эффекты.
|
||||
|
||||
Отсутствуют:
|
||||
Поверх игрового цикла уже добавлены автомат оболочки, title/story, demo
|
||||
уровень 0, global timer, сценарный интерпретатор, level-flow, ending и Hall
|
||||
of Fame. Полный маршрут также собирается в HDD-образ.
|
||||
|
||||
- верхнеуровневая оболочка приложения;
|
||||
- title/story sequence и текстовые экраны;
|
||||
- уровень 0 и записанное управление демо;
|
||||
- общий cutscene engine и PV-ресурсы;
|
||||
- сцены между уровнями;
|
||||
- глобальный таймер и time-expired sequence;
|
||||
- корректный переход из уровня 14 в ending;
|
||||
- финальная сцена и Hall of Fame;
|
||||
- возврат к title без перезапуска программы.
|
||||
Однако это **не означает готовность оболочки**. На момент аудита остаются
|
||||
существенные незакрытые места:
|
||||
|
||||
- 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. Архитектура: автомат состояний приложения
|
||||
|
||||
@@ -218,6 +228,13 @@ title -> demo -> title повторяется неограниченно.
|
||||
Критерий: одинаковый игровой отрезок в 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 состоят из небольшого набора повторяемых команд. Вместо набора
|
||||
@@ -305,24 +322,26 @@ Hall of Fame хранится на HDD в отдельном версионир
|
||||
|
||||
## 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 |
|
||||
Легенда аудита: **✓** — критерий этапа закрыт; **~** — код существует, но
|
||||
критерий приёмки ещё не закрыт; **○** — не начат. Статус отражает состояние
|
||||
исходников и последней MAME-проверки на 2026-08-24, а не только наличие
|
||||
модуля в bank 9.
|
||||
|
||||
QuickSave допускается реализовать до остальных частей оболочки: FG2 — одна
|
||||
из ближайших самостоятельных задач.
|
||||
| этап | статус | результат и фактическое состояние | критерий приёмки |
|
||||
|---|---|---|---|
|
||||
| **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. Проверки
|
||||
|
||||
@@ -346,4 +365,3 @@ QuickSave допускается реализовать до остальных
|
||||
- replay/recording;
|
||||
- точная эмуляция SDL video/controller options;
|
||||
- профиль ENHANCED и индивидуальные switches fixes.
|
||||
|
||||
|
||||
@@ -505,6 +505,53 @@ BUG-CHEAT-FIGHT-1 (выход из боя), и лечится там же.
|
||||
«страж давит сильнее». Режим NORMAL (дефолт) даёт 102,4 мс — см.
|
||||
`frame_pacing_plan.md`.
|
||||
|
||||
## PV intro: единые 12,5 FPS вместо переменных 10/7,5/8,57 FPS
|
||||
|
||||
**Оригинал.** `proc_cutscene_frame()` двигает последовательности через
|
||||
`cutscene_frame_time`: 6 тиков 60 Гц в начале, 8 после первой речи и 7 во
|
||||
время заклинания. Это соответственно 10, 7,5 и примерно 8,57 FPS.
|
||||
|
||||
**У нас (осознанное временное отличие).** Один логический кадр PV держится
|
||||
четыре физических кадра Sprinter: номинально 50/4 = 12,5 FPS. Молния живёт
|
||||
на отдельной физической шкале и не растягивается этим делителем. Если полная
|
||||
отрисовка пересечёт дополнительный фронт, реальная частота может упасть до
|
||||
10 FPS — это допустимо на текущем этапе, но должно быть измерено.
|
||||
|
||||
**TODO.** Перевести PV-сцену на тот же anchor-based механизм точного темпа,
|
||||
который gameplay использует через `pop_beam_sample/pop_pace_end`: измерять
|
||||
число реально прошедших фронтов во время сборки кадра, держать период ровно
|
||||
четыре фронта при укладывании в бюджет и явно учитывать overrun. После замера
|
||||
можно вернуть точные переменные интервалы SDLPoP без накопления фазы.
|
||||
|
||||
## Межуровневые PV-сцены: сохранён реальный период 100 мс
|
||||
|
||||
Это правило не относится к временному темпу основного Princess/Jaffar intro
|
||||
выше. `reset_cutscene()` SDLPoP задаёт для сцен перед уровнями период
|
||||
6 кадров при 60 Гц, то есть 100 мс. На Sprinter тот же период получается
|
||||
ровно как 5 кадров при 50 Гц.
|
||||
|
||||
Суммы вызовов `proc_cutscene_frame()` перенесены без изменения реального
|
||||
времени: сцены 2/4/6 и обе ветки 12 содержат 26 логических кадров (130
|
||||
физических, 2,6 с), сцена 8 — 60 (300, 6,0 с), сцена 9 — 72 (360, 7,2 с).
|
||||
Fade in/out в эти числа не входят, как и в оригинале.
|
||||
|
||||
## Gameplay: загрузка уровней через чёрный cut, без fade
|
||||
|
||||
**Оригинал.** На границах игровых уровней использует fade out/in.
|
||||
|
||||
**У нас (решение пользователя 2026-08-24).** Вход в первый уровень и
|
||||
переход между уровнями выполняются как `старый кадр -> чёрная палитра ->
|
||||
подготовка -> новый кадр с новой палитрой`. Fade на этих двух маршрутах
|
||||
отсутствует. Сюжетные title/story/PV переходы сохраняют собственные fade и
|
||||
left-to-right эффекты.
|
||||
|
||||
Чёрная палитра устанавливается до любого HDD I/O. Загрузчики guard и
|
||||
tileset сами физически правят отдельные цветовые слоты, поэтому после них
|
||||
чёрный экран подтверждается повторно. Зеркальные атласы уровня 9 готовятся
|
||||
до финального источника палитры. CBL открывается последним: старый порядок
|
||||
`level_switch -> CBL open -> BIOS fade` давал скрежет повторяющейся половины
|
||||
аппаратного буфера на входе в Level 1; после перестановки баг исчез в MAME.
|
||||
|
||||
## Тень: кайма силуэта не подкрашивается фоном
|
||||
|
||||
**Оригинал.** Спрайт Тени не хранится — он кладётся ДВАЖДЫ: обычным
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Pause menu и Settings для Sprinter PoP
|
||||
|
||||
Статус: **согласованный план, код не начат** (2026-08-21; обновлён
|
||||
2026-08-22 — решение о рендеринге UI см. §10, Restart Level/Game без
|
||||
подтверждения).
|
||||
Статус: **MS0, MS2 и MS4–MS8 выполнены** (2026-08-23). Pause menu, CFG,
|
||||
Settings, диалоги, Controls и build screen находятся в bank 9. Решение о
|
||||
рендеринге и затемнении — §10.
|
||||
|
||||
Связанные документы:
|
||||
|
||||
@@ -41,12 +41,12 @@ acceleration, scaling, aspect ratio, rumble. UI берёт структуру SD
|
||||
|
||||
```text
|
||||
RESUME
|
||||
QUICKSAVE
|
||||
QUICKLOAD
|
||||
QUICKSAVE (F6)
|
||||
QUICKLOAD (F9)
|
||||
RESTART LEVEL
|
||||
SETTINGS
|
||||
RESTART GAME
|
||||
QUIT
|
||||
QUIT GAME
|
||||
```
|
||||
|
||||
Поведение:
|
||||
@@ -56,9 +56,13 @@ QUIT
|
||||
- QuickSave/QuickLoad только взводят запрос, фактическая операция идёт на
|
||||
безопасной границе кадра;
|
||||
- QuickLoad disabled/показывает `NO QUICKLOAD`, если нет валидных SAV/BAK;
|
||||
- перед QuickLoad из меню лёгкий probe проверяет заголовок и checksum обоих
|
||||
файлов: валидный `POP.BAK` при отсутствующем/битом `POP.SAV` требует
|
||||
отдельного `LOAD BACKUP?`, а не загружается молча;
|
||||
- Restart Level и Restart Game выполняются сразу, БЕЗ подтверждения
|
||||
(2026-08-22): обе операции дёшево обратимы — Restart Level перечитывает
|
||||
уровень, после Restart Game можно тут же сделать QuickLoad из POP.SAV;
|
||||
(2026-08-22): Restart Level перечитывает уровень, Restart Game завершает
|
||||
gameplay и возвращает к первому экрану title/intro; новая игра создаётся
|
||||
общим LEVEL_LOAD только после skip/attract;
|
||||
- Quit требует подтверждения и закрывает файлы/каналы штатным путём;
|
||||
- меню недоступно в demo, cutscene, time-expired и ending;
|
||||
- отдельная debug-комбинация немедленного выхода может остаться только в
|
||||
@@ -86,6 +90,10 @@ BACK
|
||||
`Gameplay profile: VANILLA` показывается read-only: место в модели уже есть,
|
||||
но пользователь не может выбрать ещё не реализованный ENHANCED.
|
||||
|
||||
Изменения применяются немедленно к скорости, читам и звуку, но `POP.CFG`
|
||||
записывается один раз при Back/Esc. На экране есть итог `SETTINGS SAVED` или
|
||||
`SAVE ERROR`; во втором случае runtime-значения остаются рабочими.
|
||||
|
||||
Отладочные параметры `ROOMNAV`, border profiling, stop-frame и переключение
|
||||
double buffering не являются пользовательскими Settings. Они остаются
|
||||
compile-time/debug функциями и скрываются из release UI.
|
||||
@@ -215,9 +223,10 @@ Restart Level:
|
||||
Restart Game:
|
||||
|
||||
- выполняется сразу, без подтверждения;
|
||||
- завершить текущий gameplay session;
|
||||
- освободить level/atlas/temporary EMM;
|
||||
- создать новую игру с уровня 1 и новым глобальным таймером;
|
||||
- завершить текущий gameplay session и вернуть автомат в TITLE;
|
||||
- начать title/intro с самого первого экрана;
|
||||
- создать новую игру с `FIRST_LEVEL` и новым глобальным таймером только
|
||||
после пользовательского skip либо ввода в attract-demo;
|
||||
- настройки оставить;
|
||||
- QuickSave не удалять.
|
||||
|
||||
@@ -234,7 +243,9 @@ Restart Game:
|
||||
- F6/F9 QuickSave/QuickLoad;
|
||||
- Ctrl+S sound;
|
||||
- P speed;
|
||||
- доступные cheats, только если они включены.
|
||||
- доступные cheats, только если они включены: K/Kill Guard, I/Immortal,
|
||||
Shift+L/Next Level, U/Flip Screen и F7/F8/Time −/+ на отдельных понятных
|
||||
строках. Нижней подсказки `Esc or Enter: Back` нет.
|
||||
|
||||
## 10. UI renderer и ввод
|
||||
|
||||
@@ -321,38 +332,123 @@ On/Off и т.д.).
|
||||
|
||||
### 10.3. Наша реализация
|
||||
|
||||
- Новый банк (свободный номер, автонумерация sprinter-cc): код рендерера,
|
||||
- Банк 9: код рендерера,
|
||||
шрифт, таблицы строк, автомат меню. Резидентно — только request-flag и
|
||||
вызов процесса на границе кадра (паттерн pop_qsave_io).
|
||||
- Рендерер портирует контракт seg009, упрощённо: `font_type` + массив
|
||||
глифов переменной ширины, блит монохромного глифа в теневую страницу
|
||||
через W0-окно (как весь остальной код рисования), `draw_string(x,y,color)`
|
||||
+ центрирование по сумме ширин. Пропорциональность — сразу, API не меняется
|
||||
от моноширинного.
|
||||
- Шрифт — АССЕТ, генерируемый паковщиком toolchain из TTF (красивый, сразу
|
||||
с кириллицей), а НЕ системный ZG. Быстрый прототип для MS4 — системный
|
||||
CP866 знакогенератор через существующий `bios_get_zg` в буфер банка
|
||||
(паттерн `gfx_load_default_font`, но буфер в банке, не `_gfx_font_buf`);
|
||||
потом файл шрифта заменяется без смены API.
|
||||
- Рендерер повторяет минимальный контракт seg009: пропорциональные глифы,
|
||||
baseline, `draw_string` и центрирование по сумме advance. Блит идёт через
|
||||
W0-атлас, в `GFX_BANK_SPRITE`: `0xFF` в атласе пропускается, а UI временный
|
||||
и не портит теневую копию игрового фона. Перед каждым кадром UI `gfx_copy_page` переносит чистый
|
||||
shadow видимой страницы в скрытую, затем готовый кадр показывается только
|
||||
на следующем фронте. При выходе чистый фон тем же способом возвращается на
|
||||
обе страницы и восстанавливается исходная visible-страница. Поэтому
|
||||
перемещение выделения не показывает поэтапную перерисовку и не оставляет
|
||||
следов на back buffer.
|
||||
- Шрифт — АССЕТ из **оригинальных** `hc_small_font_data[]` и
|
||||
`hc_font_data[]` SDLPoP, не системный ZG и не TTF. Паковщик
|
||||
`toolchain/pop_extract_font.py` делает `FONT\\font.atl`: 95 ASCII-глифов
|
||||
малого и 95 крупного шрифта (7667 Б). Номер ленты вычисляется из ASCII,
|
||||
поэтому это один текстовый движок, а не атлас готовых надписей.
|
||||
- Двуязычность (eng/rus): строки храним в CP866 — латиница и кириллица одним
|
||||
байтовым порядком, одна кодировка на оба алфавита. Локаль = пара
|
||||
(указатель на таблицу строк, файл шрифта); переключатель — одна настройка.
|
||||
Русские строки длиннее английских ~10–15% — раскладку экранов и ширину
|
||||
колонок закладывать по русской. Второй язык можно добавить позже без
|
||||
переделки: сначала eng.
|
||||
- Подсветка выделенного пункта: инверсия прямоугольника или контур + цвет,
|
||||
как в SDLPoP. Цвета текста брать из стабильного диапазона палитры
|
||||
0x3A..0x3F (его никто не перезаписывает, проверено на борде-индикаторе).
|
||||
- Фон открытого меню: затемнение замороженного кадра (запечь тёмный прямоуг.
|
||||
в теневую копию страницы поверх сохранённого фона) — двойную перерисовку
|
||||
игры под меню делать не нужно, игра стоит.
|
||||
- Навигация: вверх/вниз по пунктам, left/right для значения, Enter/Esc;
|
||||
edge-triggered поверх существующего `kbd_raw`. Звук навигации — menu tick
|
||||
из имеющихся сэмплов.
|
||||
- Визуальная композиция MS4 следует SDLPoP: замороженная сцена остаётся
|
||||
открытой, поверх неё компактный центрированный список без чёрной карточки,
|
||||
выбранная строка обведена тонким светло-серым контуром, а крупное
|
||||
`GAME PAUSED` лежит в нижнем борту. Цвета текста и контура берутся из
|
||||
стабильного диапазона палитры 0x37..0x3F.
|
||||
- Фон открытого меню: снимок текущей палитры, затемнение всех слотов кроме
|
||||
UI 0x37..0x3F и точное восстановление при выходе. Снимок хранится в
|
||||
свободном хвосте EMM-страницы шрифта, не в W2.
|
||||
- Навигация MS4: вверх/вниз, Enter/Esc, edge-triggered поверх `kbd_raw`.
|
||||
Left/right и menu tick добавляются вместе с настройками на MS5.
|
||||
|
||||
Первый UI может быть визуально простым. Критично отсутствие потери клавиш,
|
||||
предсказуемая пауза и отсутствие повреждения игрового back buffer.
|
||||
|
||||
### 10.4. Затенение экрана под меню — решение MS4
|
||||
|
||||
Режим меню виден сразу: bank 9 делает динамический снимок palette 0,
|
||||
затемняет RGB-каналы вдвое и пишет одинаковый результат в обе экранные
|
||||
палитры. Девять стабильных UI-слотов 0x37..0x3F не гасятся. При Resume/Enter
|
||||
палитра восстанавливается из EMM-снимка. Это выбранный вариант Б ниже;
|
||||
ступенчатый fade для роликов пока не нужен и остаётся отдельной будущей
|
||||
задачей, а не причиной раздувать MS4.
|
||||
|
||||
**Как сделано в SDLPoP** (`seg009.c`):
|
||||
|
||||
- Меню: `draw_rect_with_alpha(&screen_rect, color_0_black, pause_menu_alpha)`
|
||||
(menu.c:1364) — альфа-заливка чёрным поверх замороженного кадра средствами
|
||||
SDL; нижняя полоса рисуется с alpha=0, чтобы сквозь неё просвечивало
|
||||
«GAME PAUSED». Прямого аналога на Sprinter НЕТ (альфа-блендинг в железе
|
||||
отсутствует) — это SDL-специфика, переносить нечего.
|
||||
- Ролики/переходы: `fade_in_2/fade_out_2(rows)` (seg009.c:3947+, вызовы из
|
||||
seg000.c) — ПОШАГОВОЕ затухание ПАЛИТРЫ к чёрному и обратно: палитра
|
||||
копируется, каждая строка по 16 цветов гасится за несколько кадров
|
||||
(`which_rows` маской выбирает, какие строки участвуют: 0x800/0x1000/...).
|
||||
Вот этот механизм на Sprinter воспроизводим один в один.
|
||||
|
||||
Отсюда рабочая гипотеза: наш примитив = «снимок текущей палитры → ступенчатое
|
||||
приближение к затемнённой копии (кроме резервного блока для UI)», статично для
|
||||
меню и анимированно для роликов/переходов. Варианты:
|
||||
|
||||
**Вариант А — единая основная палитра (глобальный рефакторинг палитры).**
|
||||
|
||||
1. Собрать ВСЕ палитры игры (уровневые наборы `pal_env*`, kid.pal, палитра
|
||||
Тени и пр.) в одну общую 256-цветную; использовать её целиком всегда.
|
||||
Сейчас переиспользования цветов НЕТ — каждая загрузка ассетов перезаписывает
|
||||
слоты (см. pop_boot: kid.pal затирает тайловые цвета, приходится
|
||||
восстанавливать `pop_bg_pal_apply`/`pop_shadow_pal_apply`).
|
||||
2. Для затенения — затемнённая копия основной палитры, КРОМЕ зарезервированного
|
||||
блока из 16 цветов для самого меню (кандидат — стандартные 16 цветов VGA).
|
||||
3. Выход из меню — возврат к полной основной палитре.
|
||||
|
||||
Плюс: решает попутно существующую боль с перезаписью палитр при загрузках.
|
||||
Минус: большой разовый рефакторинг упаковщиков и всех загрузчиков атласов;
|
||||
нужен аудит, что все цвета всех уровней влезают в 256. **Против говорит
|
||||
план перевода камней подземелья на цвета VGA-версии PoP: там ряд уровней
|
||||
несёт ДРУГУЮ палитру, отличную от SDLPoP (VDUNGEON/VPALACE каскад,
|
||||
levels_plan.md), — единая палитра этому прямо противоречит.**
|
||||
|
||||
**Вариант Б — динамический снимок текущей палитры (сейчас выглядит
|
||||
предпочтительным).**
|
||||
|
||||
1. При открытии меню прочитать всю текущую палитру, сохранить.
|
||||
2. Записать затемнённую копию (кроме зарезервированного блока для меню).
|
||||
3. При выходе — восстановить сохранённую.
|
||||
|
||||
Плюс: локальная правка внутри меню, ничего в пайплайне ассетов не меняется;
|
||||
работает при любой текущей палитре автоматически — включая будущие
|
||||
уровне-специфичные палитры VGA-камней; тот же примитив ступенями даёт
|
||||
fade-out/fade-in для роликов и переходов между уровнями (как fade_*_2 в
|
||||
SDLPoP). Минус: чтение/запись 256 записей палитры при входе/выходе (раз на
|
||||
открытие — дёшево); затемнение «на глаз» может по-разному выглядеть на разных
|
||||
уровнях.
|
||||
|
||||
Резервный блок 16 цветов нужен в ОБОИХ вариантах; текущий диапазон 0x37..0x3F
|
||||
(стабильный, проверен) даёт 9 цветов — этого может не хватить на
|
||||
текст+подсветку+рамку, тогда резервировать отдельный блок.
|
||||
|
||||
**Следствие для архитектуры:** работа с цветом/палитрой должна собраться в
|
||||
ОДИН модуль (сейчас она разбросана: gfx_pal_* вызовы в boot, pop_bg_pal_apply,
|
||||
pop_shadow_pal_apply, вспышки урона в roomtest.c и т.д.). Модуль палитры —
|
||||
единственный владелец записи в палитру и предоставляет примитивы, которые
|
||||
понадобятся и меню, и роликам:
|
||||
|
||||
```text
|
||||
pal_snapshot()/pal_restore() — снимок/восстановление всей палитры
|
||||
pal_dim(step) / pal_undim(step) — ступени затемнения (кроме резервного блока)
|
||||
pal_fade_out(rows)/pal_fade_in(rows) — анимированное затухание по строкам
|
||||
(порт fade_out_2/fade_in_2, seg009)
|
||||
```
|
||||
|
||||
Меню уже использует snapshot+dim локально в bank 9. Когда появятся ролики,
|
||||
выделить из него общий palette/fade-модуль; вспышка урона сможет переехать
|
||||
туда же после отдельного аудита.
|
||||
|
||||
## 11. Диалоги
|
||||
|
||||
Общий диалог подтверждения:
|
||||
@@ -367,6 +463,10 @@ YES / NO
|
||||
|
||||
Диалог не выполняет действие напрямую: он возвращает решение автомату меню,
|
||||
который формирует команду приложению. Так UI не зависит от gameplay-модулей.
|
||||
По умолчанию выбран `NO`; Up/Down/Left/Right меняют ответ, Enter подтверждает,
|
||||
Esc отменяет. Реализованы все три вопроса: Quit, Restore defaults и backup
|
||||
QuickLoad. В Quit-dialog вопрос и `YES / [NO]` заключены в общую рамку;
|
||||
отдельная строка `Enter: Select Esc: Cancel` не выводится.
|
||||
|
||||
## 12. Будущий ENHANCED
|
||||
|
||||
@@ -387,15 +487,15 @@ YES / NO
|
||||
|
||||
| этап | результат | критерий приёмки |
|
||||
|---|---|---|
|
||||
| **MS0** | определить команды app/menu и структуру settings | UI не вызывает gameplay internals напрямую |
|
||||
| **MS0** ✓ | определить команды app/menu и структуру settings | UI возвращает команду главному циклу; прямых gameplay-вызовов нет |
|
||||
| **MS1** | проверить запись/rename/copy на HDD DSS | crash/power-loss сценарий не теряет обе копии save |
|
||||
| **MS2** | `POP.CFG`: defaults, load, validate, save | повреждённый CFG безопасно даёт defaults |
|
||||
| **MS2** ✓ | `POP.CFG`: defaults, load, validate, save | v1 codec, будущий хвост, checksum; повреждённый CFG даёт defaults |
|
||||
| **MS3** | QuickSave hotkeys + POP.SAV/BAK | полный критерий `quicksave_plan.md` |
|
||||
| **MS4** | текстовый рендерер + два шрифта (малый для пунктов, крупный для сообщений) + минимальный pause menu | рендерер строк (глифы, локали, центрирование); все семь пунктов доступны и корректно паузят игру |
|
||||
| **MS5** | General/Gameplay Settings | значения применяются и переживают рестарт |
|
||||
| **MS6** | dialogs + backup recovery | подтверждения и fallback на POP.BAK |
|
||||
| **MS7** | Controls help | полная актуальная раскладка на экране |
|
||||
| **MS8** | интеграция с title/build info | CFG применяется до первого экрана |
|
||||
| **MS4** ✓ | текстовый рендерер + два шрифта (малый для пунктов, крупный для сообщений) + минимальный pause menu | SDLPoP fonts в одном W0-atlas, центрирование, dim/restore palette и tear-free page flip; все семь пунктов видимы, навигация и Resume/QuickSave работают в MAME |
|
||||
| **MS5** ✓ | General/Gameplay Settings | значения применяются сразу и после Back/Esc записываются в POP.CFG |
|
||||
| **MS6** ✓ | dialogs + backup recovery | подтверждения default-NO; QuickLoad спрашивает перед валидным POP.BAK |
|
||||
| **MS7** ✓ | Controls help | показаны движение, action, menu, save/load, звук, speed и conditional cheats; MAME проверил отдельные K/I и Shift+L/U и возврат Esc ровно на один уровень |
|
||||
| **MS8** ✓ | build info | CFG читается до первого показа; включаемый build screen получает ID и дату из Make/git |
|
||||
|
||||
QuickSave (`MS1/MS3`) можно реализовать раньше визуального menu: сначала
|
||||
F6/F9 и сообщения, затем подключить те же команды к пунктам UI.
|
||||
@@ -409,8 +509,12 @@ F6/F9 и сообщения, затем подключить те же кома
|
||||
- Host: SAV invalid -> BAK valid; оба invalid -> NO QUICKLOAD.
|
||||
- MAME: F6, изменение сцены, F9; затем рестарт программы и повторный F9.
|
||||
- MAME: прервать запись/испортить SAV — BAK остаётся загружаемым.
|
||||
- MAME: pause на бое/падении, Resume не меняет состояние и таймер.
|
||||
- MAME: pause на бое/падении, Resume не меняет состояние и таймер; смена
|
||||
выбранного пункта не показывает промежуточный кадр и после закрытия не
|
||||
оставляет меню на второй странице.
|
||||
- MAME: Settings сохраняются после полного выхода и запуска с HDD.
|
||||
- MAME: включить Show Sprinter screen, перезапустить `roomtest`, увидеть
|
||||
build ID/date до первого игрового кадра и пропустить экран Esc/Enter/Space.
|
||||
- Проверка лимита 8 DSS handles на каждом error path.
|
||||
- `make size-check`; menu/text строки не должны съесть резидентный бюджет.
|
||||
|
||||
@@ -423,4 +527,3 @@ F6/F9 и сообщения, затем подключить те же кома
|
||||
- key rebinding;
|
||||
- SDL visual/controller options;
|
||||
- фактическая реализация ENHANCED и individual fix switches.
|
||||
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
# План: консолидация работы с палитрами + переход уровня через fade
|
||||
|
||||
Статус: **этапы A и B реализованы; визуальная приёмка полного маршрута ещё
|
||||
идёт** (2026-08-24). Палитры выделены в bank 10, а renderer cutscene/intro —
|
||||
в bank 11, чтобы не переполнять bank 9 оболочки.
|
||||
Обсуждение велось вокруг `roomtest/` (банк 9 — оболочка, fade из `pop_ui.c`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Текущее состояние: карта палитры
|
||||
|
||||
Палитра Sprinter — 256 записей по 4 байта (B, G, R, 0) = 1 КБ на страницу.
|
||||
У страниц дабл-буфера ДВЕ раздельные палитры (`gfx_pal_load(0,…)` и
|
||||
`gfx_pal_load(1,…)` — почти всегда парой). BIOS читает буферы только из
|
||||
#4000–#BFFF: банковую rodata напрямую отдавать нельзя (копия в стек/W2),
|
||||
см. грабли `pop_guard_set_palette` и `bg_load_tile_pal`.
|
||||
|
||||
### 1.1 Игровая палитра `KID\kid.pal` — раскладка слотов
|
||||
|
||||
Собирается `toolchain/pop_pack_kid.py build_palette()`, грузится одним
|
||||
`gfx_pal_fload` (перезаписывает все 256 записей). Атласы запекались под эти
|
||||
индексы — менять раскладку нельзя без перепаковки ассетов.
|
||||
|
||||
| Слоты | Назначение | Источник | Динамика |
|
||||
|---|---|---|---|
|
||||
| 0x00 | Цвет фона + **вспышка молнии** (подмена записи 0, `flash_bg` ← do_flash/set_bg_attr SDLPoP) | — | меняется в игре |
|
||||
| 0x01–0x2F | Не закреплены (нули) | — | свободно |
|
||||
| 0x30–0x3F | VGA16 — базовые 16 цветов для mono-блитов: пламя факелов, пузырьки зелья (+12 красный «лечение», +10 зелёный, +9 синий), кровь чомпера (12), дворцовая кладка mono (+6) | `VGA16[]` | статично |
|
||||
| ↳ 0x37–0x3F | Поддиапазон **UI**: текст/рамка меню; единственное, что `keep_ui` не затемняет (`MENU_BORDER`=0x37) | — | — |
|
||||
| 0x40–0x4F | chtab_1 пламя/зелья (`POT_PAL_BASE`) | VDUNGEON res150.pal | статично |
|
||||
| 0x50–0x5F | **ENV фон тайлсета** (`POP_PAL_ENV`) | res200.pal набора | **меняется при смене тайлсета** |
|
||||
| 0x60–0x6F | **WALL тайлсета** (`POP_PAL_WALL`) | res360.pal набора | **меняется при смене тайлсета** |
|
||||
| 0x70–0x7F | Kid (`PAL_BASE`) | KID res400.pal | статично |
|
||||
| 0x80–0x8F | Меч chtab_0 (`SWORD_PAL_BASE`) | POT res700.pal | статично |
|
||||
| 0x90–0x9F | Страж chtab_5 (`GUARD_PAL_BASE`) | res10.bin guard_palettes | **меняется по КОМНАТАМ** |
|
||||
| 0xA0–0xAF | Тень (`POP_SHADOW_PAL_BASE`) | RGB-сетка pop_pack_shadow.py | статично |
|
||||
| 0xB0–0xFF | Свободны (5 слотов) | — | — |
|
||||
|
||||
Итого динамических зон три: запись 0 (молния), env+wall (тип здания),
|
||||
стражи (per-room). Всё остальное одинаково всю игру.
|
||||
|
||||
### 1.2 Полноэкранные палитры заставок
|
||||
|
||||
Каждая перезаписывает ВСЕ 256 записей:
|
||||
|
||||
| Файл | Где используется |
|
||||
|---|---|
|
||||
| `KID\kid.pal` (+ fallback `a:\kid.pal`) | BOOT и возврат в игру после заставок |
|
||||
| `TITLE\title.pal` | экран TITLE |
|
||||
| `PV\story.pal` | INTRO и HALL_OF_FAME (одна палитра на обе фазы) |
|
||||
|
||||
### 1.3 Тайлсеты: подземелье ↔ дворец
|
||||
|
||||
Оба набора используют ОДНИ И ТЕ ЖЕ слоты 0x50–0x5F/0x60–0x6F, заполняя их
|
||||
разными цветами (атласы обоих наборов запекались под эти индексы).
|
||||
Переключение = загрузка 64 байт (32 записи env+wall) в обе страницы
|
||||
(`bg_load_tile_pal`); остальные 224 записи не трогаются.
|
||||
|
||||
Какие уровни дворец — `tbl_level_type` (`pop_level_cold.c:44`):
|
||||
**4, 5, 6, 10, 11, 14**; остальные подземелье.
|
||||
|
||||
Палитра дворца `pal_tile.pal` (расшифровка, формат записи B,G,R):
|
||||
|
||||
ENV 0x50–0x5F (пол, ковры, факелы, ворота, пики, арки):
|
||||
|
||||
| Слот | RGB | | Слот | RGB |
|
||||
|---|---|---|---|---|
|
||||
| 50 | 0,0,0 чёрный | | 58 | 202,190,178 серо-бежевый |
|
||||
| 51 | 121,89,60 коричневый | | 59 | 153,133,129 серо-лиловый |
|
||||
| 52 | 161,121,76 светло-коричневый | | 5A | 76,64,56 тёмный серо-бурый |
|
||||
| 53 | 194,149,89 песочный | | 5B | 153,97,89 кирпично-красный |
|
||||
| 54 | 230,178,113 яркий песок | | 5C | 137,80,72 тёмный кирпич |
|
||||
| 55 | 246,202,125 кремовый | | 5D | 48,125,125 бирюзовый |
|
||||
| 56 | 255,234,170 бледно-кремовый | | 5E | 12,56,89 тёмно-синий |
|
||||
| 57 | 255,255,255 белый | | 5F | 202,56,28 красно-оранжевый |
|
||||
|
||||
WALL 0x60–0x6F (вся палитра песочная): 61=(218,170,89), 62=(226,165,93),
|
||||
63=(226,170,97), 64=(218,161,85), 65=белый, 66=(226,165,93), 67=(218,165,89),
|
||||
68=(226,170,89), 69=(218,170,97), 6A=(255,210,137), 6B=(255,218,149),
|
||||
6C=(255,210,137), 6D=(255,218,145), 6E=(194,153,80 тёмный песок),
|
||||
6F=(238,186,117).
|
||||
|
||||
Чем рисуется во дворце:
|
||||
- **Тело стены — НЕ спрайты**, а сплошные заливки; цвет разыгрывается на
|
||||
комнату prandom'ом (`gen_palace_wall_colors`, `pop_bg.c:140`, порт
|
||||
seg000:1942): подряды 1 и 3 берут случайный из 0x61–0x64, подряды 0 и 2 —
|
||||
из 0x66–0x69; соседи по горизонтали не повторяются.
|
||||
- Декор стен id 3–17 — mono-силуэт цветом VGA16+6 (0x36).
|
||||
- Верх дверных проёмов дворца — спец-id 78–84 + полоса 145 («полоса под
|
||||
окнами», pop_room.c:478).
|
||||
- Остальное (пол, ковры, порталы-факелы, ворота, пики) — env-куски
|
||||
pal_env*.atl с ENV-таблицей выше.
|
||||
|
||||
### 1.4 Стражи (0x90–0x9F)
|
||||
|
||||
Цвет задаётся на КОМНАТУ (`level.guards_color[room-1]`), при входе в
|
||||
комнату зовётся `pop_guard_set_palette(color)` ДО отрисовки (слоты общие
|
||||
на экран — смена посреди кадра дала бы стража в новой палитре с полосой HP
|
||||
в старой). Только для обычных стражей (`tbl_guard_type == 0`): скелет и
|
||||
Джафар имеют собственную палитру, зашитую в kid.pal; им зовётся с color=0
|
||||
(не трогать — иначе Джафар на ур.13 покрасился бы в цвет стража своей
|
||||
комнаты). Внутри одного уровня слоты могут перезаписываться многократно.
|
||||
|
||||
## 2. Текущее состояние: механика fade
|
||||
|
||||
### 2.1 Наша реализация (`pop_ui.c`, банк 9)
|
||||
|
||||
- `pop_ui_palette_snapshot()` — снимок всех 256 записей через
|
||||
`gfx_pal_get` по 4 чанкам × 64; хранится в хвосте страницы шрифта
|
||||
FONT.ATL ([0x3C00,0x4000)), map/unmap W0. Требует `font_ready`.
|
||||
- `pop_ui_palette_dim(step, keep_ui)` — готовит ОБЕ экранные палитры из
|
||||
снимка. Шкала без умножений (только сдвиги):
|
||||
|
||||
| Шаг | Формула на канал | Яркость |
|
||||
|---|---|---|
|
||||
| 0 | x | оригинал |
|
||||
| 1 | `(x>>1)+(x>>2)` | ≈3/4 |
|
||||
| 2 | `x>>1` | 1/2 |
|
||||
| 3 | `x>>2` | 1/4 |
|
||||
| 4 | 0 | чёрный |
|
||||
|
||||
`keep_ui` пропускает 0x37–0x3F (меню остаётся ярким).
|
||||
- `pop_ui_fade_out/in(steps)` — проигрывание ступеней за `steps` кадров
|
||||
vsync (`step = i*4/steps`, целочисленно): steps=4 — канонический (по кадру
|
||||
на ступень), steps<4 — перескакивает ступени, steps>4 — повторяет (плавнее),
|
||||
steps=0 у fade_in — мгновенный restore.
|
||||
- Контракт map/unmap: обращения к EMM/W0 и BIOS-палитре строго после unmap.
|
||||
|
||||
Стоимость одного dim ≈ 15–25 тыс. тактов (~4–7 мс при 3.5 МГц) —
|
||||
укладывается в кадр vsync, на практике лагов нет.
|
||||
|
||||
### 2.2 Как сделано в SDLPoP (seg009.c, USE_FADE/gmMcgaVga)
|
||||
|
||||
- fade_out: каждый кадр КАЖДЫЙ ненулевой канал каждой записи −1; до нуля.
|
||||
- fade_in: `fade_pos` от 0x40 вниз; канал +1, пока меньше оригинала.
|
||||
- Уровней затемнения до 63–64 (VGA-канал 6 бит), полный фейд ~63 кадра ×
|
||||
wait_time=2 тика — медленно и кинематографично.
|
||||
- `which_rows` — битовая маска групп по 16 записей: можно фейдить часть
|
||||
палитры (в оригинале используется).
|
||||
- По завершении принудительно восстанавливается оригинал; после out экран
|
||||
заливается чёрным.
|
||||
|
||||
Это осознанное расхождение (скорость/такты vs плавность) — ЗАПИСАТЬ в
|
||||
`docs/impl_diff.md` (сейчас записи нет).
|
||||
|
||||
## 3. Зафиксированные решения
|
||||
|
||||
1. **Ступени затемнения: остаются 4.** Вариант 8 ступеней той же сдвиговой
|
||||
техникой — рассмотреть отдельно, сейчас не внедрять.
|
||||
2. **Предрасчёт fade-вариантов палитры отклонён.** Аргументы: чтение файла
|
||||
с диска на порядок дороже вычисления; 3–7 КБ постоянной RAM при
|
||||
MEMORY=small непозволительны; предрасчёт привязан к конкретным палитрам,
|
||||
а снимок работает с любой текущей автоматически; keep_ui удвоил бы набор.
|
||||
3. **Считать на лету**, хранить один снимок (уже есть, бесплатно в хвосте
|
||||
страницы шрифта).
|
||||
4. **Буферы на стеке**, не статика (W1/W2 мало) и не 1 КБ: обнулить 64/256
|
||||
байт дешевле, чем держать килобайт резидентно.
|
||||
5. **Контракт `gfx_pal_load(pal, start, count, data)`**: count — число
|
||||
СЛОТОВ, буфер обязан быть `count*4` байт; count=0 означает «все 256».
|
||||
6. **Leaf-applеры остаются на месте** (`pop_bg_pal_apply` — банк 7 со своими
|
||||
таблицами, `pop_shadow_pal_apply`, `pop_guard_set_palette`): банковая
|
||||
rodata чужого банка не видна, перенос сломал бы доступ к данным.
|
||||
7. **Молния (`flash_bg` в roomtest.c) не переносится** — игровой эффект
|
||||
записи 0; после вспышки восстановление записи 0 из снимка ложится на API.
|
||||
8. Модель состояния: разделены «какая палитра логически загружена» (load_*)
|
||||
и «с какой яркостью показана» (apply/fade). Любой load_* обновляет снимок;
|
||||
apply/fade показывает его с нужной глубиной. Это позволяет грузить новую
|
||||
палитру «в темноте» (экран остаётся чёрным, пока не позвали apply/fade_in).
|
||||
|
||||
## 4. Целевой API `pop_pal.c/.h` (банк 9)
|
||||
|
||||
```c
|
||||
/* сброс */
|
||||
void pop_pal_black(void) __banked;
|
||||
/* все 256 записей ОБЕИХ страниц = 0. Стековый buf[256], обнуление циклом,
|
||||
* 8 вызовов gfx_pal_load (4 чанка × 2 страницы, паттерн как в dim).
|
||||
* Зовётся СРАЗУ ПОСЛЕ initgraph в pop_boot (раньше нельзя — нет гарантий
|
||||
* состояния графического режима): закрывает кейс «мусор/палитра предыдущей
|
||||
* программы при включении графики». СНИМОК НЕ ТРОГАЕТ (контракт:
|
||||
* чёрный экран без изменения логической палитры). */
|
||||
|
||||
/* загрузка (пишет полную палитру в обе страницы + refresh снимка;
|
||||
* видимую яркость НЕ трогают — экран меняется только по apply/fade) */
|
||||
void pop_pal_file_load(const char *name) __banked;
|
||||
/* gfx_pal_fload + fallback "a:\" + gfx_pal_sync (fallback сегодня
|
||||
* скопирован в каждом из ~6 мест вызова) */
|
||||
|
||||
void pop_pal_game_load(void) __banked;
|
||||
/* file_load("KID\kid.pal") + pop_bg_pal_apply + pop_shadow_pal_apply.
|
||||
* Сегодня тройка скопирована 3 раза (roomtest_cold ~958, pop_title ~88,
|
||||
* pop_intro ~183). Единое место инварианта «kid.pal затирает слоты
|
||||
* тайлсета 0x50..0x6F и тени 0xA0..0xAF». */
|
||||
|
||||
void pop_pal_level_load(uint8_t full) __banked;
|
||||
/* палитра уровня: kid.pal/shadow + tileset 0x50..0x6F если набор сменился
|
||||
* (сравнение через pop_level_type()). full=1 — ПРИНУДИТЕЛЬНО перечитать
|
||||
* kid.pal/shadow (один экспорт с флагом, не две функции — меньше банковых
|
||||
* точек входа). СТРАЖЕЙ (0x90..0x9F) НЕ включает: это компетенция входа
|
||||
* в комнату (pop_guard_set_palette до первого draw). */
|
||||
|
||||
void pop_pal_story_load(void) __banked; /* PV\story.pal (INTRO и HOF — файл один, функция одна) */
|
||||
void pop_pal_title_load(void) __banked; /* TITLE\title.pal */
|
||||
|
||||
/* отображение */
|
||||
void pop_pal_snapshot(void) __banked; /* переезд из pop_ui, тело то же */
|
||||
void pop_pal_apply(uint8_t fade) __banked; /* = dim(fade, 0), 0..4 */
|
||||
void pop_pal_fade_in(uint8_t steps) __banked; /* переезд из pop_ui */
|
||||
void pop_pal_fade_out(uint8_t steps) __banked;
|
||||
|
||||
/* меню продолжает звать низкоуровневый dim(step, keep_ui=1) — отдельный
|
||||
* тонкий экспорт, чтобы не тащить флаг в горячий apply. Старые имена
|
||||
* pop_ui_palette_* / pop_ui_fade_* УДАЛЯЮТСЯ (без алиасов — меньше
|
||||
* экспорта банка). */
|
||||
```
|
||||
|
||||
Соответствие старое→новое: snapshot→snapshot, restore→apply(0),
|
||||
fade_out/in→fade_out/in, тройка kid.pal×3→game_load, fload+fallback+sync×6→file_load.
|
||||
|
||||
## 5. Этап A: рефакторинг — выполнен (2026-08-24)
|
||||
|
||||
1. Создан `roomtest/pop_pal.c/.h` в **bank 10**, добавлен в Makefile.
|
||||
Он владеет политикой `load logical palette → snapshot → apply brightness`.
|
||||
Низкоуровневые snapshot/dim/fade остаются физически в `pop_ui.c`: там
|
||||
владелец страницы FONT.ATL, где лежит снимок; наружу они доступны только
|
||||
через `pop_pal`.
|
||||
2. Заменены call-sites:
|
||||
- `roomtest_cold.c` ~958: black → game_load вместо тройки;
|
||||
- `pop_title.c` title_restore_game_palette → game_load; загрузка title.pal → title_load;
|
||||
- `pop_intro.c` intro_load/intro_restore → story_load/game_load;
|
||||
- `pop_hof.c` (2 × story.pal) → story_load;
|
||||
- `pop_menu.c`: fade/dim → новые имена (dim с keep_ui — низкоуровневый экспорт);
|
||||
- `roomtest.c` demo-start (snapshot+dim(4,0)+fade_in(4)) → новый API.
|
||||
3. Старые вызовы не остаются в коде приложения; внутренние функции `pop_ui`
|
||||
сохранены как реализации одного владельца памяти снимка.
|
||||
4. Сборка и host-тесты пройдены. `make size-check` неприменим: меняется
|
||||
приложение, а не libc/libbgi.
|
||||
5. MAME smoke-тест полного цикла смен палитр: boot → title (title.pal +
|
||||
fade) → intro (story/kid) → demo fade-in → игра → HOF (story.pal).
|
||||
Проверить: отсутствие мусора при включении графики (эффект black),
|
||||
меню с keep_ui остаётся ярким при затемнении, молния (запись 0)
|
||||
восстанавливается.
|
||||
|
||||
## 6. Этап B: переход уровня через fade — реализован, ждёт визуальной приёмки
|
||||
|
||||
Сценарий (обсуждён, детали уточнить по SDLPoP перед реализацией — как
|
||||
оригинал делает смену уровня, есть ли там fade в DOS-версии):
|
||||
|
||||
```
|
||||
fade_out // последний кадр уровня N темнеет
|
||||
рисуем комнату 1 уровня N+1 // во ВТОРУЮ страницу, в темноте
|
||||
pop_pal_level_load(full=0) // новая палитра: железо+снимок обновлены,
|
||||
// экран всё ещё чёрный
|
||||
флип + копия второй страницы обратно в первую
|
||||
fade_in // = анимированный apply 3→2→1→0
|
||||
```
|
||||
|
||||
Экономия: реально переезжают только 32 записи (env/wall) при смене набора
|
||||
dungeon↔palace; guards_color обновит вход в комнату. Kid/shadow не меняются
|
||||
— потому full=0.
|
||||
|
||||
Реализация находится в `roomtest.c` / `roomtest_cold.c`: последний кадр
|
||||
уровня N темнеет, `pop_level_switch()` подготавливает первый кадр N+1 и
|
||||
обновляет логический источник через `pop_pal_level_load(1)`, затем главный
|
||||
цикл показывает кадр только через fade-in. Восемь ступеней и отдельная
|
||||
анимация смерти не входят в этот этап.
|
||||
|
||||
**Этап B закрывает два открытых бага** (разборы — `roomtest/BUGS_OPEN.md`):
|
||||
- [PAL-L1-AFTER-INTRO] — вход в игру на уровень 1 после интро с чёрным
|
||||
экраном (маршрут demo_new_game; корень не установлен, воспроизведение
|
||||
нестабильно);
|
||||
- [PAL-DUNGEON-STALE] — переход 3→4 оставляет подземную палитру (корень
|
||||
ясен: fade_in восстанавливает из снимка, снятого ДО загрузки тайлсета
|
||||
дворца; быстрый фикс `fade_in_pending` 2026-08-23 сам же и проявляет этот
|
||||
дефект модели).
|
||||
|
||||
Быстрый фикс 2026-08-23 (маршрут CUTSCENE → LEVEL_LOAD → PLAYING,
|
||||
`fade_in_pending` + `pop_ui_fade_in(4)` после `pop_level_switch`) закрыл
|
||||
чёрный экран на переходах с pre-cutscene внутри подземелья (1→2), но модель
|
||||
«кто и когда меняет яркость» остаётся разношёрстной — её и приводит в
|
||||
порядок этап B.
|
||||
|
||||
## 7. Этап C: документирование
|
||||
|
||||
- Запись в `docs/impl_diff.md`: наши 4 ступени vs SDLPoP ~64 (что делает
|
||||
оригинал, что делаем мы — сдвиговая шкала ради тактов, чем платим —
|
||||
грубее градации, что проверять при регрессе).
|
||||
- После этапа B — дополнить запись про сам переход.
|
||||
|
||||
## 8. Не трогаем
|
||||
|
||||
- Молнию (`flash_bg`, roomtest.c) — включая обход SDCC-бага
|
||||
`gfx_pal_set(0,0,0,0,0)` → ручные `gfx_pal_set(0/1, 0, r,g,b)`;
|
||||
- leaf-applеры: `pop_bg_pal_apply` (банк 7), `pop_shadow_pal_apply`,
|
||||
`pop_guard_set_palette` (данные своих модулей);
|
||||
- хранилище снимка в хвосте страницы шрифта FONT.ATL (бесплатное место,
|
||||
guard `font_ready`);
|
||||
- раскладку слотов 0x00–0xAF (зафиксирована атласами).
|
||||
@@ -1,6 +1,6 @@
|
||||
# Звук в порте PoP — разбор и план
|
||||
|
||||
Дата: 2026-08-20. Статус: **разбор, кода нет.**
|
||||
Дата: 2026-08-20. Статус: **PCM-эффекты реализованы; музыка/AY не реализованы.**
|
||||
|
||||
Задача пользователя: добавить звук. Приоритет — эффекты; музыку, если
|
||||
найдётся способ. Эффекты — **обязательно WAV, а не PC-спикер**
|
||||
@@ -351,6 +351,7 @@ IBM PC 1989 года: один квадратный голос. Он у нас
|
||||
| 4/5/6/7 | ворота: закрываются / открываются / рухнули / стоп | `pop_trob.c` | seg007 |
|
||||
| 8 | удар о стену | `pop_map.c` bumped_fall/bumped_floor + seqtbl | seg004/seg006 |
|
||||
| 9 | зацеп за карниз | `pop_map.c` check_grab | seg006 |
|
||||
| 10 | клинок о клинок | `roomtest.c` после `check_sword_hurt`, если один из бойцов в кадре 167 | seg000:1353 |
|
||||
| 11 | свист клинка мимо | `guards.c` check_hurting | seg002:0DAE |
|
||||
| 12/13 | ранен соперник / Кид | `guards.c` hurt_by_sword | seg002:0C1F |
|
||||
| 13 | Кид ранен зельем | `pop_map.c` ветка «злого» зелья | seg006:1894 |
|
||||
@@ -368,7 +369,8 @@ IBM PC 1989 года: один квадратный голос. Он у нас
|
||||
| 48 | напоролся на пики | `pop_map.c` | seg005 |
|
||||
| 49 | пики пошли | `pop_map.c` start_anim_spike | seg007:08F6 |
|
||||
|
||||
Что осталось не разведено — только МУЗЫКА (25 презентация, 26 объятия,
|
||||
Что осталось не разведено — только МУЗЫКА (24/28 смерть, 25 презентация,
|
||||
26 объятия,
|
||||
27/35/40 заставки, 29 встреча Джафара, 30/33 зелья, 32/41 конец уровня,
|
||||
36 время вышло, 37 победа, 43 смерть Джафара, 50/52/53 сюжетные вставки)
|
||||
и 51 (дверь принцессы, тоже из заставки). Их черёд — фаза «музыка».
|
||||
@@ -641,3 +643,42 @@ play_next_sound() seg000:1304 — раз в кадр решает, зап
|
||||
нужно сходить Кидом на левую кнопку и вернуться. Это поведение оригинала
|
||||
(trob челюстей создаётся событием), а не наш дефект — учитывать при
|
||||
постановке автотестов.
|
||||
|
||||
## 13. Повторный аудит игрового звука (2026-08-24)
|
||||
|
||||
Проверены три независимых слоя: содержимое атласов, места вызова и живой
|
||||
тракт `play -> tick -> CBL`.
|
||||
|
||||
- В восьми `SND*.ATL` есть все 31 оцифрованных ресурса: `0..23`, `44..49`
|
||||
и `51`; ненулевая страница/длина есть у каждой записи таблицы.
|
||||
- Для всех 30 PCM-эффектов, которые могут возникать непосредственно в игре,
|
||||
есть место вызова. Последним пропуском был звук 10 при столкновении
|
||||
клинков; условие перенесено буквально из `check_sword_vs_sword` SDLPoP.
|
||||
Звук 51 относится к сцене с принцессой, а не к игровому циклу.
|
||||
- Звук 19 «Кид достал меч» проверен в MAME брейкпоинтами. В момент вызова
|
||||
предыдущий PCM уже закончился (`sfx_left=0`), номинация дошла до
|
||||
`pop_sfx_tick`, после чего курсор получил id 19, страницу 5, смещение
|
||||
`0x1500` и длину 2816 байт. В этом прогоне его не подавляли решётка,
|
||||
плиты, шаги или приоритеты. Если он всё ещё субъективно не слышен, искать
|
||||
надо после выбора эффекта — в непрерывности CBL/громкости самого сэмпла.
|
||||
- После продолжительного прогона title/intro/demo/menu CBL обслужил 3303
|
||||
блока и сообщил 0 программных недоливов (`cbl_requests=0x0CE7`,
|
||||
`cbl_underruns=0`). Это исключает возврат `fill=0`, но само по себе не
|
||||
измеряет запоздание прерывания внутри слишком длинной секции `DI`.
|
||||
- Регрессия full-game на входе в Level 1 оказалась именно запозданием CBL:
|
||||
`pop_level_switch` открывал его ДО блокирующего BIOS fade-in. Demo был
|
||||
чистым, потому что включал палитру без fade. Теперь загрузчик оставляет
|
||||
CBL закрытым, а caller открывает его после окончательной палитры/QuickLoad;
|
||||
скрежет на Level 1 исчез в MAME. Однократный щелчок самого первого
|
||||
`cbl_open` за всю MAME-сессию остаётся отдельной низкоприоритетной задачей.
|
||||
- Открытие pause menu у SDLPoP беззвучно; движение играет 21, вход/выход
|
||||
из подменю — 22, изменение настройки — 10. Эти вызовы перенесены. На
|
||||
время полного копирования страницы и файловых операций CBL закрывается,
|
||||
после flip открывается снова: аппаратная половина не должна повторять
|
||||
старые данные и давать «скрежет».
|
||||
|
||||
Отдельно остаётся игровая музыка и сигнальные мелодии без PCM: смерть
|
||||
(`24/28`), начало/появление Shadow (`25`), встреча Jaffar (`29`), большое
|
||||
и малое зелья (`30/33`), Shadow (`32`), победа/меч (`37`), перо (`39`),
|
||||
конец уровня (`41`) и победа над Jaffar (`43`). Вызовы и AY-проигрыватель
|
||||
для них ещё не реализованы; наличие всех PCM-эффектов эту задачу не закрывает.
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# Текст в нижней статус-строке (строке HP) — полная инвентаризация SDLPoP
|
||||
|
||||
Разбор `SDLPoP/src/` на 2026-08-25. Цель — знать ВЕСЬ набор сообщений,
|
||||
которые оригинал печатает в ту же полосу, где нарисованы деления HP,
|
||||
и правила их появления/исчезновения. Это входные данные для порта:
|
||||
у нас пока туда пишется только `GAME PAUSED`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Геометрия: одна полоса на HP и на текст
|
||||
|
||||
```
|
||||
rect_bottom_text = { top 193, left 70, bottom 202, right 250 } // data.h:217
|
||||
display_text_bottom: draw_rect(чёрным) + show_text(halign_center, valign_bottom)
|
||||
```
|
||||
|
||||
* Деления HP **Кида** — от `x = 0` вправо, шаг 7, максимум 10 → занимают `x 0..69`.
|
||||
* Деления HP **стража** — от `x = 314` влево, шаг 7, максимум 10 → занимают `x 245..320`.
|
||||
* Текст живёт РОВНО в промежутке `x 70..250` и по X с делениями не пересекается.
|
||||
* По Y деления на `y = 194..200`, текст (`valign_bottom` к 202) — на `y = 195..201`,
|
||||
то есть на строку ниже. Именно поэтому в оригинале текст выглядит «сидящим»
|
||||
чуть ниже стрелок HP.
|
||||
|
||||
**У нас**: `POP_HP_Y = 194` (`pop_cdraw.h`), экран сдвинут на `POP_YOFF = 28`,
|
||||
базовая линия крупного шрифта `POP_YOFF + POP_HP_Y + 8 = 230` — силуэт
|
||||
ложится на `223..229`, то есть та же картинка.
|
||||
|
||||
## 2. Два примитива и два таймера
|
||||
|
||||
| Имя | Что делает |
|
||||
|-----|------------|
|
||||
| `display_text_bottom(text)` (seg008:2644) | стереть прямоугольник цветом 0 и напечатать текст по центру |
|
||||
| `erase_bottom_text(arg)` (seg008:266D) | стереть прямоугольник; при `arg != 0` ещё и обнулить оба таймера |
|
||||
| `text_time_remaining` | сколько игровых тиков сообщение ещё висит; 0 — ничего не висит |
|
||||
| `text_time_total` | **идентификатор сообщения**, а не только его длительность |
|
||||
|
||||
Обработка тика — в `draw_game_frame`/`idle` (seg000:956). Комментарий в
|
||||
оригинале прямой: *«Note: texts are identified by their total time!»* Значения
|
||||
`text_time_total`, у которых есть особое поведение:
|
||||
|
||||
| `total` | Смысл | Что происходит по истечении |
|
||||
|---------|-------|------------------------------|
|
||||
| 12 | «1 SECOND LEFT» | обычное стирание |
|
||||
| 24 | обычное короткое сообщение | обычное стирание |
|
||||
| 36 | смерть на демо-уровне (0) или на уровне зелий (15) — **текста нет** | `start_game()` — рестарт игры |
|
||||
| 288 | «Press Button to Continue» | `start_game()` — рестарт игры |
|
||||
| 1188 | защита от копирования (уровень 15) | **не убывает и не исчезает** |
|
||||
|
||||
Мигание: при `total == 288` и `remaining < 72` сообщение мигает с периодом 12
|
||||
тиков — 4 тика видно (`blink_frame <= 3`), 8 нет; в кадре `blink_frame == 3`
|
||||
заново печатается текст и играет звук 38 (`sound_38_blink`).
|
||||
|
||||
Сброс: `init_game()` (seg003:32) обнуляет оба таймера и `is_show_time` — то есть
|
||||
любое сообщение умирает на старте уровня.
|
||||
|
||||
---
|
||||
|
||||
## 3. Полный список сообщений
|
||||
|
||||
### 3.1. Состояние программы
|
||||
|
||||
| Текст | Где | Таймер |
|
||||
|-------|-----|--------|
|
||||
| `GAME PAUSED` | seg000:1769, пока `is_paused` | **без таймера**: печатается на входе в паузу, `erase_bottom_text(1)` на выходе (seg000:1784) |
|
||||
|
||||
### 3.2. Уровень и оставшееся время (`show_level` / `show_time`, seg008)
|
||||
|
||||
| Текст | Условие | `total` |
|
||||
|-------|---------|---------|
|
||||
| `LEVEL %d` | `show_level()` при старте уровня; только `1..12` (`hide_level_number_from_level = 14`), не при `seamless`; уровень 13 показывается как **12** (`level_13_level_number`) | 24, дальше сразу `is_show_time = 1` |
|
||||
| `%d MINUTES LEFT` | каждая минута, кратная 5, и каждая из последних 5 | 24 |
|
||||
| `%d SECONDS LEFT` | последняя минута, раз в 12 тиков | 24 |
|
||||
| `1 SECOND LEFT` | остался 1 с | **12** |
|
||||
| `TIME HAS EXPIRED!` | `rem_min == 0` | 24 |
|
||||
| `%d MINUTES PASSED` / `1 MINUTE PASSED` | только SDLPoP (`ALLOW_INFINITE_TIME`), при отрицательном таймере | 24 |
|
||||
|
||||
Что взводит `is_show_time` (все → следующий кадр печатает время):
|
||||
|
||||
* **Space** — seg000:612, штатная клавиша оригинала «сколько осталось»;
|
||||
* читы **`-`/`+` numpad** (изменение времени) — seg000:762 / 777, при этом
|
||||
таймеры сообщения обнуляются, чтобы новое напечаталось немедленно;
|
||||
* **смерть Джафара** — `on_guard_killed()` seg006:1936, уровень 13
|
||||
(`jaffar_victory_level`): вспышка + показать время;
|
||||
* истечение очередной минуты — seg008:1796;
|
||||
* сразу после `show_level()`.
|
||||
|
||||
Обнуляет `is_show_time`: `play_kid()` при смерти (seg006:1365) и
|
||||
`show_copyprot(1)` (seg000:2385).
|
||||
|
||||
### 3.3. Смерть Кида
|
||||
|
||||
| Текст | Где | `total` |
|
||||
|-------|-----|---------|
|
||||
| `Press Button to Continue` | `play_kid()` seg006:1383 — умер на обычном уровне | **288** (мигает, затем рестарт игры) |
|
||||
| *(без текста)* | тот же код, но уровень 0 (демо) или 15 (зелья) | **36** (тихая пауза, затем рестарт игры) |
|
||||
|
||||
Стирается: `fell_out()` (seg006:1342, упал из комнаты 0) и чит **R**
|
||||
(воскрешение, seg000:783) — оба зовут `erase_bottom_text(1)`.
|
||||
|
||||
### 3.4. Сохранение и загрузка
|
||||
|
||||
| Текст | Клавиша | `total` |
|
||||
|-------|---------|---------|
|
||||
| `GAME SAVED` / `UNABLE TO SAVE GAME` | Ctrl+G (`save_game`, seg000:2211) | `total` не ставится, `remaining = 24` |
|
||||
| `QUICKSAVE` / `NO QUICKSAVE` | F6 (расширение SDLPoP, seg000:497) | 24 |
|
||||
| `QUICKLOAD` / `NO QUICKLOAD` | F9 (расширение SDLPoP, seg000:514) | 24 |
|
||||
|
||||
### 3.5. Ответы на клавиши (`answer_text` → `need_show_text`, все `total = 24`)
|
||||
|
||||
| Текст | Клавиша |
|
||||
|-------|---------|
|
||||
| `SOUND ON` / `SOUND OFF` | Ctrl+S |
|
||||
| `KEYBOARD MODE` | Ctrl+K |
|
||||
| `JOYSTICK MODE` / `JOYSTICK NOT FOUND` / `JOYSTICK UNAVAILABLE` | Ctrl+J |
|
||||
| `PRINCE OF PERSIA V1.0` (в SDLPoP заменено на `SDLPoP v%s`) | Ctrl+V |
|
||||
| `SDL COMP v… LINK v…` | Ctrl+C — только SDLPoP |
|
||||
|
||||
### 3.6. Отладочные читы (`cheats_enabled`, `total = 24`)
|
||||
|
||||
| Текст | Клавиша | Смысл |
|
||||
|-------|---------|-------|
|
||||
| `S%d L%d R%d A%d B%d` | `C` | номер отрисованной комнаты и её соседей L/R/A/B |
|
||||
| `AL%d AR%d BL%d BR%d` | Shift+`C` | диагональные соседи |
|
||||
|
||||
### 3.7. Защита от копирования (только уровень 15)
|
||||
|
||||
| Текст | Где | `total` |
|
||||
|-------|-----|---------|
|
||||
| `WORD %d LINE %d PAGE %d` | `show_copyprot(1)` seg000:2389 | **1188** — висит, пока не сменится уровень |
|
||||
|
||||
### 3.8. Только SDLPoP, в оригинале 1989 отсутствует
|
||||
|
||||
| Текст | Где |
|
||||
|-------|-----|
|
||||
| `RECORDING`, `REPLAY SAVED`, `REPLAY CANCELED` | replay.c:599/626/628 |
|
||||
| имя файла скриншота | screenshot.c:62 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Что из этого касается нашего порта
|
||||
|
||||
Реализовано:
|
||||
|
||||
* `GAME PAUSED` — в строке HP, без таймера (`pop_menu.c`).
|
||||
|
||||
Есть у нас, но сообщение пока НЕ печатается (показывается иначе или не
|
||||
показывается вовсе):
|
||||
|
||||
* `LEVEL %d` и вся группа времени — таймер уже есть (`pop_timer.c`), текста нет;
|
||||
* `QUICKSAVE`/`QUICKLOAD` — у нас вместо строки экран-уведомление (`ui_notice`);
|
||||
* `SOUND ON/OFF` — у нас индикатор в отладочной полосе, а не текст;
|
||||
* `Press Button to Continue` — у нас авто-респавн по таймеру/стрелке вверх;
|
||||
* номера комнат (`C`/Shift+`C`) — у нас палочки в отладочной полосе.
|
||||
|
||||
Нам не нужно: copy protection (уровень 15 исключён из порта — см.
|
||||
`full_game_plan.md`), joystick-режимы, replay, скриншоты.
|
||||
|
||||
Механика, которую придётся портировать целиком, если брать группу времени:
|
||||
пара таймеров `text_time_total`/`text_time_remaining` с семантикой
|
||||
«идентификатор сообщения» — иначе не воспроизвести ни мигание, ни рестарт по
|
||||
истечении 36/288.
|
||||
|
||||
---
|
||||
|
||||
## 5. Цена вывода и что делать, если упрёмся
|
||||
|
||||
Блит одного глифа стоит ~4,6 тыс. тактов почти независимо от размера — это
|
||||
цена вызова, а не пикселей (memory `blit_cost_model`). Полсотни символов =
|
||||
полкадра. Что уже сделано в `pop_status.c` / `pop_ui.c`:
|
||||
|
||||
* **change-driven**: пока показанное не изменилось, не рисуем вовсе;
|
||||
* **по полям**: смена комнаты — две цифры (~9 тыс. тактов, 2% кадра), а не
|
||||
вся строка; подписи рисуются только при полной инвалидации;
|
||||
* **пробелы не блитятся**: их глиф целиком прозрачен, а стоит как буква —
|
||||
на полной отладочной строке это девять сэкономленных блитов;
|
||||
* **заливка только поля** при входе в комнату (`pop_screen_fill_field`):
|
||||
борта от комнаты к комнате не меняются, это и четверть заливки, и то, что
|
||||
обе полосы вход переживают.
|
||||
|
||||
Запас, если бюджета всё же не хватит (идеи пользователя, 2026-08-25):
|
||||
|
||||
1. **Растянуть вывод на несколько кадров, не показывая полуготовую строку.**
|
||||
Печатать по нескольку букв за кадр, держа цвет шрифта чёрным (отдельный
|
||||
индекс палитры), а по готовности подменить этот индекс на белый — строка
|
||||
появится целиком и мгновенно. Стоит ноль байт памяти и укладывается в
|
||||
нашу же технику «два разных чёрных» (`POP_COL_OUTSIDE`).
|
||||
2. **Собирать строку в один спрайт** в свободном хвосте страницы шрифта и
|
||||
блитить одним вызовом. Дороже по подготовке (~35 тыс. тактов на
|
||||
копирование), но выгодно там, где строка ЦЕЛИКОМ меняется каждый раз.
|
||||
Для меню этот путь уже рассматривался и был отвергнут; для статус-строк
|
||||
он имеет смысл только вместе с п.1.
|
||||
|
||||
Про QuickSave/QuickLoad оптимизация не нужна вовсе: там игра и так стоит на
|
||||
время дисковой операции.
|
||||
|
||||
## 6. Ловушка: свисающие глифы
|
||||
|
||||
Зона стирания текста обязана захватывать строку НИЖЕ базовой линии. В малом
|
||||
шрифте `'p'` имеет высоту 7 при ascent 5, `','` — 6: они свисают под базовую
|
||||
линию. Стирание ровно до неё оставляло от хвоста «p» в «Speed:» одинокую
|
||||
точку (поймано в MAME 2026-08-25).
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user