Катсцены: тайминги, часы и «время вышло» — по оригиналу; тумблер музыки

ТАЙМИНГИ. Треки заставок между уровнями длиннее самих сцен (27 — 10,7 с
против 2,6 с картинки), и оригинал их не обрывает: load_intro гасит экран
и возвращает управление, а звук продолжает играть поверх загрузки уровня
и первых секунд игры (seg001:690). Мы глушили CBL на выходе — музыка
обрывалась на полуслове. Больше не глушим.

«ВРЕМЯ ВЫШЛО». Оригинал (seg001:04D3) не зовёт ни одного init_*: на
экране пустая комната и ОСТАНОВИВШИЕСЯ часы (state 7, струйки песка
нет). Мы показывали готовую композицию с принцессой и полными часами —
противоположное по смыслу. Теперь это живая сцена без персонажей,
102 кадра, и трек 36 дослушивается под чёрным экраном, как в оригинале.

ПЕСОЧНЫЕ ЧАСЫ. Сцена «времени мало» (cutscene_12) шла статической
композицией, куда упаковщик запёк res953 — ПОЛНЫЕ часы. Отсюда и
наблюдение «чем меньше времени, тем полнее часы»: картинка была одна и та
же независимо от таймера. Сделана живой (cut_scene_12_short: принцесса
стоит правее, через два кадра оборачивается), часы берутся от
pre_hourglass_state. Заодно: при state 7 струйка песка не рисуется.

МУЗЫКА — СВОЙ ТУМБЛЕР. Settings -> MUSIC, Ctrl+M и статус в debug bar.
Флаг pop_mus_want отдельный от звукового: музыка у нас поток с диска, и
выключают её по другим причинам, чем эффекты; выключение обрывает текущий
трек, эффекты продолжают звучать. Строка-заглушка GAMEPLAY PROFILE
(«VANILLA ONLY») уступила место MUSIC — на десятую строку экрана не
хватает. Подписи тумблеров в debug bar сокращены до S:/M:/I:.

БАГИ. После меню персонаж оставался невидимым до первого движения:
снимок слота продолжал утверждать, что кадр на странице уже нарисован.
Общая pop_cd_forget() теперь зовётся после меню, QuickLoad и переворота
экрана. «QUICKLOAD» мигал через кадр — pop_status_show_now снимал бит
видимой страницы, а её успевало переписать восстановление комнаты.

