From c6828c0ad1eaa57d1b9729c6543f2d750e754bb3 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 20 Aug 2026 21:03:16 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BE=D1=80=D0=BE=D1=82=D0=B0=20PoP:=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=B3=D0=B5=D0=B9=D1=82=20=D1=81=D0=BB=D1=8B=D1=88=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8,=20=D0=B7=D0=B2=D1=83=D0=BA=20=C2=AB?= =?UTF-8?q?=D1=80=D0=B5=D1=88=D1=91=D1=82=D0=BA=D0=B0=20=D0=B2=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BB=D0=B0=C2=BB,=20playsound=20=D0=BA=D0=BD=D0=BE?= =?UTF-8?q?=D0=BF=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверка на сцене 9/9 (кнопка 1,8; челюсти 1,2; ворота — в комнате 4, тайл 1,9) вскрыла три расхождения. 1. Гейт слышимости стоял как «ворота в текущей комнате», а у оригинала (play_door_sound_if_visible, seg007:1239) слышны ещё и ворота в комнате СЛЕВА, если они в колонке 9; и НЕ слышны в колонке 9 своей комнаты; плюс особый случай «уровень 3, комната 2». Сцена 9/9 — ровно первый пункт, поэтому спуск решётки молчал. Подъём совпадал, потому что звук открытия идёт без гейта — эта асимметрия и была подсказкой. 2. Потерян звук 7 «решётка встала»: gate_stop (seg007:05E3) зовётся из трёх мест animate_door и каждый раз играет его через гейт. У нас во всех трёх стояло только type = -1. 3. У trigger_button оригинала есть параметр playsound, нулевой в трёх местах (вход на уровень, выход Джаффара, зелье «открыть»). Добавлен. Щелчок кнопки слышен через раз — это НЕ баг: prio 0x66 против 0x10 у челюстей, а укус занимает 465 мс из цикла 1229 мс (замерено). Разбор с цифрами — sound_plan.md §12. Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/sound_plan.md | 62 +++++++++++++++++++++ applications/PoP/roomtest/guards.c | 3 +- applications/PoP/roomtest/pop_map.c | 8 +-- applications/PoP/roomtest/pop_trob.c | 67 +++++++++++++++++++---- applications/PoP/roomtest/pop_trob.h | 3 +- applications/PoP/roomtest/roomtest.c | 2 +- applications/PoP/roomtest/roomtest_cold.c | 3 +- 7 files changed, 129 insertions(+), 19 deletions(-) diff --git a/applications/PoP/docs/sound_plan.md b/applications/PoP/docs/sound_plan.md index 27c92d9..bd38d18 100644 --- a/applications/PoP/docs/sound_plan.md +++ b/applications/PoP/docs/sound_plan.md @@ -579,3 +579,65 @@ play_next_sound() seg000:1304 — раз в кадр решает, зап Цена: резидент +~250 Б (таблицы + логика), куча ужалась с 347 до 134 Б — довод в пользу давно назревшей реорганизации базовой памяти. + +## 12. Ворота: гейт слышимости и «решётка встала» (2026-08-20) + +Проверка на сцене, которую предложил пользователь — уровень 9, комната 9: +кнопка (1,8), челюсти (1,2), а ворота **в комнате 4, тайл (1,9)**, то есть +в комнате СЛЕВА. Нашлись три расхождения сразу. + +### 12.1 Гейт слышимости был неверный + +У нас стояло `audible = (room == cur_room)`. У оригинала +(`play_door_sound_if_visible`, seg007:1239) правило другое: + +- ворота в комнате СЛЕВА и в колонке 9 — СЛЫШНЫ (створка видна в шве); +- ворота в отрисованной комнате и НЕ в колонке 9 — слышны; +- особый случай: уровень 3, комната 2 — слышны всегда. + +Сцена 9/9 попадает ровно в первый пункт, поэтому спуск решётки у нас +молчал. Подъём при этом совпадал с оригиналом — потому что звук открытия +(5) идёт БЕЗ гейта (seg007:386, прямой `play_sound`). Эта асимметрия и была +подсказкой. + +Взят вариант под `FIX_GATE_SOUNDS` (условия через ИЛИ): в config.h SDLPoP +он определён безусловно. + +### 12.2 Потерян звук «решётка встала» (7) + +`gate_stop()` (seg007:05E3) зовётся из ТРЁХ мест `animate_door` и каждый раз +играет звук 7 через гейт слышимости: конец закрытия, открытие насовсем и +ветка «уже 0xFF». У нас во всех трёх стояло только `*type = -1` без звука. +Добавлено. Лязг после ОБЫЧНОГО открытия (seg007:395) остаётся без гейта — +там оригинал зовёт `play_sound` напрямую. + +### 12.3 Кнопка: у оригинала есть параметр playsound + +`trigger_button(playsound, ...)` — в трёх местах он нулевой: вход на уровень +(seg003:170), выход Джаффара (seg002:520) и зелье «открыть» (seg006:1890, у +нас не портировано). Мы играли щелчок всегда. Добавлен параметр `snd`. + +### 12.4 Почему щелчок кнопки слышно через раз — это НЕ баг + +Бюджет сцены 9/9 (длительности после пересчёта на 10 937,5 Гц): + +| звук | длительность | prio | +|---|---:|---:| +| челюсти (47) | 465 мс | 0x10 | +| решётка вниз (4) | 97 мс | 0x32 | +| решётка вверх (5) | 123 мс | 0x37 | +| решётка встала (7) | 75 мс | 0x30 | +| кнопка (3) | 106 мс | 0x66 | + +Цикл челюстей — 15 кадров = 1229 мс (замерено брейкпоинтом на номинации: +25 805 000 тактов между укусами). Значит укус занимает 465 мс, пауза 764 мс. +Кнопка (prio 0x66) перебить челюсти не может (0x66 > 0x10), поэтому слышна +только если нажатие попало в паузу — примерно в 6 случаях из 10. +Подтверждено пользователем на живой сцене. + +### 12.5 Грабли сцены + +Если игра стартует ПРЯМО в комнате с челюстями, они не заводятся сами: +нужно сходить Кидом на левую кнопку и вернуться. Это поведение оригинала +(trob челюстей создаётся событием), а не наш дефект — учитывать при +постановке автотестов. diff --git a/applications/PoP/roomtest/guards.c b/applications/PoP/roomtest/guards.c index 6724ce9..c8f4c73 100644 --- a/applications/PoP/roomtest/guards.c +++ b/applications/PoP/roomtest/guards.c @@ -406,7 +406,8 @@ void pop_jaffar_exit(void) __banked m = pop_trob_modif(JAFFAR_EXIT_ROOM); pop_trigger_button(JAFFAR_EXIT_ROOM, JAFFAR_EXIT_TILEPOS, pop_level_tile(JAFFAR_EXIT_ROOM, JAFFAR_EXIT_TILEPOS), - m ? m[JAFFAR_EXIT_TILEPOS] : 0); + m ? m[JAFFAR_EXIT_TILEPOS] : 0, + 0 /* seg002:520 — playsound=0 */); } /* on_guard_killed (seg006:1927) — соперник только что потерял последнее HP. diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index c84bd04..2395cf3 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -880,7 +880,7 @@ static uint8_t check_grab_run_jump(void) if (grab_col >= 0 && grab_col <= 9 && grab_row >= 0 && grab_row <= 2) { uint8_t tp = (uint8_t)(grab_row * 10 + grab_col); if (grab_tile == TILE_OPENER || grab_tile == TILE_CLOSER) - pop_trigger_button(g_room, tp, grab_tile, pop_trob_modif(g_room)[tp]); + pop_trigger_button(g_room, tp, grab_tile, pop_trob_modif(g_room)[tp], 1); else if (grab_tile == TILE_LOOSE) { is_guard_notice = 1; make_loose_fall(tp, 1); @@ -2318,7 +2318,7 @@ static void died_on_button(uint8_t room, uint8_t tp, uint8_t code) if (tp % 10 < 9) pop_set_redraw((uint8_t)(tp + 1), POP_RD_FLOOR, 2); } - pop_trigger_button(room, tp, button_type, modifier); + pop_trigger_button(room, tp, button_type, modifier, 1); } /* check_press (seg006:0EC8, упрощ.): Kid стоит на loose → make_loose_fall; @@ -2373,7 +2373,7 @@ static void check_press(void) /* seg006:1707 — ЖИВОЙ жмёт кнопку на 5 кадров, МЁРТВЫЙ ломает её * насовсем. `alive < 0` = жив (соглашение оригинала). */ if (Char.alive < 0) - pop_trigger_button(btn_room, tp, t, pop_trob_modif(btn_room)[tp]); + pop_trigger_button(btn_room, tp, t, pop_trob_modif(btn_room)[tp], 1); else died_on_button(btn_room, tp, t); } @@ -2861,7 +2861,7 @@ void pop_loose_tick(void) __banked if (tt == TILE_OPENER || tt == TILE_CLOSER) pop_trigger_button(g_room, pos, (uint8_t)(tt == TILE_OPENER ? TILE_DEBRIS : tt), - pop_trob_modif(g_room)[pos]); + pop_trob_modif(g_room)[pos], 1); /* На факеле — отдельный тайл «факел с щебнем» (seg007:1067). */ g_fg[pos] = (uint8_t)((tt == TILE_TORCH || tt == TILE_TORCH_DEBRIS) ? TILE_TORCH_DEBRIS : TILE_DEBRIS); diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index d6adaf5..48dc19d 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -455,7 +455,8 @@ static void do_trigger_list(uint8_t index, uint8_t button_type) } void pop_trigger_button(uint8_t room, uint8_t tilepos, - uint8_t button_type, uint8_t modifier) __banked + uint8_t button_type, uint8_t modifier, + uint8_t snd) __banked { uint8_t link_timer = get_doorlink_timer(modifier); if (link_timer != 0x1F) { /* не заклинено */ @@ -466,8 +467,14 @@ void pop_trigger_button(uint8_t room, uint8_t tilepos, if (link_timer < 2) { pop_add_trob(room, tilepos, 1); /* кнопка нажимается */ /* seg007:766: щелчок кнопки — шум, и страж оборачивается - * (флаг обязателен даже без звука, BUG-GUARD-DEAF-1). */ - pop_sfx_play(3); + * (флаг обязателен даже без звука, BUG-GUARD-DEAF-1). + * + * Звук НЕ у всех вызовов: у оригинала первый параметр playsound, + * и в трёх местах он нулевой — вход на уровень (seg003:170), + * выход Джаффара (seg002:520) и зелье «открыть» (seg006:1890, + * у нас пока не портировано). Там кнопка нажимается служебно, + * щёлкать нечему. */ + if (snd) pop_sfx_play(3); is_guard_notice = 1; } do_trigger_list(modifier, button_type); @@ -558,6 +565,30 @@ static void animate_leveldoor(uint8_t *m, int8_t *type, uint8_t cur_room) static const uint8_t gate_close_speeds[9] = {0, 0, 0, 20, 40, 60, 80, 100, 120}; static const int8_t door_delta[3] = {-1, 4, 4}; +/* play_door_sound_if_visible (seg007:1239): СЛЫШНА ли решётка. + * + * Правило не «решётка в текущей комнате», как можно подумать, а такое: + * - решётка в комнате СЛЕВА и стоит в колонке 9 — слышна (это тайл вплотную + * к левому краю экрана, игрок фактически видит её створку в шве); + * - решётка в ОТРИСОВАННОЙ комнате и НЕ в колонке 9 — слышна (колонка 9 + * уезжает за правый край); + * - особый случай оригинала: уровень 3, комната 2 — слышна всегда. + * Всё остальное молчит, иначе на 13-м уровне скрипели бы решётки со всей + * карты. + * + * Берём вариант ПОД FIX_GATE_SOUNDS (условия через ИЛИ, а не тернарником): + * в SDLPoP этот фикс включён по умолчанию, а сравниваемся мы именно с ним. + * Отличие только в вырожденном случае room_L == cur_room. */ +static uint8_t door_audible(uint8_t room, uint8_t tp, + uint8_t room_L, uint8_t cur_room) +{ + uint8_t col = TP_COL[tp]; + if (pop_current_level == 3 && room == 2) return 1; + if (room == room_L && col == 9) return 1; + if (room == cur_room && col != 9) return 1; + return 0; +} + static void animate_door(uint8_t *m, int8_t *type, uint8_t audible) { int8_t anim_type = *type; @@ -573,20 +604,30 @@ static void animate_door(uint8_t *m, int8_t *type, uint8_t audible) } else if (mod != 0xFF) { mod = (uint8_t)(mod + door_delta[anim_type]); if (anim_type == 0) { /* закрытие */ - if (mod == 0) *type = -1; /* gate_stop */ - /* seg007:376: скрип закрывающейся решётки — раз в четыре - * шага модификатора и ТОЛЬКО если комната на экране - * (play_door_sound_if_visible), иначе на 13-м уровне - * скрипели бы решётки из соседних комнат. */ - else if (mod < 188 && (mod & 3) == 3 && audible) pop_sfx_play(4); + /* seg007:376: скрип закрывающейся решётки — раз в четыре шага + * модификатора; лязг «встала» (7) — в конце. Оба через + * гейт слышимости (gate_stop, seg007:05E3, тоже через него). */ + if (mod != 0) { + if (mod < 188 && (mod & 3) == 3 && audible) pop_sfx_play(4); + } else { + *type = -1; + if (audible) pop_sfx_play(7); /* gate_stop */ + } } else { /* открытие */ if (mod >= 188) { + /* Лязг после ОБЫЧНОГО открытия оригинал даёт без гейта + * (seg007:395 — прямой play_sound), а после открытия + * насовсем — через gate_stop, то есть с гейтом. */ if (anim_type < 2) { mod = 238; *type = 0; pop_sfx_play(7); } - else { mod = 0xFF; *type = -1; } /* насовсем */ + else { + mod = 0xFF; *type = -1; + if (audible) pop_sfx_play(7); + } } else if ((mod & 7) == 0) pop_sfx_play(5); /* seg007:386 */ } } else { *type = -1; + if (audible) pop_sfx_play(7); /* gate_stop, seg007:404 */ } } *m = mod; @@ -668,6 +709,10 @@ void pop_process_trobs(uint8_t cur_room) __banked { uint8_t mod_room = 0; /* 0 = «ещё не брали» (комнаты 1..24) */ uint8_t *mod = 0; + /* Сосед СЛЕВА нужен гейту слышимости решёток (door_audible). Берём один + * раз на проход: pop_room_link маппит страницу уровня, а решёток в + * списке может быть несколько. */ + uint8_t room_L = pop_room_link(cur_room, 0 /* L */); for (i = 0; i < trobs_count; i++) { uint8_t room = trobs[i].room; @@ -689,7 +734,7 @@ void pop_process_trobs(uint8_t cur_room) __banked animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */ break; case TILE_GATE: - animate_door(&mod[tp], &type, (uint8_t)(room == cur_room)); + animate_door(&mod[tp], &type, door_audible(room, tp, room_L, cur_room)); break; case TILE_LOOSE: animate_loose(&mod[tp], &type, room, tp, cur_room); diff --git a/applications/PoP/roomtest/pop_trob.h b/applications/PoP/roomtest/pop_trob.h index 8264e7a..9a67028 100644 --- a/applications/PoP/roomtest/pop_trob.h +++ b/applications/PoP/roomtest/pop_trob.h @@ -47,7 +47,8 @@ void pop_process_trobs(uint8_t cur_room) __banked; * room/tilepos — кнопка; button_type — её тайл-код; modifier — её modif * (= индекс в LINKLOC). Звать из check_press (pop_map). */ void pop_trigger_button(uint8_t room, uint8_t tilepos, - uint8_t button_type, uint8_t modifier) __banked; + uint8_t button_type, uint8_t modifier, + uint8_t snd) __banked; /* Есть ли активные trob в комнате room? Для перерисовки ворот соседа через * шов (roomtest не дёргает шов, пока сосед не анимируется). */ diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 3ff499f..1891940 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -562,7 +562,7 @@ int main(void) if (tt == TILE_OPENER || tt == TILE_CLOSER) pop_trigger_button(lroom, lpos, (uint8_t)(tt == TILE_OPENER ? TILE_DEBRIS : tt), - pop_trob_modif(lroom)[lpos]); + pop_trob_modif(lroom)[lpos], 1); /* На факеле остаётся отдельный тайл «факел с щебнем» * (seg007:1067), а не обычный щебень. */ pop_level_set_tile(lroom, lpos, diff --git a/applications/PoP/roomtest/roomtest_cold.c b/applications/PoP/roomtest/roomtest_cold.c index 53404c1..4777264 100644 --- a/applications/PoP/roomtest/roomtest_cold.c +++ b/applications/PoP/roomtest/roomtest_cold.c @@ -163,7 +163,8 @@ void pop_start_level(void) __banked * col2, row0 её же типом и модификатором (в оригинале и то и другое * берётся из curr_* после get_tile). */ uint8_t tp = 2; /* row0*10 + col2 */ - pop_trigger_button(5, tp, pop_level_tile(5, tp), pop_trob_modif(5)[tp]); + pop_trigger_button(5, tp, pop_level_tile(5, tp), pop_trob_modif(5)[tp], + 0 /* seg003:170 — playsound=0 */); seq = SEQ_FALL; } else if (entry_pose == ENTRY_POSE_RUN) { seq = SEQ_RUN;