Зацеп за кромку нижнего ряда: 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:
@@ -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. Чит `+`/`−` в бою: Кид остаётся в режиме боя и теряет управление
|
||||
|
||||
|
||||
@@ -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 и
|
||||
задержке нажатия) — на нём и проверять «стало стабильнее».
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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. Состояние репозитория
|
||||
|
||||
|
||||
@@ -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).** В
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user