diff --git a/applications/PoP/roomtest/BUGS_CLOSED.md b/applications/PoP/roomtest/BUGS_CLOSED.md index cc2be16..887daf4 100644 --- a/applications/PoP/roomtest/BUGS_CLOSED.md +++ b/applications/PoP/roomtest/BUGS_CLOSED.md @@ -13,6 +13,69 @@ --- + +## GRAB-BELOW-ROOM. Зацеп за кромку НИЖНЕГО ряда при пролёте вниз не работал вовсе + +**Наблюдение (пользователь, 2026-08-12).** Уровень 7, комната 14: по сценарию +уровня Кид спускается с ряда 0 на ряд 2 через зацеп — виснет на кромке кнопки +`(0,2)`, отпускает и в падении цепляется за кромку `(2,2)`. «Пытался много +раз — Кид летит и не цепляется ни за `(2,2)` комнаты 14, ни за `(1,2)` комнаты +15». При этом вис на `(2,2)` с последующим перелётом на `(1,2)` **работает +стабильно** — то есть сам зацеп в падении жив. + +**Корень.** `do_fall` (порт seg005:0030) не давал ряду персонажа выйти за +нижнюю границу комнаты: + +```c +else if (Char.curr_row < 2) + inc_curr_row(); /* «следующий ряд ВНУТРИ комнаты» */ +``` + +В оригинале `inc_curr_row()` безусловный, и `curr_row` доходит до **3** — ряда +«за нижней кромкой»; `get_tile` для него уходит по `links.down` в комнату снизу +(`find_room_of_tile`, seg006:005D). Ряд 3 нужен не сам по себе, а потому, что +`check_grab` целится в тайл **спереди-сверху**, то есть в ряд `curr_row − 1`: +только при `curr_row == 3` целью становится ряд 2 своей комнаты. + +С залипшим рядом 2 происходило вот что: пока Кид летел вдоль нижнего ряда, +`do_fall` каждый кадр уходил в ветку «достиг `y_land`», где `check_grab` не +вызывается вовсе. К моменту, когда переход в комнату снизу открывал окно +зацепа заново (там цель — «ряд −1», уже реализованный `g_above`), скорость +падения успевала дойти до `fall_y = 33`, а `check_grab` требует `< 32`. Окно +закрывалось **по скорости** — отсюда «летит и не цепляется никак». + +Второй дефект, найденный там же: ветка `action == ACT_MIDAIR` в `check_action` +стояла ПУСТОЙ заглушкой с комментарием «frames 102..105: check_grab — K4». В +оригинале (seg006:0619) это первые четыре кадра падения, где `fall_y` ещё не +разгоняется (`fall_accel` работает только в `ACT_FREEFALL`), — самое широкое +окно зацепа. У нас его не было. + +**Фикс** (`pop_map.c`): `inc_curr_row()` теперь безусловный внутри ветки +`curr_row <= 2` (то есть 2 → 3 разрешён), а сама ветка «достиг `y_land`» +гейтится по `curr_row <= 2` — при ряде 3 ни `in_wall`, ни `land` звать нельзя +(тайлов своей комнаты там нет, `get_tile` отдаёт `WALL`-сентинел; до +`y_land[4] = 244` дело не доходит, `check_leave_below` переводит в комнату +снизу на `y >= 211`). Плюс восстановлена ветка `ACT_MIDAIR` с кадрами 102..105. + +**Подтверждение.** +- Хост-тест `tests-host/t_grab.c` → `grab_below_room_edge_window_exists`: + сцена комнаты 14 + переход в 15 по `pop_fell_out` (как в главном цикле). + Карта исходов по фазе X и задержке Shift **до** фикса — сплошные точки (ни + одного зацепа), **после** — окно из 8 фаз X при задержках 0..5 кадров. + Тест проверяет и играбельность: зацеп обязан удаваться при самом + естественном вводе (отпустил вис и сразу зажал Shift). +- Живьём в MAME (мост, 2026-08-12): Кид повис на кромке `(2,2)` — в памяти + `frame=91 y=55 row=0 room=15 act=6`, на экране держится за правый край плиты. + +**Грабли теста, стоившие часа.** Первая версия сцены засчитывала как успех +ЛЮБОЙ вис — а Кид на первых кадрах падения цепляется обратно за ту же верхнюю +кромку, и тест проходил даже на сломанном коде. Признак цели пришлось делать +позиционным (`went_below` + `curr_row == 0` после перехода). Вторая ловушка: +кадры спуска `seq_68` идут с `action == 3`, то есть «дождаться падения» по +одному лишь `action` нельзя — ждать надо ВИСА, и только потом отпускать Shift. + +--- + ## BUG-CHEAT-FIGHT-1. Чит `+`/`−` в бою: Кид остаётся в режиме боя и теряет управление diff --git a/applications/PoP/roomtest/BUGS_OPEN.md b/applications/PoP/roomtest/BUGS_OPEN.md index 6cf1f88..6ca2b28 100644 --- a/applications/PoP/roomtest/BUGS_OPEN.md +++ b/applications/PoP/roomtest/BUGS_OPEN.md @@ -22,6 +22,41 @@ | [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт | | [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводим: ни сценарием, ни попиксельной подгонкой X не поднимается | | [BUG-CHOMP-JUMP-1](#bug-chomp-jump-1) | прыжок с места вплотную к чомперу: кадр с отступом назад | **низкий** | маловоспроизводим: на повторе не поднялся; на пререлиз | +| [GRAB-KBD-TIMING](#grab-kbd-timing) | зацеп в падении срабатывает менее стабильно, чем в оригинале | **после всех уровней** | открыт: подозрение на клавиатурный модуль, не на физику | + +--- + + +## GRAB-KBD-TIMING. Зацеп в падении срабатывает НЕ ТАК СТАБИЛЬНО, как в оригинале — РАЗБОР ПОСЛЕ ВСЕХ УРОВНЕЙ + +> **Решение пользователя (2026-08-12): отложено до готовности всех уровней.** +> Механика работает, это вопрос ощущения, а не проходимости. + +**Наблюдение (пользователь).** Вход на уровень 7: Кид влетает в комнату сверху +и, пролетая мимо края пола, МОЖЕТ за него зацепиться — но получается заметно +реже, чем в оригинале. «Похоже, это проблемы нашего клавиатурного модуля». + +**Что уже сделано и почему это НЕ закрывает вопрос.** В ходе разбора +[GRAB-BELOW-ROOM](BUGS_CLOSED.md#grab-below-room) восстановлена ветка +`ACT_MIDAIR` в `check_action` (кадры 102..105 — начало падения, где `fall_y` ещё +не разогнан): раньше на этих четырёх кадрах `check_grab` не звался вовсе, то +есть окно зацепа было короче оригинального. Это должно было добавить +стабильности, но саму гипотезу про клавиатуру не проверяет. + +**Куда смотреть, когда дойдут руки.** Порядок именно такой — сначала +измерить, потом чинить: + +1. Снять, ЧТО видит движок в момент падения: `pop_ctrl_shift_held()` покадрово + (Shift зажат заранее — читается ли он каждый кадр, или FIFO SIO отдаёт + состояние с пропусками; см. memory `kbd_raw_fifo_drain`, + `kbd_overrun_wipe_modifiers` — оба прошлых бага были именно про модификаторы). +2. Сверить с оригиналом ширину окна по кадрам: у нас `check_grab` доступен на + 102..105 (midair) + весь `ACT_FREEFALL`, как в seg006 — расхождений в коде + быть не должно, значит расхождение либо во вводе, либо в темпе игры + ([L1-SPEED](TASKS_OPEN.md#l1-speed): игра идёт на ~39 % быстрее оригинала, + а окно зацепа отмеряется В КАДРАХ — то есть по времени оно у нас короче). +3. Хост-тест `t_grab.c` уже умеет мерить ширину окна (карта исходов по фазе X и + задержке нажатия) — на нём и проверять «стало стабильнее». --- diff --git a/applications/PoP/roomtest/NEXT_SESSION.md b/applications/PoP/roomtest/NEXT_SESSION.md index 8577cdf..ff193cf 100644 --- a/applications/PoP/roomtest/NEXT_SESSION.md +++ b/applications/PoP/roomtest/NEXT_SESSION.md @@ -1,4 +1,4 @@ -# Точка входа для следующей сессии (обновлено 2026-08-11, вечер) +# Точка входа для следующей сессии (обновлено 2026-08-12) Файл для старта с чистого контекста: где всё стоит, что делать первым, какие грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам: @@ -6,22 +6,24 @@ --- -## 1. СЛЕДУЮЩАЯ ЗАДАЧА: зацеп в ПАДЕНИИ (вход на уровень 7) +## 1. СЛЕДУЮЩАЯ ЗАДАЧА: зелье МЕДЛЕННОГО ПАДЕНИЯ (уровень 7, комната 1) -Уровень 6 пройден и принят: спецсобытие с тенью работает, переход на уровень -7 падением работает. Но **вход на 7-й ломается на механике**: Кид влетает в -комнату сверху и, пролетая мимо края пола, ДОЛЖЕН иметь возможность -зацепиться за него руками. Похоже, этого у нас нет вовсе — проверить и -реализовать. +План готов и лежит в [`TASKS_OPEN.md#l7-feather`](TASKS_OPEN.md#l7-feather) — +шесть шагов с уже выписанными константами оригинала (ускорение 1 / потолок 4, +`SEQ_JMP_IF_FEATHER` → ветки `stepfloat`/`bumpfloat`, зелёная вспышка, зелёные +пузырьки, таймер 18,75 с). Начинать прямо с шага 1. -Куда смотреть: `check_grab` / ветка `action == actions_3_in_midair` в -seg005-seg006 SDLPoP (у нас `POP_ENABLE_JUMP_GRAB` и `check_grab_run_jump` -в `pop_map.c` — это ДРУГОЙ случай, зацеп из прыжка). Начинать, как всегда, -с чтения оригинала, а не с догадок. +**Зацеп в падении — закрыт 2026-08-12** +([GRAB-BELOW-ROOM](BUGS_CLOSED.md#grab-below-room)). Оказалось, что зацеп +из падения был жив, а сломан был спуск через НИЖНЮЮ кромку комнаты (уровень 7, +комната 14): `do_fall` не пускал ряд персонажа до 3, и `check_grab` на этом +отрезке не звался ни разу. Плюс восстановлена ветка `ACT_MIDAIR` (кадры +102..105 — начало падения). Проверено хост-тестом `t_grab` и живьём в MAME. -Опора: вход падением уже портирован — `FALLING_ENTRY_LEVEL/ROOM` в -`pop_guard.h` + ветка в `pop_start_level` (`roomtest.c`), Кид появляется в -комнате 17 и экран сразу переводится на комнату под ней. +Осталась ощущенческая часть — [GRAB-KBD-TIMING](BUGS_OPEN.md#grab-kbd-timing): +зацеп при входе на уровень 7 срабатывает менее стабильно, чем в оригинале +(подозрение на клавиатурный модуль и на общий темп игры, `L1-SPEED`). +**Решение пользователя: разбирать после того, как будут готовы все уровни.** ## 2. Состояние репозитория diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index c85b6d7..3875f75 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -87,6 +87,56 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS. ## P0 — делаем сейчас +### L7-FEATHER. Зелье МЕДЛЕННОГО ПАДЕНИЯ (уровень 7, комната 1) + +**Задача (пользователь, 2026-08-12): составить план — здесь он и есть; код +следующим заходом.** + +Зелье на `(2,8)` комнаты 1 уровня 7 (сверено по `res2007.bin`: modifier 3, то +есть `potion_type = 3` — «slow fall»). Сейчас `pop_proc_get_object` +(`pop_map.c`) для типов 3/4/6 не делает НИЧЕГО (явный TODO): зелье выпивается +без эффекта. + +Механика оригинала (прочитана в SDLPoP до планирования, правило проекта): + +| что | где в SDLPoP | значение | +|---|---|---| +| включение | `feather_fall()`, seg000:15F8 | `is_feather_fall = 1`, зелёная вспышка (`flash_color = 2`, `flash_time = 3`), `stop_sounds` + `sound_39_low_weight` | +| физика | `fall_accel()`, seg006:057C | ускорение **1** вместо 3, потолок скорости **4** вместо 33 (`FALLING_SPEED_*_FEATHER`, types.h:1435) | +| анимация | опкод `SEQ_JMP_IF_FEATHER`, seg006:586 | в seqtbl есть ветки `stepfloat` / `bumpfloat` — «плавные» кадры падения и удара; таблица у нас из данных оригинала, ветки УЖЕ ЛЕЖАТ в ней | +| длительность | `do_timers`, seg003:517 | ваниль: пока играет звук (или 225 тиков); фикс SDLPoP `fix_quicksave_during_feather` — таймер `FEATHER_FALL_LENGTH = 18.75 c` | +| сброс | seg003:189 (`start_level`) | на старте уровня и, по фиксу, при смерти Кида | +| вид склянки | `draw_tile_fore`, seg008:740 | типы 2..4 — БОЛЬШАЯ склянка (id 13) — **у нас уже так** | +| цвет пузырьков | `draw_tile_anim`, seg008:652 | типы 3/4 — **зелёный** (`color = 10`), 5/6 — синий, остальные — красный (12) | + +Шаги (в порядке выполнения, каждый проверяем отдельно): + +1. **Состояние.** `pop_feather` (счётчик кадров) в `pop_map.c` рядом с + `fall_accel`; экспорт в `pop_map.h` для `play_seq`. Длительность — по + таймеру (ванильная привязка к звуку нам не подходит: звука нет), значение + пересчитать из 18,75 с в наши кадры и завести в `pop_tune.h`. Сброс — в + `pop_start_level` и по смерти Кида. +2. **Физика.** `fall_accel()`: при `pop_feather` — `FALL_ACCEL_FEATHER 1` / + `FALL_MAX_FEATHER 4`. Только для Кида (`charid == CHARID_0_KID`) — так + правильнее по смыслу, но это ОСОЗНАННОЕ расхождение с ванилью (там эффект + ловят все, и SDLPoP чинит это опцией) → запись в `../docs/impl_diff.md`. +3. **Анимация.** Опкод `0xF7` в `play_seq` (`pop_kid.c`) сейчас БЕЗУСЛОВНО + пропускает адрес; сделать как в оригинале: при `pop_feather` — прыжок по + адресу (это и даёт `stepfloat`/`bumpfloat`, то есть отсутствие урона и + «парение»). Правка на 3 строки, но именно она даёт весь визуальный эффект + падения. +4. **Вспышка.** Сейчас цвет вспышки — булев `pop_flash_red` (жёлтая/красная); + расширить до кода цвета и добавить ЗЕЛЁНУЮ (`flash_time = 3`). +5. **Зелёные пузырьки.** `pop_pack_bg.py` уже красит пузырёк mono-цветом + (`POT_BUBBLE_COLOR = VGA16 + 12`, красный). Добавить второй набор кадров + 16..22 в зелёном (`+10`) под своими id в атласе `pop_pot` и выбирать набор + по `potion_type` в `pop_potion_draw` (`pop_room.c`): 3/4 — зелёный, 5/6 — + синий, иначе красный. Цена — 7 маленьких спрайтов. +6. **Проверка.** Хост-тест: падение с трёх рядов под пером — скорость не выше + 4, урона нет, Кид жив (сцена в `t_phys`/`t_char`). MAME: комната 1 уровня + 7 — выпить, спрыгнуть в шахту, убедиться в плавном спуске и в том, что + эффект кончается по таймеру. + ### L3-COLOR. Палитра КЛАДКИ уровня 3 (в оригинале он зелёный) **Наблюдение (пользователь, 2026-08-07, со сравнением карт VGA).** В diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h index 8bef336..e3c3fe0 100644 --- a/applications/PoP/roomtest/pop_guard.h +++ b/applications/PoP/roomtest/pop_guard.h @@ -114,6 +114,13 @@ #define FALLING_ENTRY_LEVEL 7 #define FALLING_ENTRY_ROOM 17 +/* Комната, которую проходят СВЕРХУ ВНИЗ спуском через зацеп: Кид висит на + * кромке кнопки (0,2), отпускает и цепляется за кромку (2,2). Нужна ЧИТУ + * навигации (`+`/`−` в roomtest.c): он ставит Кида на нижний пол, а отсюда + * этот спуск не проверить — для этой комнаты старт в ряду 0. */ +#define GRAB_DOWN_LEVEL 7 +#define GRAB_DOWN_ROOM 14 + /* Единственный уровень, где тень — БОЕВОЙ персонаж (seg003:702). На всех * остальных (4 зеркало, 5 зелье, 6 плита) она Кида «не видит»: см. * pop_check_can_guard_see_kid. */ diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index c1a0157..6234863 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -797,17 +797,27 @@ static void do_fall(void) if (nrow > 4) nrow = 4; /* защита pop_y_land[] от выхода */ if ((uint16_t)pop_y_land[nrow] > (uint16_t)Char.y) { check_grab(); /* ещё летит — попытка зацепа */ - } else { + } else if (Char.curr_row <= 2) { if (get_tile_at_char() == TILE_WALL) in_wall(); if (tile_is_floor(g_curr_tile)) land(); - else if (Char.curr_row < 2) - inc_curr_row(); /* следующий ряд ВНУТРИ комнаты */ - /* curr_row==2, нет пола: продолжает падать (fall_speed двигает y); - * пересечение нижней границы комнаты ловит check_leave_below (y>=211, - * порт leave_room) — общий путь для провала И спуска-зацепа. */ + else + inc_curr_row(); /* следующий ряд, ВКЛЮЧАЯ 2 -> 3 */ + /* Ряд 3 — «за нижней кромкой комнаты» (в оригинале get_tile уходит по + * links.down и это ряд 0 комнаты снизу, seg006:005D). Нам он нужен + * ровно за тем, за чем нужен оригиналу: пока Kid летит вдоль ряда 3, + * check_grab пробует зацеп за тайл ряда 2 СВОЕЙ комнаты — иначе + * спуск «повис на кромке, отпустил, зацепился этажом ниже» через + * нижнюю границу комнаты невозможен в принципе (BUG-GRAB-SEAM-DOWN). + * Раньше ряд залипал на 2, check_grab не звался ни разу, и Kid + * пролетал мимо кромки, набирая fall_y > 32 — после чего окно + * зацепа закрыто уже по скорости. */ } + /* curr_row == 3: тайлов своей комнаты там нет (get_tile отдаёт WALL как + * сентинел), поэтому ни in_wall, ни land здесь звать нельзя — до y_land[4] + * дело всё равно не доходит: check_leave_below переводит в комнату снизу + * на y >= 211 (порт leave_room). */ } static void fall_accel(void) @@ -1310,7 +1320,13 @@ static void check_action(void) } else if (action == ACT_FREEFALL) { do_fall(); } else if (action == ACT_MIDAIR) { - /* frames 102..105: check_grab — K4 */ + /* Кадры 102..105 — НАЧАЛО падения (seg006:0619). Здесь fall_y ещё + * не разгоняется (fall_accel работает только в ACT_FREEFALL), то есть + * это САМОЕ ШИРОКОЕ окно зацепа: ветка стояла заглушкой, и первые + * четыре кадра падения зацеп не работал вовсе — отсюда «иногда + * цепляется, иногда нет» на входе в уровень 7. */ + if (frame >= 102 && frame < 106) + check_grab(); } else if (action != ACT_HANGCLIMB) { check_on_floor(); } diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index a124d68..1fca257 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -696,15 +696,25 @@ int main(void) * это и «прилёт» в проём/ворота, и, на уровне 5, посадка * вплотную к тени спецсобытия, которая ждёт в колонке −1 * ряда 0. Вместо сцены кражи зелья начиналась схватка, - * которой в этой комнате быть не должно. + * которой в этой комнате быть не должно. Снизу вверх нужно + * и самой навигации: в верхнем ряду пола чаще нет, и Кид + * проваливается из комнаты сразу после телепорта. + * + * ИСКЛЮЧЕНИЕ — комната 14 уровня 7 (пользователь 2026-08-12): + * её проходят СВЕРХУ ВНИЗ (с ряда 0 на ряд 2 через зацеп за + * кромки), и с нижнего ряда этот спуск не проверить вовсе. + * Пол в ряду 0 там есть, так что Кид не проваливается. * * В каждом ряду сначала ищем ОБЫЧНЫЙ пол, и только если его * там нет — любой проходимый tile_is_floor (seg001: всё, * кроме пустоты, верха колонны, верха двери, стены и * «половинок» 26..29). */ { - int8_t row; - for (row = 2; row >= 0 && any == 0xFF; row--) { + int8_t row, step = -1, first = 2; + if (pop_current_level == GRAB_DOWN_LEVEL && + r == GRAB_DOWN_ROOM) { first = 0; step = 1; } + for (row = first; row >= 0 && row <= 2 && any == 0xFF; + row = (int8_t)(row + step)) { uint8_t fallback = 0xFF; for (i = (uint8_t)(row * 10); i < (uint8_t)(row * 10 + 10); i++) { c = (uint8_t)(room_fg[i] & 0x1F); diff --git a/applications/PoP/roomtest/tests-host/t_grab.c b/applications/PoP/roomtest/tests-host/t_grab.c index 4efac36..0d64355 100644 --- a/applications/PoP/roomtest/tests-host/t_grab.c +++ b/applications/PoP/roomtest/tests-host/t_grab.c @@ -149,11 +149,135 @@ TC_TEST(grab_same_for_all_key_patterns) } } +/* ---- Спуск через НИЖНЮЮ КРОМКУ комнаты (BUG-GRAB-SEAM-DOWN) ---------- * + * Уровень 7, комната 14 — по ней уровень и проходят: Кид на ряду 0 висит на + * кромке кнопки (0,2), отпускает и в падении должен зацепиться за кромку + * (2,2). Тайл-цель лежит В НИЖНЕМ ряду, то есть в момент зацепа персонаж + * уже НИЖЕ комнаты — в оригинале это curr_row == 3, а get_tile для него + * уходит по links.down в комнату снизу (find_room_of_tile, seg006:005D). + * + * Пока do_fall не пускал ряд дальше 2, check_grab на этом отрезке не звался + * НИ РАЗУ: Кид пролетал мимо кромки, набирал fall_y > 32, и дальше окно + * зацепа было закрыто уже по скорости — спуск становился непроходимым. + * Тест держит именно это: окно существует и достижимо. */ +#define C 6 /* closer */ +#define O 15 /* opener */ + +static const uint8_t room14[30] = { + C, F, O, E, E, E, E, E, F, F, + W, W, W, E, E, E, E, E, W, W, + F, F, P, E, E, E, E, E, W, W, +}; +static const uint8_t room15[30] = { + W, W, W, E, E, E, E, E, W, W, + F, F, P, E, E, E, E, E, W, W, + W, W, W, E, E, E, E, E, W, W, +}; +/* Ряд 2 комнаты 14 = «ряд −1» комнаты 15 (pop_map_set_above): после перехода + * зацеп продолжает целиться в ту же кромку, уже как в потолок. */ +static uint8_t above14[10]; + +#define ACT_MIDAIR 3 +#define ACT_FREEFALL 4 + +static uint8_t went_below; /* Kid уже пересёк нижнюю границу комнаты 14 */ + +/* Один кадр + переход в комнату снизу — ровно то, что делает главный цикл + * roomtest.c по pop_fell_out (enter_room(down) + exit_room_timer). */ +static void step14(uint8_t keys) +{ + sc_trace_clear(); /* трасса тут не нужна, важен исход */ + sc_run(keys, 1); + if (pop_fell_out) { + uint8_t i; + pop_fell_out = 0; + went_below = 1; + for (i = 0; i < 10; i++) above14[i] = room14[20 + i]; + sc_room(room15, 15); + pop_map_set_edges(0, 0, 14, 0); /* сверху — комната 14 */ + pop_map_set_above(above14); + pop_leave_timer = 2; + } +} + +/* Исход попытки: + * 'G' — повис на НИЖНЕЙ кромке (то, ради чего тест: ряд 2 комнаты 14); + * 'U' — зацепился обратно за ту кромку, с которой сорвался (ряд 0) — + * это другой, всегда работавший случай, и засчитывать его нельзя; + * '.' — пролетел мимо; 'x' — так и не сорвался (проба не состоялась). */ +static char probe_down(uint8_t x, uint8_t delay) +{ + uint8_t i; + sc_room(room14, 14); + pop_map_set_edges(0, 0, 0, 15); /* links комнаты 14: снизу — 15 */ + pop_map_set_above(0); + pop_leave_timer = 0; + pop_fell_out = 0; + went_below = 0; + /* Лицом ВЛЕВО у правой кромки кнопки: обрыв позади — это и есть поза + * спуска (pop_down_action, seg005:482). */ + sc_kid_at_x(2, 0, x, -1); + + /* ↓ + Shift — спуск с уступа (pop_down_action, seq_68) и вис на кромке. + * Shift держим, иначе Кид сорвётся, не успев повиснуть. */ + for (i = 0; i < 30; i++) { + step14(SC_D | SC_SHIFT); + if (Kid.action == ACT_HANG_CLIMB || Kid.action == ACT_HANG_STRAIGHT) break; + } + if (Kid.action != ACT_HANG_CLIMB && Kid.action != ACT_HANG_STRAIGHT) return 'x'; + /* отпустить Shift — сорваться с виса (hang_fall, seg005) */ + for (i = 0; i < 12; i++) { + step14(0); + if (Kid.action == ACT_MIDAIR || Kid.action == ACT_FREEFALL) break; + } + if (Kid.action != ACT_MIDAIR && Kid.action != ACT_FREEFALL) return 'x'; + + for (i = 0; i < delay; i++) step14(0); /* пауза перед Shift */ + for (i = 0; i < 30; i++) { + step14(SC_SHIFT); + if (Kid.action == ACT_HANG_CLIMB || Kid.action == ACT_HANG_STRAIGHT) { + if (!went_below) return 'U'; + /* За ЧТО взялся: после перехода вниз ряд 0 = кромка ряда 2 + * комнаты 14 (наша цель), ряд 2 = кромка ряда 1 комнаты 15 + * (этажом ниже — этот случай работал и раньше). */ + return Kid.curr_row == 0 ? 'G' : 'g'; + } + } + return '.'; +} + +#define XD_LO 90 +#define XD_HI 113 + +#define XD_DELAY_MAX 8 + +TC_TEST(grab_below_room_edge_window_exists) +{ + uint8_t x, d, n = 0, n0 = 0; + for (x = XD_LO; x <= XD_HI; x++) + for (d = 0; d <= XD_DELAY_MAX; d++) + if (probe_down(x, d) == 'G') { n++; if (!d) n0++; } + TC_TRUE(n > 0); + /* И играбельность: зацеп обязан удаваться при самом естественном вводе — + * отпустил вис и сразу зажал Shift (d = 0). Окно, доступное только с + * точной задержкой, для игрока почти равно отсутствующему. */ + TC_TRUE(n0 > 0); + if (!n) { /* карта исходов: строка = задержка Shift */ + uint8_t i; + for (i = 0; i <= XD_DELAY_MAX; i++) { + put('d'); put((char)('0' + i)); put(':'); put(' '); + for (x = XD_LO; x <= XD_HI; x++) put(probe_down(x, i)); + put('\n'); + } + } +} + int main(void) { sc_init(); TC_RUN(grab_window_exists); TC_RUN(grab_window_reachable_by_safe_step); TC_RUN(grab_same_for_all_key_patterns); + TC_RUN(grab_below_room_edge_window_exists); return 0; }