Приёмка ур. 2–3: сквозь стену, клин в шве, бар под плитой, глухой страж
Четыре разбора с прогонов пользователя; три бага закрыты, четвёртый (BUG-SPIKE-1, пики) заведён с замером и гипотезой. BUG-JUMPWALL-1 (Critical) — недолетевший прыжок проходил СКВОЗЬ стену. check_collisions держал флаги перекрытия ОДНОГО ряда, а оригинал (seg004:0004) — трёх, и move_coll_to_prev (seg004:00DF) берёт «прошлые» флаги из нужного. Наш prev=3 («уже перекрывал») подавлял бамп ровно на кадре смены ряда, а в падении ряд меняется почти каждый кадр — переход 0→1 на стене приходился как раз на него. Порт трёх рядов дословно. Воспроизведено и закрыто на харнессе (новый набор t_wall: свип по 20 стартовым X, 4 давали проход сквозь кладку); 1723 трассы t_phys НЕ изменились — правка поведение-сохраняющая. Живьём подтвердил пользователь. BUG-GUARD-DEAF-1 (Major) — is_guard_notice не взводился НИГДЕ, поэтому неактивный страж не оборачивался на Кида за спиной никогда. Портированы все пять мест оригинала: опкод SOUND в play_seq (звуки 0..2), bumped_sound, мягкое/среднее приземление, обрушенная плита, щелчок кнопки. Ждёт игровой проверки боем в комнате 11 уровня 2. BUG-SEAM-WEDGE-1 — клин кладки в пустом (2,0). Сосед угла снизу-слева лежит в комнате по диагонали (room_BL); мы безусловно считали его стеной, оригинал (load_rowbelow, seg008:368) — только когда такой комнаты нет. Ряд «снизу» стал 11-байтным: [10] = тайл (0,9) диагональной комнаты. BUG-LOOSE-3 — чёрный бар под упавшей плитой-потолком. pop_ceil_bake_empty стирал полосу и восстанавливал только два тайла ряда −1, а в полосу лезет графика соседа слева и верхушки ряда 0. Теперь перерисовываются ряды −1 и 0, колонки col−1..col+1. Проверено попиксельной сверкой с эталонной перерисовкой: 0 различий. Плюс карта связности комнат уровней 1–3 (TASKS.md): на ур. 2 недостижимых нет, на ур. 3 это 23 и 24 — те же односторонние ссылки, что дали 13/18/24 на уровне 1, только комнаты полностью пустые. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,8 @@ BUG-GATE-PASS-1 — однократное наблюдение прохода
|
||||
|
||||
| ID | что | тип | статус |
|
||||
|----|-----|-----|--------|
|
||||
| [BUG-SPIKE-1](#bug-spike-1) | пробег по выдвинутым пикам не убивает: модификатор застревает в «полностью вышли» (h=1), а это в оригинале не смертельно для бегущего | Major | **открыт, замер есть, заведён 2026-08-05** |
|
||||
| [BUG-GUARD-DEAF-1](#bug-guard-deaf-1) | страж не оборачивается на вернувшегося Кида: `is_guard_notice` не взводился | Major | **фикс есть, ждёт игровой проверки** |
|
||||
| [BUG-GRAB-1](#bug-grab-1) | ур. 2 комн. 9: прыжок с места через 3 тайла — нет зацепа за кромку | Major | **причина найдена (клавиатура, BUG-KBD-5), фикс есть, ждёт игровой проверки** |
|
||||
| [BUG-GATEMOD-1](#bug-gatemod-1) | ворота стартуют закрытыми, хотя в уровне открыты | Major | **фикс есть, ждёт проверки** |
|
||||
| [Уровень 2](#уровень-2) | остальные баги с приёмки | — | принимаются по ходу |
|
||||
@@ -45,6 +47,15 @@ BUG-GATE-PASS-1 — однократное наблюдение прохода
|
||||
подаст их списком отдельно — раздел заведён под них заранее, чтобы
|
||||
наблюдения не растеклись по переписке.
|
||||
|
||||
**Закрыто с этой волны:** [BUG-LOOSE-3](bug_closed.md#bug-loose-3) — чёрный
|
||||
бар под упавшей плитой-потолком (запечка восстанавливала не всё, что стёрла).
|
||||
|
||||
**Порядок работы по уровню 2 (2026-08-05):** наблюдения СНАЧАЛА копятся
|
||||
здесь целиком — фиксы берём, когда уровень пройден до конца. Причина:
|
||||
косметика отрисовки почти всегда сводится к общему корню (ср. волну
|
||||
BUG-CEIL-1/2/3 + BUG-OCCL-1), и чинить по одному наблюдению — значит
|
||||
чинить один и тот же корень трижды.
|
||||
|
||||
Карта содержимого уровня (что где стоит по данным `res2002.bin`, какие
|
||||
кнопки какие ворота открывают) — в [`TASKS.md`](TASKS.md#l2-pass),
|
||||
запись **L2-PASS**: по ней видно, «механика не сработала» это или
|
||||
@@ -56,6 +67,169 @@ BUG-GATE-PASS-1 — однократное наблюдение прохода
|
||||
вводит ровно три новых фоновых тайла — **большая колонна (низ 8 / верх 9)
|
||||
и верх двери (12)**; если артефакт рядом с ними, это первый подозреваемый.
|
||||
|
||||
<a id="уровень-3"></a>
|
||||
# Уровень 3 — баги с приёмки (заведено 2026-08-05)
|
||||
|
||||
Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал четыре наблюдения. Два
|
||||
из них — **не баги, а неначатые задачи**, и записаны как таковые:
|
||||
|
||||
| наблюдение | что это на самом деле |
|
||||
|------------|------------------------|
|
||||
| к.22: чомпер (2,6) не анимируется и **вообще не рисуется** | [L3-CHOMP](TASKS.md#l3-chomp) — механики чомперов НЕТ. В таблице тайлов `pop_bg.c:211` строка `12 chomper` рисует только основание, правую грань и низ; самих челюстей (спрайт из `chtab`, `draw_tile_anim` seg008) нет вовсе. Так и должно выглядеть до порта |
|
||||
| к.10: скелет не оживает | [L3-SKEL](TASKS.md#l3-skel) — `check_skel` (seg002:0E1F) не портирован. **И комната другая:** тайл `skeleton(21)`, который оживает, лежит в **к.1 (1,5)** — это `skeleton_room=1, skeleton_column=5, skeleton_row=1`. Ещё два скелета уровня (к.17 (2,7), к.19 (2,2)) — просто декорация, они не оживают никогда. Плюс условие: скелет встаёт, только когда **дверь уровня уже открыта** и Кид стоит в колонке 2 или 3 |
|
||||
|
||||
Остальные два были настоящими багами и **закрыты в тот же день** — разбор
|
||||
корней в [`bug_closed.md`](bug_closed.md): **BUG-JUMPWALL-1** (флаги
|
||||
коллизий считались для одного ряда вместо трёх — бамп терялся на кадре
|
||||
смены ряда) и **BUG-SEAM-WEDGE-1** (сосед угла (2,0) безусловно считался
|
||||
стеной).
|
||||
|
||||
<a id="bug-spike-1"></a>
|
||||
## BUG-SPIKE-1. Пики не убивают бегущего Кида — ОТКРЫТ, ЗАМЕР ЕСТЬ
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-05).** Уровень 2, комната 6, пики (1,3):
|
||||
|
||||
| действие | что происходит |
|
||||
|----------|----------------|
|
||||
| длинный прыжок с ряда 0 на пики | **смерть — правильно** (путь `fell_on_spikes`) |
|
||||
| пробег по ряду 1 прямо по пикам | **урона нет** |
|
||||
| после уборки пик | **на экране остаются белые остатки остриёв** (в оригинале чисто) |
|
||||
| прыжок на месте, стоя на пиках | урона нет |
|
||||
| просто стоять на выдвинутых пиках | можно сколько угодно |
|
||||
|
||||
**Замер (MAME, чтение `room_modif` комнаты 6).** Пока Кид стоит на тайле,
|
||||
модификатор пики (индекс 13) = **0x8E**, то есть «пики ПОЛНОСТЬЮ вышли и
|
||||
идёт обратный отсчёт». Дальше вся арифметика сходится с оригиналом:
|
||||
|
||||
```
|
||||
is_spike_harmful (seg007:1178): 0/-1 → 0; <0 → 1; 1..4 → 2; >=5 → 0
|
||||
check_spiked (seg006:0658): убивает при h>=2 на кадрах бега 7..14
|
||||
и при h!=0 на кадрах приземления 43/26
|
||||
```
|
||||
|
||||
То есть **при h = 1 (пики уже вышли) бегущий не гибнет и в оригинале** —
|
||||
смертельно только окно ВЫДВИЖЕНИЯ (модификатор 1..4, h = 2). Наши
|
||||
`animate_spike`, `start_anim_spike`, `is_spike_harmful`, `check_spiked`
|
||||
сверены с seg006/seg007 построчно и совпадают дословно.
|
||||
|
||||
**Значит расходится ТАЙМИНГ, а не логика:** у нас пики успевают полностью
|
||||
выйти ДО того, как Кид на них вбежит, и он проскакивает мимо смертельного
|
||||
окна 1..4. Триггер — `check_spike_below` (seg006:1199), и у нас он берёт
|
||||
габарит спрайта из `kid_fp` — метрик ПОСЛЕДНЕГО отрисованного кадра, а
|
||||
оригинал берёт `char_x_left/right` ТЕКУЩЕГО кадра (`set_char_collision`).
|
||||
Это первый подозреваемый; второй — момент вызова `process_trobs`
|
||||
относительно `play_frame` (сколько кадров модификатор успевает пройти,
|
||||
пока Кид добегает).
|
||||
|
||||
**ВТОРОЕ НАБЛЮДЕНИЕ (то же место, сравнение с оригиналом) — оно и уточняет
|
||||
гипотезу.** Кид уронил плиту-потолок и спрыгнул вниз; пики выдвинулись и
|
||||
«спрятались не все — часть артефактов осталась». Скриншоты рядом:
|
||||
[наш](bugscreens/l2-r6-spikes-ours.png) и
|
||||
[SDLPoP](bugscreens/l2-r6-spikes-sdlpop.png) в той же позе. У нас из-под
|
||||
щебня торчат белые острия, у оригинала пик не видно ВООБЩЕ.
|
||||
|
||||
Это стыкуется с замером: пока Кид стоит рядом, `check_spike_below` каждый
|
||||
кадр зовёт `start_anim_spike`, а тот при отрицательном модификаторе
|
||||
переставляет его обратно в 0x8F — то есть пики у нас «залипают выдвинутыми»
|
||||
и отсчёт до уборки не доходит. В оригинале в той же позе они убраны,
|
||||
значит его `check_spike_below` эту колонку УЖЕ не задевает.
|
||||
|
||||
**Отсюда единая рабочая гипотеза на оба симптома: наш триггер шире/раньше
|
||||
оригинального.** Оригинал берёт габарит ТЕКУЩЕГО кадра
|
||||
(`char_x_left/right`, их ставит `set_char_collision` в этом же кадре), мы —
|
||||
`kid_fp`, метрики ПОСЛЕДНЕГО ОТРИСОВАННОГО кадра. Если наш диапазон
|
||||
колонок хоть на пиксель шире, получаем ровно это: пики выходят до подхода
|
||||
Кида (он проскакивает окно 1..4 → не гибнет) и не убираются, пока он рядом
|
||||
(→ белые остатки на экране).
|
||||
|
||||
**Что снять при разборе:** покадрово `room_modif[13]` вместе с `Kid.x`,
|
||||
`Kid.frame` и вычисленными `c0..c1` на подходе и пробеге через тайл; тот же
|
||||
прогон в SDLPoP с печатью `left_checked_col`/`right_checked_col`. Сверять
|
||||
надо не результат, а ДИАПАЗОН КОЛОНОК кадр в кадр.
|
||||
|
||||
**Не регрессия правок 2026-08-05:** ни трёхрядные флаги коллизий, ни
|
||||
`is_guard_notice`, ни правки отрисовки в путь пик не входят (`check_spiked`
|
||||
сам зовёт `get_tile_at_char`, модификаторы читает из `pop_trob_modif`).
|
||||
|
||||
---
|
||||
|
||||
<a id="bug-guard-deaf-1"></a>
|
||||
## BUG-GUARD-DEAF-1. Страж не оборачивается на вернувшегося Кида — ФИКС ЕСТЬ, ЖДЁТ ИГРОВОЙ ПРОВЕРКИ
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-05).** Уровень 2, комната 11, идёт бой
|
||||
со стражем. Страж **выталкивает Кида в правую комнату 22**. Кид заходит
|
||||
обратно в комнату 11 — страж стоит на (1,1), **повёрнут налево и Кида не
|
||||
видит**. Предположение пользователя: это часть большой задачи «полностью
|
||||
переделать поведение стража по образцу Кида».
|
||||
|
||||
**Диагноз: большая переделка тут ни при чём, корень маленький и точный.**
|
||||
Само возвращение стража в исходную позу — ПРАВИЛЬНОЕ поведение, порт верен;
|
||||
не хватает ровно одного сигнала.
|
||||
|
||||
Разбор по SDLPoP:
|
||||
|
||||
1. **Выход Кида из комнаты «усыпляет» стража — так и в оригинале.**
|
||||
`leave_guard` (seg002:02F5) складывает стража обратно в данные уровня
|
||||
(тайл, `x`, **направление**, skill, HP), а `enter_guard` (seg002:0112)
|
||||
при возврате поднимает живого стража **с УБРАННЫМ мечом**
|
||||
(`sword_0_sheathed` + `seq_77_guard_stand_inactive`) и обнуляет
|
||||
`is_guard_notice`/`guard_refrac`. То есть страж после возврата ВСЕГДА
|
||||
неактивен и смотрит в запомненную сторону — у нас так же
|
||||
(`pop_guard_enter`, `pop_guard.c:127`).
|
||||
2. **Дальше решает `autocontrol_guard_inactive` (seg002:0876), и он Кида за
|
||||
спиной ИГНОРИРУЕТ.** Кид вернулся справа, страж смотрит влево →
|
||||
`char_opp_dist()` отрицательна → ветка `else if (distance < 0) return;`.
|
||||
Единственный выход из неё — флаг **`is_guard_notice`**: «Кид нашумел».
|
||||
При нём страж оборачивается (`move_4_down`). Направление в
|
||||
`check_can_guard_see_kid` НЕ участвует вовсе, так что «не видит» — это
|
||||
не про луч видимости, а именно про этот флаг.
|
||||
3. **У нас `is_guard_notice` не взводится НИГДЕ.** `grep` по всем исходникам
|
||||
roomtest: объявление (`pop_guard.c:20`), сброс (`pop_guard.c:47`) и одно
|
||||
чтение (`guards.c:171`). Присваивания `= 1` нет ни одного — флаг мёртв,
|
||||
поэтому неактивный страж не обернётся НИКОГДА, что бы Кид ни делал.
|
||||
|
||||
**Где его взводит оригинал** (это и есть объём фикса):
|
||||
|
||||
| место | событие |
|
||||
|-------|---------|
|
||||
| `seg006:633..641` — `play_seq`, опкод SOUND | звук `SND_SILENT`(0), `SND_FOOTSTEP`(1), `SND_BUMP`(2). `SND_DRINK`(3)/`SND_LEVEL`(4) — НЕ шум. Главный источник: шаги бега/приземления |
|
||||
| `seg004:05F1` `bumped_sound` | удар в стену |
|
||||
| `seg005:185,195` | мягкое и среднее приземление (**только `charid_0_kid`**) |
|
||||
| `seg006:1294`, `seg006:1734` | Кид обрушил loose-плиту (зацепом и наступив) |
|
||||
| `seg007:766` | нажата кнопка |
|
||||
|
||||
У нас опкод SOUND в `play_seq` (`pop_kid.c:426`) просто съедает байт
|
||||
аргумента — звука нет, и флаг вместе с ним потерялся. Именно эта строка —
|
||||
90 % фикса: `SND_SILENT` называется «silent» потому, что звука не издаёт,
|
||||
**но стражи его всё равно замечают**, и в `seqtbl` он стоит, например, в
|
||||
`ready` (доставание меча).
|
||||
|
||||
**Ожидаемое поведение после фикса.** Кид возвращается в комнату 11 бегом →
|
||||
первый же `SND_FOOTSTEP` взводит флаг → страж оборачивается и достаёт меч.
|
||||
Стоя на месте, Кид может подкрасться к стражу со спины — это НЕ баг, а
|
||||
механика оригинала.
|
||||
|
||||
**Оговорка (не проверено вживую).** Разбор построен на том, что страж после
|
||||
возврата неактивен (меч убран, кадр 166). Это следует из кода
|
||||
`pop_guard_enter`, но в MAME не снималось; если окажется, что меч у него
|
||||
ОБНАЖЁН, то работает другая ветка (`autocontrol_guard_active`, где
|
||||
`can_guard_see_kid == 2` направления не спрашивает) — и тогда корень другой.
|
||||
Снять при фиксе: `Guard.sword`, `Guard.frame`, `can_guard_see_kid` сразу
|
||||
после входа в комнату.
|
||||
|
||||
**Фикс (2026-08-05): все пять мест портированы.** Опкод SOUND в `play_seq`
|
||||
(`pop_kid.c`) взводит флаг для звуков 0..2; `bumped_fall`/`bumped_floor`,
|
||||
мягкое и среднее приземление, обрушенная плита (все три пути `check_press`)
|
||||
— в `pop_map.c`; щелчок кнопки — в `pop_trob.c`. Заглушка `is_guard_notice`
|
||||
добавлена в `tests-host/stubs.c` (автопилота стража в наборах нет).
|
||||
|
||||
**Как проверить в игре:** комната 11 уровня 2, дать стражу вытолкнуть Кида
|
||||
вправо в комнату 22 и вернуться бегом — на первом же шаге страж обязан
|
||||
обернуться и достать меч. Подкрасться к нему СТОЯ (не двигаясь) по-прежнему
|
||||
можно — это механика оригинала, а не баг.
|
||||
|
||||
---
|
||||
|
||||
<a id="bug-grab-1"></a>
|
||||
## BUG-GRAB-1. Прыжок с места через провал в 3 тайла: зацепа нет — **ПРИЧИНА НАЙДЕНА, ФИКС ЕСТЬ, ЖДЁТ ИГРОВОЙ ПРОВЕРКИ**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user