Files
Sprinter-SDCC/applications/SprPoP/src/sprpop_cold.h
T
snark13 7377bf6a2c SprPoP: заставка и музыка сразу после запуска, а не через несколько секунд
От запуска до проявления титула проходило ~3 с чёрного экрана в тишине:
pop_boot читал ВСЕ ресурсы игры до первого пикселя, а pop_title_show перед
fade_in вычитывал целиком трек заставки (248 КБ).

Старт разбит на три фазы (sprpop_cold.c):
- pop_boot() — настройки, POP.CFG, графика, чёрный экран, FONT.ATL;
- pop_boot_sound() — snd.idx + ОДНА страница snd.arc, индекс музыки, CBL;
- pop_boot_rest() — остальные страницы звука, kid.ani, Тень, атласы Кида;
  идемпотентно, между кусками доливает трек.

pop_title_show теперь: fade_in -> pop_boot_sound -> pop_music_stream(54) ->
pop_boot_rest под стоящим титулом.  Порядок «сначала fade_in, потом тема»
сохранён как у show_title (seg000.c:1981).  Первый такт заставки укорочен
на фактически потраченное время (часы насоса 85,4 Гц -> кадры луча
сдвигами), иначе сцена уехала бы относительно музыки.  title_wait доливает
трек по полстраницы за кадр.

Музыка играет с ПЕРВОЙ страницы: pop_music_stream уже был (игровые
джинглы), кольцо won не понадобилось — m54 это 16 страниц из 20 доступных.

Звук поднимается в два приёма (pop_sfx_init_begin/finish): насосу для
тишины нужен ровно один блок, и упаковщик обязан класть его первым блоком
страницы 0 (tools/pop_idx.py) — значит для открытия CBL хватает ОДНОЙ
страницы (33 мс вместо 440).  Пока набор неполон, pop_sfx_play пропускает
эффекты; заявки на музыку проходят.

Четыре загрузки из boot УБРАНЫ, а не отложены: атлас фона, страж, уровень 1
и pop_pal_game_load — их и так делает pop_level_switch на входе в любой
уровень (а pop_new_game_load ещё и палитру перед ним), и оба маршрута в
игру, LEVEL_LOAD и DEMO, упираются в него.  Это ~0,77 с на ровном месте.
Так же устроен оригинал: init_game_main до заставки читает только меч,
пламя, звуки и палитры, а chtab_5/6/7 грузит load_lev_spr на старте уровня.
Страховка на случай пропуска заставки в первую секунду — идемпотентный
pop_boot_rest() в начале pop_new_game_load и pop_level_switch.

FONT.ATL остаётся в минимальной фазе, и это не про шрифт: снимок палитры
физически лежит в хвосте его страницы (pop_ui.c), а вся машинерия яркости
начинается с `if (!font_ready) return`.  С отложенным шрифтом заставка
возникала разом на полной яркости — поймано пользователем.

Проверено в MAME: титул виден сразу и проявляется полосами, pop_mus_left
убывает (трек стримится), pop_snd_pages=10 (набор дочитался), цепочка
титул -> интро -> пропуск -> уровень 1 работает.  Банк 8: 95,5 % (732 Б).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
2026-09-02 11:30:53 +03:00

144 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* sprpop_cold.h — контракт между главным циклом (резидент W1/W2) и его
* ХОЛОДНОЙ половиной, вынесенной в банк (sprpop_cold.c).
*
* Зачем. В режиме huge под кучу остаётся то, что не занял _CODE+_DATA, и к
* уровню 8 запас упал ниже килобайта. Главный цикл при этом наполовину
* состоит из кода, который отрабатывает РАЗ НА УРОВЕНЬ (старт/рестарт) или
* вообще только по отладочной клавише (обход комнат, лейбл номера) — держать
* его в дефицитном окне незачем. Кадровый путь (tick/draw/флип) остался в
* резиденте целиком: цена выноса — один трамплин на вызов, а он тут ноль раз
* за обычный кадр.
*
* Что ОСТАЛОСЬ в sprpop.c и почему: enter_room_side (её зовут и холодная
* половина, и переходы между комнатами в кадре падения) и рабочие массивы
* комнаты — они же и есть то состояние, ради которого функция вообще
* существует; банковый модуль читает их как обычные глобалы (данные банков
* линкуются в общий _DATA, --bank-data мы не включаем).
*/
#ifndef ROOMTEST_COLD_H
#define ROOMTEST_COLD_H
#include <stdint.h>
/* ---- то, что холодная половина берёт у главного цикла ---------------- */
/* Рабочие массивы комнаты: заполняет вход в комнату (банк), читают и он, и
* главный цикл (шов, straddle, чит-навигация). Данные банковых модулей
* линкуются в общий _DATA, поэтому обе половины видят их как обычные
* глобалы — в банк уехал только КОД. */
extern uint8_t room_fg[30], room_bg[30];
extern uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
extern uint8_t above_fg[10], above_mod[10];
extern uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (straddle) */
extern uint8_t seam_sig[3]; /* последняя нарисованная openness в шве */
extern uint8_t seam_rows, seam_redraw;
/* Сигнатура ряда шва: openness ворот соседа либо «кнопка нажата». Живёт в
* резиденте — её зовёт и кадровый путь главного цикла, и вход в комнату. */
uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r);
extern uint8_t cur_room; /* её номер (drawn_room) */
extern uint8_t pop_checkpoint; /* checkpoint (seg000:270) — ставит уход */
/* влево из комнаты 7 уровня 3 */
/* Вход в комнату. side — куда ушёл КИД (0 L, 1 R, 2 U, 3 D); 0xFF = «не
* переход за Кидом» (старт уровня, респавн, чит-навигация): от этого
* зависит, пойдёт ли страж следом. enter_room — обёртка с 0xFF.
* Обе в банке 8: зовутся раз на комнату, а кода там на полтора килобайта. */
void enter_room_side(uint8_t room, uint8_t side) __banked;
void enter_room(uint8_t room) __banked;
/* Пересобрать только производные кэши и обе страницы после QuickLoad.
* Игровые таймеры, trobs, mobs и персонажей не изменяет. */
void pop_qsave_restore_room(uint8_t room) __banked;
/* ---- холодная половина ----------------------------------------------- */
/* start_level (seg003:0055) + do_startpos + set_start_pos: поставить Кида в
* стартовую позицию уровня. Один вход и для запуска, и для рестарта после
* смерти / выпадения из уровня — в оригинале это буквально один путь. */
void pop_start_level(void) __banked;
/* ОТЛАДОЧНЫЙ обход комнат по номеру ('+' / ''). dir: 1 — следующая,
* 2 — предыдущая. Ставит Кида на подходящий пол новой комнаты и выводит
* его из боя. Возвращает 1, если комната сменилась (вызывающий обязан
* вернуть себе draw-страницу и обнулить счётчик кадров смерти). */
uint8_t pop_dbg_roomnav(uint8_t dir) __banked;
/* Читы по фронту нажатия (K/I/S/Shift+L/U/[/]/F7/F8). В банке: раз в кадр,
* а резидент W1/W2 переполнен — см. шапку функции. */
void pop_cheat_tick(void) __banked;
/* Кто рисуется ПОЗЖЕ — Кид или соперник (порядок обхода тайлов оригинала,
* set_objtile_at_char + sort_curr_objs). 1 = соперник поверх. Зовётся раз
* в кадр, поэтому живёт в банке: трамплин на кадр дешевле полукилобайта
* резидента. */
uint8_t guard_over_kid(void) __banked;
/* Перевернуть уже нарисованное (зелье инверсии, уровень 9): две копии
* акселератором + инвалидация слотов персонажей и полосы HP. Звать РАЗ на
* переключение, по флагу pop_upside_dirty. */
void pop_flip_screen(void) __banked;
/* ---- Запуск / смена уровня / завершение ------------------------------ *
* Три куска главного цикла с частотой «раз за игру» или «раз на уровень».
* В резиденте от них остались только вызовы. */
/* СТАРТ В ТРИ ФАЗЫ — ради быстрого появления заставки; подробности и
* причины (в том числе почему часть загрузок убрана совсем) — в шапке
* pop_boot в sprpop_cold.c. 0 — OK, -1 — фатально (сообщение выведено).
*
* pop_boot() настройки + графика, чёрный экран;
* pop_boot_sound() индекс+первая страница звука, индекс музыки, CBL —
* звать, когда титул уже на экране: после него можно
* пускать тему заставки;
* pop_boot_rest() всё тяжёлое (эффекты, Кид, Тень, шрифт) под стоящим
* титулом; идемпотентно, страхуется вызовом из
* pop_new_game_load/pop_level_switch на случай, если
* заставку пропустили сразу. */
int pop_boot(void) __banked;
int pop_boot_sound(void) __banked;
int pop_boot_rest(void) __banked;
/* Перейти на pop_next_level: файл уровня, спрайты соперника, тайлсет,
* старт. На время I/O закрывает CBL и оставляет его закрытым: caller обязан
* вызвать pop_sfx_start ПОСЛЕ palette/fade/восстановления snapshot. Если
* файла нет — молча остаться на текущем. -1 — фатально. */
int pop_level_switch(void) __banked;
/* Смена уровня целиком (катсцена, гашение, загрузка, палитра, звук).
* back — страница кадра главного цикла. 0 — ок, -1 — сбой. */
int8_t pop_level_change(uint8_t back) __banked;
/* Клавиши служебного слоя. Живут здесь, а не в sprpop.c: сам обработчик
* (pop_frame_ui) переехал в банк, а главный цикл о них больше не знает. */
#define KBD_QUICKSAVE 0x0B /* F6, PS/2 set 2 */
#define KBD_QUICKLOAD 0x01 /* F9, PS/2 set 2 */
/* МЕНЮ — Backspace, как в оригинале (keys.txt); Esc оставлен дублёром,
* потому что у SDLPoP он тоже открывает меню по умолчанию. */
#define KBD_MENU 0x66 /* Backspace */
#define KBD_MENU_ALT 0x76 /* Esc — дублёр */
/* F10 больше НЕ здесь: он ловится в kbd_idle (sprpop.c) и работает везде, а
* не только в игровом цикле. Тут остаётся Ctrl+Q — вторая клавиша выхода
* из keys.txt. */
#define KBD_QUIT 0x15 /* Q — вместе с Ctrl */
#define KBD_RESTART 0x1C /* A — Ctrl+A, рестарт уровня */
#define KBD_INTRO 0x2D /* R — Ctrl+R, вернуться в заставку */
#define KBD_SHOWTIME 0x29 /* Space — сколько осталось (seg000:612) */
#define KBD_TIMER 0x2C /* T — постоянный показ таймера. БЕЗ модификаторов:
* Shift+T в keys.txt — «добавить HP» (фаза C) */
/* Меню, F10 и QuickSave/Load на границе кадра. restart_level/dead_reset —
* флаги для главного цикла. 0 — продолжаем, 1 — Restart Game, 2 — Quit,
* -1 — сбой. */
int8_t pop_frame_ui(uint8_t *restart_level, uint8_t *dead_reset) __banked;
/* Чистый старт настоящей игры после Enter/Esc на title/intro/credits либо
* клавиши в attract-demo. Ждёт отпускания клавиши, сбрасывает состояние
* новой игры, загружает FIRST_LEVEL и проявляет его первый кадр. */
int pop_new_game_load(void) __banked;
/* Снять idle-хук, закрыть ввод и графику, отдать все ресурсы. */
void pop_shutdown(void) __banked;
#endif /* ROOMTEST_COLD_H */