Зацеп за кромку нижнего ряда: do_fall пускает ряд до 3 + check_grab в midair

Уровень 7, комната 14: спуск с ряда 0 на ряд 2 через зацеп (повис на кромке
кнопки (0,2), отпустил, взялся за (2,2)) не работал ни при какой фазе и
задержке — Кид пролетал мимо и разгонялся до fall_y = 33, после чего окно
зацепа закрыто уже по скорости.

Корень: do_fall не давал curr_row выйти за 2, а check_grab целится в тайл
ряда curr_row-1 — то есть ряд 2 своей комнаты становится целью только при
curr_row == 3.  В оригинале (seg005:0030) inc_curr_row безусловный, а
get_tile для ряда 3 уходит по links.down (find_room_of_tile, seg006:005D).

- pop_map.c do_fall: inc_curr_row теперь 2 -> 3; ветка «достиг y_land»
  гейтится по curr_row <= 2 (при ряде 3 ни in_wall, ни land звать нельзя —
  тайлов своей комнаты там нет, а до y_land[4] дело не доходит: комнату
  меняет check_leave_below на y >= 211).
- pop_map.c check_action: восстановлена ветка ACT_MIDAIR (кадры 102..105,
  seg006:0619) — первые четыре кадра падения, где fall_y ещё не разогнан,
  зацеп не работал вовсе.  Отсюда же «иногда цепляется, иногда нет».
- tests-host/t_grab.c: регресс grab_below_room_edge_window_exists — сцена
  комнаты 14 + переход в 15 по pop_fell_out.  До фикса ни одного зацепа,
  после — окно из 8 фаз X при задержках 0..5 кадров; проверяется и
  играбельность (зацеп при «отпустил и сразу зажал Shift»).
- roomtest.c + pop_guard.h: чит-навигация ставит Кида на верхний ряд в
  комнате 14 уровня 7 (GRAB_DOWN_LEVEL/ROOM) — снизу этот спуск не проверить.
  Общее правило (снизу вверх) не тронуто: в верхнем ряду пола чаще нет и Кид
  проваливается сразу после телепорта.

Проверено: tests-host 5/5 (55 в [grab]), size-check OK, живьём в MAME —
Кид повис на кромке (2,2): frame=91 y=55 row=0 room=15.

Доски: GRAB-BELOW-ROOM в BUGS_CLOSED, GRAB-KBD-TIMING в BUGS_OPEN (отложен
пользователем до готовности всех уровней), план L7-FEATHER в TASKS_OPEN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 11:58:17 +03:00
parent 4fc4283232
commit b5951c9b5c
8 changed files with 331 additions and 24 deletions
+63
View File
@@ -13,6 +13,69 @@
---
<a id="grab-below-room"></a>
## 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.
---
<a id="bug-cheat-fight-1"></a>
## BUG-CHEAT-FIGHT-1. Чит `+`/`−` в бою: Кид остаётся в режиме боя и теряет управление
+35
View File
@@ -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) | зацеп в падении срабатывает менее стабильно, чем в оригинале | **после всех уровней** | открыт: подозрение на клавиатурный модуль, не на физику |
---
<a id="grab-kbd-timing"></a>
## 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 и
задержке нажатия) — на нём и проверять «стало стабильнее».
---
+16 -14
View File
@@ -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. Состояние репозитория
+50
View File
@@ -87,6 +87,56 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
## P0 — делаем сейчас
### <a id="l7-feather"></a>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 — выпить, спрыгнуть в шахту, убедиться в плавном спуске и в том, что
эффект кончается по таймеру.
### <a id="l3-color"></a>L3-COLOR. Палитра КЛАДКИ уровня 3 (в оригинале он зелёный)
**Наблюдение (пользователь, 2026-08-07, со сравнением карт VGA).** В
+7
View File
@@ -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. */
+23 -7
View File
@@ -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();
}
+13 -3
View File
@@ -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);
@@ -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;
}