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
This commit is contained in:
@@ -1114,6 +1114,36 @@ void pop_flip_screen(void) __banked
|
||||
* уровень). В резиденте от них оставались только вызовы — сам код здесь.
|
||||
*/
|
||||
|
||||
/* Фазы старта (см. шапку pop_boot): обе половины идемпотентны, а
|
||||
* boot_rest_fail нужен, чтобы повторный вызов не пытался читать заново
|
||||
* то, что уже не прочиталось. */
|
||||
static uint8_t boot_sound_done;
|
||||
static uint8_t boot_rest_done;
|
||||
static uint8_t boot_rest_fail;
|
||||
|
||||
/* СТАРТ РАЗБИТ НА ТРИ ФАЗЫ — чтобы игрок увидел заставку и услышал музыку
|
||||
* сразу, а не через несколько секунд чёрного экрана в тишине.
|
||||
*
|
||||
* pop_boot() — только настройки и графика: чёрный экран за ~0,1 с.
|
||||
* pop_boot_sound() — индекс+первая страница звука, индекс музыки, CBL:
|
||||
* после него титул уже показан, и тема заставки может
|
||||
* начать играть, не дожидаясь остальных ресурсов.
|
||||
* pop_boot_rest() — всё тяжёлое (эффекты, Кид, Тень, шрифт) ПОД стоящим
|
||||
* титулом и звучащей темой; идемпотентно.
|
||||
*
|
||||
* ЧЕТЫРЕ ЗАГРУЗКИ ОТСЮДА ПРОСТО УБРАНЫ, а не отложены: атлас фона, страж,
|
||||
* уровень 1 и игровая палитра — это дубликаты того, что и так делает
|
||||
* pop_level_switch() на входе в ЛЮБОЙ уровень (а pop_new_game_load ещё и
|
||||
* pop_pal_game_load перед ним). Оба маршрута в игру, LEVEL_LOAD и DEMO,
|
||||
* упираются в pop_level_switch, так что второй раз читать их до заставки
|
||||
* незачем — это ~0,77 с на ровном месте. Так же устроен и оригинал: у
|
||||
* SDLPoP chtab_5/6/7 грузит load_lev_spr() на старте уровня, а init_game_main
|
||||
* до заставки читает только меч, пламя, звуки и палитры (seg000.c:156).
|
||||
*
|
||||
* ПАЛИТРУ ТРОГАТЬ НЕЛЬЗЯ, пока на экране титул: и kid.pal (все 256 записей),
|
||||
* и палитра тайлсета пишутся прямо в железо и перекрасили бы заставку.
|
||||
* Поэтому в отложенной части нет ни одной палитровой операции — они все
|
||||
* живут на маршруте входа в уровень, за чёрным экраном. */
|
||||
int pop_boot(void) __banked
|
||||
{
|
||||
/* Настройки: дефолты, затем POP.CFG, если он есть и корректен. */
|
||||
@@ -1141,77 +1171,105 @@ int pop_boot(void) __banked
|
||||
gfx_set_visible_page(0);
|
||||
pop_pal_black();
|
||||
|
||||
/* Набор фона — по типу СТАРТОВОГО уровня; atlas_load безопасен и после
|
||||
* initgraph: при первом вызове он сохраняет страницу DSS W0. Уровень к
|
||||
* этому моменту ещё не прочитан, поэтому тип берём напрямую от номера. */
|
||||
/* FONT.ATL — В МИНИМАЛЬНОЙ ФАЗЕ, И ОТКЛАДЫВАТЬ ЕГО НЕЛЬЗЯ.
|
||||
*
|
||||
* Дело не в шрифте: СНИМОК ПАЛИТРЫ физически живёт в хвосте страницы
|
||||
* шрифта (pop_ui.c, POP_FONT_SNAPSHOT_OFF — в W2 килобайта нет), и вся
|
||||
* машинерия яркости начинается с `if (!font_ready) return`. Без него
|
||||
* pop_pal_apply(DIM_BLACK) не гасит, а fade_in нечего проявлять —
|
||||
* заставка возникает разом на полной яркости (поймано пользователем
|
||||
* сразу после первой версии этой перестановки, 2026-09-02).
|
||||
* Цена вопроса — 12 КБ, около 75 мс. */
|
||||
if (pop_menu_init() != 0) {
|
||||
puts("menu font atlas load failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pop_current_level = FIRST_LEVEL;
|
||||
if (pop_bg_load(pop_level_type()) != 0) {
|
||||
puts("pop_bg_load failed");
|
||||
return -1;
|
||||
}
|
||||
/* pop_bg_load установил только свои слоты палитры. До конца загрузки
|
||||
* всё равно держим экран чёрным. */
|
||||
pop_pal_black();
|
||||
pop_cd_init(); /* метка «фон трогали» — пустые диапазоны */
|
||||
/* pop_cheats/speed применит pop_settings_apply после pop_sfx_init. */
|
||||
pop_guard_reset();
|
||||
if (pop_kid_data_load("KID\\kid.ani") != 0) { /* кадры+seqtbl в EMM-странице */
|
||||
puts("kid.ani load failed\n");
|
||||
return -1;
|
||||
}
|
||||
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
||||
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
|
||||
pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */
|
||||
pop_next_level = pop_current_level; /* current_level = next_level (load_lev_spr) */
|
||||
pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */
|
||||
pop_checkpoint = 0; /* init_game (seg003:37) */
|
||||
if (!pop_settings.show_build_info) pop_pal_apply(POP_PAL_DIM_BLACK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ЗВУК ВПЕРЁД ОСТАЛЬНЫХ РЕСУРСОВ. Насосу для тишины (а значит и для того,
|
||||
* чтобы CBL вообще открылся) нужен ровно ОДИН блок набора, и упаковщик
|
||||
* обязан класть его первым блоком страницы 0 — поэтому здесь читается одна
|
||||
* страница вместо восьми (33 мс против 440). Остальные приезжают в
|
||||
* pop_boot_rest, а до тех пор pop_sfx_play молча пропускает эффекты.
|
||||
*
|
||||
* Звать ПОСЛЕ того, как титул уже проявлен: чтение всё равно занимает
|
||||
* полсекунды, и лучше, чтобы она прошла под картинкой. */
|
||||
int pop_boot_sound(void) __banked
|
||||
{
|
||||
if (boot_sound_done) return 0;
|
||||
boot_sound_done = 1;
|
||||
(void)pop_sfx_init_begin(); /* эффектов может и не быть — не фатально */
|
||||
/* Индекс музыкального набора — одна страница EMM. Без него музыки не
|
||||
* будет (pop_music_info вернёт -1 на любой трек), но игра работает:
|
||||
* это необязательный ресурс, как и сами треки. */
|
||||
(void)pop_music_index_load();
|
||||
pop_settings_apply(1); /* CFG -> speed/cheats/CBL */
|
||||
(void)pop_sfx_start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Шаг подкачки трека между кусками загрузки: заставочная тема играет уже
|
||||
* с первой страницы (pop_music_stream), остальные обязаны приезжать по
|
||||
* ходу — иначе насос уйдёт в ещё не прочитанную страницу. Запас после
|
||||
* шага — 0,75 с звучания на каждые 8 КБ, куски загрузки короче. */
|
||||
static void boot_music_step(void)
|
||||
{
|
||||
if (pop_music_loading()) (void)pop_music_load_step();
|
||||
}
|
||||
|
||||
int pop_boot_rest(void) __banked
|
||||
{
|
||||
if (boot_rest_done) return boot_rest_fail ? -1 : 0;
|
||||
boot_rest_done = 1;
|
||||
/* Заставку могли пропустить в первую же секунду — тогда звук ещё не
|
||||
* поднимался. Идемпотентно. */
|
||||
(void)pop_boot_sound();
|
||||
|
||||
(void)pop_sfx_init_finish(); /* дочитать эффекты (музыка уже играет) */
|
||||
boot_music_step();
|
||||
if (pop_kid_data_load("KID\\kid.ani") != 0) { /* кадры+seqtbl в EMM-странице */
|
||||
puts("kid.ani load failed\n");
|
||||
boot_rest_fail = 1;
|
||||
return -1;
|
||||
}
|
||||
boot_music_step();
|
||||
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
|
||||
boot_music_step();
|
||||
if (pop_kid_load(KID_PAGES) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
||||
puts("kid atlas load failed");
|
||||
boot_rest_fail = 1;
|
||||
return -1;
|
||||
}
|
||||
if (pop_level_load_num(FIRST_LEVEL) != 0) { /* сырой уровень в EMM-страницу */
|
||||
puts("level load failed");
|
||||
return -1;
|
||||
}
|
||||
pop_next_level = pop_current_level; /* current_level = next_level (load_lev_spr) */
|
||||
pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */
|
||||
pop_checkpoint = 0; /* init_game (seg003:37) */
|
||||
boot_music_step();
|
||||
|
||||
/* FONT.ATL нужен palette snapshot/dim; загрузка идёт ещё на чёрной
|
||||
* палитре и поэтому не открывает игроку старое содержимое VRAM. */
|
||||
if (pop_menu_init() != 0) {
|
||||
puts("menu font atlas load failed");
|
||||
return -1;
|
||||
}
|
||||
/* kid.pal + динамические slots tileset/Shadow — один логический
|
||||
* источник. pop_pal_game_load делает снимок ПОСЛЕ всех доплат. */
|
||||
pop_pal_game_load();
|
||||
/* Сохраняем ИМЕННО игровую палитру: после стартового title/intro она
|
||||
* понадобится как источник обычного fade-in. Для build-info экран
|
||||
* остаётся штатный яркий игровой фон. */
|
||||
if (!pop_settings.show_build_info) {
|
||||
pop_pal_apply(POP_PAL_DIM_BLACK);
|
||||
}
|
||||
|
||||
/* Стартовая комната до title НЕ НУЖНА: LEVEL_LOAD всё равно заново
|
||||
* выполняет pop_level_switch. Если нарисовать её сейчас в обе страницы,
|
||||
* она становится сохранённой VRAM и может мелькнуть перед первым title
|
||||
* при следующем запуске приложения. Единственное исключение — явно
|
||||
* включённый build-info: его фон по контракту является игровой сценой. */
|
||||
/* Стартовая комната до title НЕ НУЖНА: LEVEL_LOAD всё равно выполняет
|
||||
* pop_level_switch. Единственное исключение — явно включённый
|
||||
* build-info: его фон по контракту является игровой сценой, поэтому
|
||||
* только здесь читаются фон с уровнем и применяется игровая палитра. */
|
||||
if (pop_settings.show_build_info) {
|
||||
if (pop_bg_load(pop_level_type()) != 0 ||
|
||||
pop_level_load_num(FIRST_LEVEL) != 0) {
|
||||
puts("build-info scene load failed");
|
||||
boot_rest_fail = 1;
|
||||
return -1;
|
||||
}
|
||||
pop_guard_load();
|
||||
pop_pal_game_load();
|
||||
pop_start_level();
|
||||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||||
gfx_set_draw_page(0);
|
||||
pop_char_draw(POP_CH_KID);
|
||||
pop_char_fore(POP_CH_KID);
|
||||
}
|
||||
/* Звук включаем ПОСЛЕДНИМ действием инициализации — когда все атласы,
|
||||
* уровень и палитры уже прочитаны. Раньше нельзя: ESTEX уходит в диск
|
||||
* надолго, насос не успевает долить блок, и на слух это скрежет
|
||||
* (pop_sfx.h). Парная к pop_sfx_pause в pop_level_switch. */
|
||||
pop_sfx_start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1224,6 +1282,11 @@ int pop_level_switch(void) __banked
|
||||
uint8_t old_page;
|
||||
gfx_rect_t full;
|
||||
|
||||
/* РЕСУРСЫ МОГУТ БЫТЬ ЕЩЁ НЕ ПРОЧИТАНЫ. Кид, Тень и шрифт грузятся под
|
||||
* заставкой (см. шапку pop_boot), а её можно пропустить клавишей в
|
||||
* первую же секунду. Идемпотентно: со второго раза это проверка байта. */
|
||||
if (pop_boot_rest() != 0) return -1;
|
||||
|
||||
/* Граница FG0: повреждённый snapshot или старый внешний вызов не должен
|
||||
* даже пытаться открыть res2015.bin. Финальный маршрут 14 -> ENDING
|
||||
* добавит автомат приложения (FG11); пока просто оставляем уровень. */
|
||||
@@ -1368,6 +1431,10 @@ int pop_new_game_load(void) __banked
|
||||
gfx_wait_vsync();
|
||||
}
|
||||
|
||||
/* ДО pop_pal_game_load: она доплачивает слоты палитры по загруженным
|
||||
* наборам (Тень, страж, тайлсет), и звать её раньше их чтения нельзя. */
|
||||
if (pop_boot_rest() != 0) return -1;
|
||||
|
||||
pop_kid_hp_newgame();
|
||||
hitp_max = POP_START_HITP;
|
||||
pop_timer_new_game();
|
||||
|
||||
Reference in New Issue
Block a user