Host-тесты: 15 наборов. Проверено в MAME: debug bar, выход из меню.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 09:28:36 +03:00
parent 0086ac80c2
commit 4a939e42f8
17 changed files with 257 additions and 78 deletions
+84 -27
View File
@@ -892,13 +892,19 @@ static uint8_t pre_hourglass_state(void)
* Статична только поза Princess; draw_princess_room_bg оригинала
* каждый логический кадр заново обновляет факелы, звёзды, корпус часов и
* одну из трёх фаз сыплющегося песка. */
static int pre_princess_animated(pop_pre_cutscene_t scene)
/* Живая комната принцессы БЕЗ актёрской механики: готовая композиция (или
* чистый фон, если picture == PV_ROOM_BARE), поверх неё пламя факелов,
* звёзды в окне и песочные часы заданного состояния. На этом держатся и
* сцены перед уровнями, и экран «время вышло» — разница только в картинке,
* состоянии часов и длине.
*
* hg_state: 0 — часов нет, 7 — время вышло (пустые, без струйки песка). */
#define PV_ROOM_BARE 0xFF
static int pre_room_animated(uint8_t picture, uint8_t state, uint8_t frames)
{
atlas_t hourglass, torch;
gfx_rect_t full;
uint8_t picture = (uint8_t)(scene == POP_PRE_4 ?
INTRO_PV_LYING : INTRO_PV_STAND);
uint8_t state = pre_hourglass_state();
uint8_t torch_left = 1, torch_right = 6, sand = 0, frame;
uint8_t star_phase[6] = { 1, 1, 1, 1, 1, 1 };
intro_skip_t skip;
@@ -908,8 +914,12 @@ static int pre_princess_animated(pop_pre_cutscene_t scene)
intro_commit_palette();
/* Обе shadow-страницы содержат чистую статическую сцену. Динамика ниже
* рисуется GFX_BANK_SPRITE и потому не оседает в этом источнике. */
if (intro_draw_page(picture, 0) != 0 ||
intro_draw_page(picture, 1) != 0) return -1;
if (picture == PV_ROOM_BARE) {
if (intro_pv_draw_base() != 0) return -1;
} else if (intro_draw_page(picture, 0) != 0 ||
intro_draw_page(picture, 1) != 0) {
return -1;
}
{
uint8_t tbl[ARC_PV_COUNT * POP_ARC_REC];
pop_arc_t arc;
@@ -927,7 +937,7 @@ static int pre_princess_animated(pop_pre_cutscene_t scene)
result = 0;
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
intro_skip_begin(&skip);
for (frame = 0; frame < 26; frame++) {
for (frame = 0; frame < frames; frame++) {
uint8_t back, hold, star;
if (intro_skip_requested(&skip)) { result = 1; break; }
@@ -938,8 +948,13 @@ static int pre_princess_animated(pop_pre_cutscene_t scene)
pv_blit_floor(&torch, torch_left, 93, 116);
pv_blit_floor(&torch, torch_right, 211, 116);
pv_draw_stars(star_phase);
pv_blit_floor(&hourglass, state, 152, 168);
pv_blit_floor(&hourglass, (uint8_t)(8 + sand), 160, 164);
if (state) {
pv_blit_floor(&hourglass, state, 152, 168);
/* Струйка песка есть, пока часы идут: при state 7 время уже
* вышло (draw_hourglass, sandflow = -1). */
if (state < 7)
pv_blit_floor(&hourglass, (uint8_t)(8 + sand), 160, 164);
}
pv_blit_floor(&hourglass, 0, 240, 167); /* передняя колонна */
gfx_wait_vsync();
gfx_set_visible_page(back);
@@ -1042,7 +1057,7 @@ static void intro_restore_game_palette(void)
* 17..29 в a2. Номер кадра при этом остаётся исходным image-id. */
#define CUT_PV2_SPLIT 17
/* Один логический кадр катсцены = 5 vsync, как у остальных PV-сцен
* (cutscene_frame_time SDLPoP; см. pre_princess_animated). */
* (cutscene_frame_time SDLPoP; см. pre_room_animated). */
#define CUT_FRAME_VSYNC 5
static atlas_t cut_pv1, cut_pv2a, cut_pv2b, cut_hg, cut_torch;
@@ -1237,10 +1252,13 @@ static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
pv_draw_stars(cut_star_phase);
/* hourglass_state == 0 — часов в комнате нет вовсе (reset_cutscene
* SDLPoP ставит state=0 и sandflow=-1). Так идёт финальная встреча
* с принцессой: время уже не считается, это победа. */
* с принцессой: время уже не считается, это победа.
* state 7 — время ВЫШЛО: часы стоят пустыми, и струйки песка в них
* тоже нет (draw_hourglass: `if (hourglass_state >= 7) return;`). */
if (cut_hg_state) {
pv_blit_floor(&cut_hg, cut_hg_state, 152, 168);
pv_blit_floor(&cut_hg, (uint8_t)(8 + cut_sand), 160, 164);
if (cut_hg_state < 7)
pv_blit_floor(&cut_hg, (uint8_t)(8 + cut_sand), 160, 164);
}
cut_blit_princess(&Guard, &pr_fr);
if (kid_visible) cut_blit_kid(&Kid, &kid_fr);
@@ -1342,6 +1360,27 @@ static int cut_scene_9(void)
return 0;
}
/* cutscene_12 (seg001:0525), ветка «времени мало»: принцесса стоит правее
* обычного, через два кадра оборачивается — и всё. Живой её делает та же
* машинка, что сцены 8/9: часы здесь обязаны показывать ОСТАВШЕЕСЯ время,
* а не запечённую в композицию картинку. */
static int cut_scene_12_short(void)
{
uint8_t aborted;
if (cut_begin(pre_hourglass_state()) != 0) return -1;
/* init_princess_x156 (seg001:0230) = init_princess + x = 156. */
cut_shad_init(156, 166, 94);
pop_pal_fade_in(INTRO_FADE);
aborted = cut_run(2, 0);
if (!aborted) {
cut_shad_seq(98); /* princess turn around [PV1] */
aborted = cut_run(24, 0);
}
cut_end(aborted);
return 0;
}
/* end_sequence_anim (seg001:041C): Кид вбегает к принцессе, она
* поворачивается и обнимает его; следом появляется мышь. */
static int cut_ending(void)
@@ -1522,7 +1561,15 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked
const pop_cs_cmd_t *script = 0;
pop_sfx_pause();
/* Музыка сцены — как в оригинале: 27 для 2/4/6/12 (seg001:401),
/* МУЗЫКА ПЕРЕЖИВАЕТ СЦЕНУ. Треки заставок между уровнями длиннее самих
* сцен (27 — 10,7 с против 2,6 с картинки), и оригинал их не обрывает:
* load_intro просто гасит экран и возвращает управление, а звук
* продолжает играть уже поверх загрузки уровня и первых секунд игры
* (seg001:690). Мы глушили CBL на выходе — музыка обрывалась на
* полуслове. Теперь не глушим: доиграет сама, а следующая загрузка
* трека всё равно освободит слот.
*
* Какой именно трек — как в оригинале: 27 для 2/4/6/12 (seg001:401),
* 35 для 8/9 (seg001:298), 40 для короткой «времени мало» (seg001:388).
* Экран в этот момент чёрный, поэтому читаем трек целиком: пауза на
* диск здесь никому не мешает. */
@@ -1537,20 +1584,22 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked
}
}
if (scene == POP_PRE_2_6 || scene == POP_PRE_4 || scene == POP_PRE_12) {
if (pre_princess_animated(scene) >= 0) {
pop_music_free();
pop_sfx_pause();
if (pre_room_animated((uint8_t)(scene == POP_PRE_4 ? INTRO_PV_LYING
: INTRO_PV_STAND),
pre_hourglass_state(), 26) >= 0) {
intro_restore_game_palette();
return;
}
/* Неполный HDD: статический fallback сохраняет корректные 130 тиков. */
}
if (scene == POP_PRE_12_SHORT && cut_scene_12_short() == 0) {
intro_restore_game_palette();
return;
}
/* Сцены 8/9 — живая анимация мыши и принцессы (cutscene_8/cutscene_9). */
if (scene == POP_PRE_8 || scene == POP_PRE_9) {
int done = (scene == POP_PRE_8) ? cut_scene_8() : cut_scene_9();
if (done == 0) {
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette();
return;
}
@@ -1560,27 +1609,35 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked
else if (scene == POP_PRE_8) script = pre_8;
else if (scene == POP_PRE_9) script = pre_9;
else if (scene == POP_PRE_12_SHORT) script = pre_12_short;
if (!script) {
pop_music_free();
pop_sfx_pause();
return;
}
if (!script) return;
intro_run(script);
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette();
}
void pop_time_expired_show(void) __banked
{
pop_sfx_pause();
/* time_expired (seg001:365) начинается с play_sound(sound_36). */
/* time_expired (seg001:04D3) начинается с play_sound(sound_36). */
if (pop_music_load(36) == 0) {
(void)pop_sfx_start();
pop_music_play();
}
intro_run(time_expired_script);
/* ПРИНЦЕССЫ ЗДЕСЬ НЕТ. Оригинал не зовёт ни одного init_*: экран —
* это пустая комната и ОСТАНОВИВШИЕСЯ часы (state 7, струйки песка
* нет). Мы показывали готовую композицию с принцессой и полными
* часами — то есть ровно противоположное по смыслу.
* Длина — 2 + 100 кадров cutscene, как в оригинале. */
if (pre_room_animated(PV_ROOM_BARE, 7, 102) < 0)
intro_run(time_expired_script); /* нет ресурсов — старый путь */
/* Оригинал ждёт КОНЦА ТРЕКА уже под чёрным экраном (seg001:374). */
{
intro_skip_t tail;
intro_skip_begin(&tail);
while (pop_music_busy() && !intro_skip_requested(&tail))
pop_wait_edge();
}
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette();