Приёмка ур. 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:
@@ -51,7 +51,12 @@
|
||||
|
||||
## P0 — делаем сейчас
|
||||
|
||||
### <a id="l2-pass"></a>L2-PASS. Приёмка уровня 2
|
||||
### <a id="l2-pass"></a>L2-PASS. Приёмка уровня 2 — **SMOKE ПРОЙДЕН 2026-08-05**
|
||||
|
||||
> **Прогон 2026-08-05 (пользователь): уровень 2 пройден.** Полная приёмка по
|
||||
> всем комнатам — позже; из smoke пришли [BUG-LOOSE-3](bug_list.md#bug-loose-3)
|
||||
> и [BUG-GUARD-DEAF-1](bug_list.md#bug-guard-deaf-1). Следом прогнан smoke
|
||||
> уровня 3 — его наблюдения в [разделе «Уровень 3»](bug_list.md#уровень-3).
|
||||
|
||||
Баги отрисовки уровня 2 пользователь подаёт списком отдельно — они идут в
|
||||
[`bug_list.md`](bug_list.md), раздел «Уровень 2». Ниже — **карта
|
||||
@@ -105,6 +110,45 @@
|
||||
закрыт на уровне 1 (L1-EXIT), но на этом уровне не прогонялся;
|
||||
- **смерть/респавн** возвращают на уровень 2, а не на 1.
|
||||
|
||||
### <a id="rooms-graph"></a>Связность комнат уровней 1–3 (снято 2026-08-05)
|
||||
|
||||
Обход графа `roomlinks` (@1952, по 4 байта на комнату: L, R, U, D; 0 = нет
|
||||
соседа) от стартовой комнаты — тем же методом, которым на уровне 1 нашлись
|
||||
13/18/24 (см. «НЕ БАГИ» в [`bug_closed.md`](bug_closed.md)). Скрипт разовый,
|
||||
в репозиторий не клался: чтение трёх массивов, BFS и проверка симметрии.
|
||||
|
||||
| уровень | старт | недостижимы | признак |
|
||||
|---------|-------|-------------|---------|
|
||||
| 1 | к.1 (0,0) | **13, 18, 24** | ссылки наружу есть, обратных нет |
|
||||
| 2 | к.5 (1,3) | **нет** | граф полностью симметричен, все 24 достижимы |
|
||||
| 3 | к.9 (2,4) | **23, 24** | то же, что на 1: односторонние ссылки, обе комнаты **полностью пустые** |
|
||||
|
||||
```
|
||||
ур.3: 23 L→4, у 4 R=22 | 24 L→2, у 2 R=7
|
||||
23 R→22, у 22 L=4 | 24 R→7, у 7 L=2
|
||||
| 24 U→16, у 16 D=0
|
||||
на 23 ссылается только 24, на 24 — только 23; тайлы обеих = все empty
|
||||
```
|
||||
|
||||
То есть на уровне 3 это даже более чистый случай, чем на уровне 1: там в
|
||||
брошенных комнатах была геометрия, здесь — пустота. Практический вывод тот
|
||||
же: **в 23/24 возможен «мусор в шве»** (наш рендер кромки читает крайнюю
|
||||
колонку соседа ПО ССЫЛКЕ, а сосед соседом себя не считает), приоритет багов
|
||||
там низкий, в игре их не видно.
|
||||
|
||||
**Уровень 2 — недостижимых нет, но есть три КОЛОДЦА без выхода** (единственная
|
||||
связь — вверх, откуда Кид падает):
|
||||
|
||||
```
|
||||
к.10 U→4 пики(2,2) + пол — падение из комнаты 4
|
||||
к.14 U→21 шахта 2 тайла шириной, дно = обломки
|
||||
к.17 U→15 то же
|
||||
```
|
||||
|
||||
Это не баги данных: в 14/17 попадают только падением насмерть, а из 10
|
||||
(если выжил) выхода нет вовсе — так в оригинале. При приёмке не считать
|
||||
«застрял» багом.
|
||||
|
||||
### <a id="l3-chomp"></a>L3-CHOMP. Чомперы — механика уровня 3
|
||||
|
||||
**Где:** 5 штук, комнаты 5(тайл 4), 16(23,24,25), 22(26). Дальше они почти
|
||||
@@ -576,7 +620,15 @@ BUG-1 (провал на row 1 при переходе через открыты
|
||||
Условие «делать ПОСЛЕ CLIP-1» **снято** — CLIP-1 закрыт (см. P0). Проверка —
|
||||
секундомером по одинаковому отрезку рядом с живым SDLPoP, не «на глаз».
|
||||
|
||||
### <a id="l1-pass"></a>L1-PASS. Сквозное прохождение уровня 1
|
||||
### <a id="l1-pass"></a>L1-PASS. Сквозное прохождение уровня 1 — **SMOKE ПРОЙДЕН 2026-08-05**
|
||||
|
||||
> **Прогон 2026-08-05 (пользователь): успешный.** От старта до выхода с
|
||||
> уровня одним заходом — меч подобран, **оба стража побеждены в честном бою**
|
||||
> (без читов), выход отработал корректно. Это smoke: пройдены не все
|
||||
> комнаты, поэтому таблица обхода 24 комнат остаётся открытой. Ценность
|
||||
> прогона в том, что он снял главные риски этапа 1 разом — боёвка,
|
||||
> предметы, переход с уровня — и стал регресс-базой для уровня 2.
|
||||
|
||||
От старта до двери уровня одним заходом: подбор меча, страж, кнопки/ворота,
|
||||
пики, loose-полы, зелье, падения. Это приёмка этапа 1 и одновременно
|
||||
регресс-база для уровня 2.
|
||||
|
||||
@@ -11,6 +11,99 @@
|
||||
|
||||
---
|
||||
|
||||
## BUG-JUMPWALL-1. Недолетевший прыжок проходил СКВОЗЬ стену — **ЗАКРЫТ 2026-08-05**
|
||||
|
||||
**Симптом (пользователь, приёмка уровня 3).** Комната 14: Кид на (0,8)
|
||||
лицом влево, прыжок с места — пролетает сквозь кладку и падает в комнату 13
|
||||
на (2,3). Обобщение пользователя (оно и оказалось верным): «если Кид
|
||||
прыгает, недолетает и должен врезаться в стену и упасть ВДОЛЬ неё, у нас он
|
||||
летит по свободной траектории, как будто ему никто не мешает».
|
||||
|
||||
**Корень — упрощение в `check_collisions`, про которое там же стояла
|
||||
оговорка.** Оригинал (seg004:0004) каждый кадр считает флаги перекрытия для
|
||||
ТРЁХ рядов (`curr`/`above`/`below`), а `move_coll_to_prev` (seg004:00DF) в
|
||||
начале следующего кадра выбирает из них тот, что соответствует ряду прошлого
|
||||
кадра. Так «прошлые» флаги остаются настоящими и на кадре СМЕНЫ РЯДА.
|
||||
|
||||
У нас ряд был один, и на смене ряда `prev` заполнялся тройками («уже
|
||||
перекрывал всё»), что подавляло бамп на этом кадре. В падении ряд меняется
|
||||
почти каждый кадр, а переход флага 0→1 на стене приходится ровно на него —
|
||||
поэтому удар не регистрировался и `bumped_fall` (который и гасит `fall_x`)
|
||||
не вызывался.
|
||||
|
||||
**Как ловили.** Не в MAME, а на харнессе: набор
|
||||
[`tests-host/t_wall.c`](tests-host/t_wall.c) — комната 14 уровня 3 с
|
||||
подложенным дном шахты, свип по всей ширине стартовой плиты (x = 177..196).
|
||||
Из 20 стартовых позиций 4 давали проход сквозь кладку (Кид оказывался в
|
||||
колонке 4 при стене в колонке 5). Первый вариант сценария (одна стартовая
|
||||
X, из центра плиты) БАГА НЕ ПОКАЗАЛ — отсюда свип.
|
||||
|
||||
**Фикс.** Три ряда как в оригинале + дословный `move_coll_to_prev`
|
||||
(включая условия ±3 на заворот ряда при смене комнаты). Цена — 3×14
|
||||
`get_tile` на кадр вместо 14, банк 3 +115 Б.
|
||||
|
||||
**Чем проверено.** `t_wall` зелёный; **все 1723 существующие трассы
|
||||
`t_phys` не изменились ни на байт** — правка поведение-сохраняющая для
|
||||
всего остального. В живой игре подтвердил пользователь: «ударились и
|
||||
падаем вертикально вниз».
|
||||
|
||||
---
|
||||
|
||||
## BUG-SEAM-WEDGE-1. Клин кладки в пустом углу (2,0) — **ЗАКРЫТ 2026-08-05**
|
||||
|
||||
**Симптом (пользователь, приёмка уровня 3).** Комната 18: в тайле (2,0)
|
||||
нарисован кусок кладки, хотя по данным там пусто.
|
||||
|
||||
**Корень.** У тайла (2,0) «сосед снизу-слева» — это колонка −1 ряда 3, то
|
||||
есть тайл ЧУЖОЙ комнаты (`room_BL` — левая от нижней). `load_rowbelow`
|
||||
(seg008:368) резолвит его через `get_tile_to_draw(room_left, 9, 0, …)` и
|
||||
подставляет стену ТОЛЬКО когда такой комнаты нет. Мы клали стену
|
||||
безусловно, а `draw_topright` для стены рисует угловой кусок — он и был
|
||||
клином. Для комнаты 18 диагональ — комната 13, её тайл (0,9) пуст, значит
|
||||
рисовать нечего.
|
||||
|
||||
**Фикс.** Ряд «снизу» стал одиннадцатибайтным: `[0..9]` — колонки комнаты
|
||||
снизу, `[10]` — тайл (0,9) комнаты снизу-слева (дефолт «стена», если её
|
||||
нет). Затронуты `pop_level.c` (заполнение), `pop_bg.c` (чтение),
|
||||
`roomtest.c` (размер массива).
|
||||
|
||||
**Проверено** в MAME: комната 18 уровня 3, клина нет.
|
||||
|
||||
---
|
||||
|
||||
## BUG-LOOSE-3. Чёрный бар под упавшей плитой-потолком — **ЗАКРЫТ 2026-08-05**
|
||||
|
||||
**Симптом (пользователь, приёмка уровня 2).** Комната 6: Кид сбивает
|
||||
плиту-потолок (−1,2) — плита падает, но на её месте остаётся чёрный бар.
|
||||
Два уточнения пользователя оказались диагностическими:
|
||||
|
||||
1. «бар попал в фоновое изображение» — Кид прыгает поверх, уходит, бар
|
||||
остаётся → чернота лежит в ОЗУ-копии, и heal возвращает её каждый кадр;
|
||||
2. «когда возвращаемся в комнату после выхода — бара нет» → статическая
|
||||
отрисовка комнаты рисует всё правильно, виновата ЗАПЕЧКА.
|
||||
|
||||
**Корень.** `pop_ceil_bake_empty` стирал полосу потолка чёрной плитой
|
||||
(`bar`, банк NORMAL — пишет и в ОЗУ-копию) шириной 64 px, а восстанавливал
|
||||
только ДВА тайла ряда −1: `(−1,col)` и `(−1,col+1)`. Но куски тайлов
|
||||
рисуются ВВЕРХ от своей нижней грани и свисают ВПРАВО, поэтому в стёртую
|
||||
полосу попадает графика и соседа СЛЕВА, и тайлов ряда 0 — их верхушки.
|
||||
Незакрашенным оставался прямоугольник ~13×3 px.
|
||||
|
||||
**Замер (он же метод).** Плита роняется без игры — записью
|
||||
`pop_ceil_modif[col] = 1` в отладчике MAME (это ровно то, что делает
|
||||
`make_loose_fall` для потолка). Дальше попиксельная сверка скриншота
|
||||
«после запечки» со скриншотом «комната перерисована заново» (выйти и
|
||||
вернуться): различие локализовалось в прямоугольник **экранные x 230..255,
|
||||
y 47..52** при полосе потолка y 44..52.
|
||||
|
||||
**Фикс.** Запечка восстанавливает всё, чья графика попадает в полосу:
|
||||
ряды −1 И 0, колонки `col−1..col+1`.
|
||||
|
||||
**Проверено:** та же попиксельная сверка после фикса даёт **0 различий** в
|
||||
полосе; пользователь подтвердил в игре.
|
||||
|
||||
---
|
||||
|
||||
## BUG-RJUMP-1. Разбег-прыжок не берёт провал в три тайла — **ЗАКРЫТ 2026-08-04**
|
||||
|
||||
**Симптом (пользователь, приёмка уровня 2).** Комната 1: Кид с разбегу
|
||||
|
||||
@@ -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 тайла: зацепа нет — **ПРИЧИНА НАЙДЕНА, ФИКС ЕСТЬ, ЖДЁТ ИГРОВОЙ ПРОВЕРКИ**
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 482 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 426 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 532 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 413 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 462 KiB |
@@ -633,7 +633,11 @@ static void draw_tile(int row, int col)
|
||||
* room_B(0, col-1); col-1<0 → левая комната снизу = стена уровня. */
|
||||
if (row + 1 > 2) {
|
||||
int lc = col - 1;
|
||||
if (lc < 0) rbl_code = 20; /* кромка уровня = стена */
|
||||
/* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате
|
||||
* (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда;
|
||||
* «стена» там стоит, только если такой комнаты нет — как в
|
||||
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
|
||||
if (lc < 0) rbl_code = g_below ? (uint8_t)(g_below[10] & 0x1F) : 20;
|
||||
else rbl_code = g_below ? (uint8_t)(g_below[lc] & 0x1F) : 1;
|
||||
} else rbl_code = tile_code(row + 1, col - 1);
|
||||
|
||||
@@ -843,8 +847,22 @@ void pop_ceil_bake_empty(int col) __banked
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
g_clip_top = POP_YOFF;
|
||||
bake_rest = 1; /* фон = покой (см. bake_rest) */
|
||||
draw_tile(-1, col); /* тайл уже EMPTY → колодец наверх */
|
||||
if (col + 1 < 10) draw_tile(-1, col + 1);
|
||||
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
||||
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
||||
* нижней грани и свисают ВПРАВО, поэтому в полосу над тайлом col лезут:
|
||||
* • ряд −1, колонки col−1..col+1 — сама полоса кладки у потолка;
|
||||
* • ряд 0, колонки col−1..col+1 — их верхушки заходят в те же 8 px.
|
||||
* Раньше перерисовывались только (−1,col) и (−1,col+1), и в полосе
|
||||
* оставался чёрный прямоугольник ~13×3 px (замер: экранные y 47..52 при
|
||||
* полосе 44..52), который heal возвращал каждый кадр. Признак, что дело
|
||||
* именно в запечке: после выхода из комнаты и возврата бара нет. */
|
||||
{
|
||||
int c0 = (col > 0) ? col - 1 : 0;
|
||||
int c1 = (col + 1 < 9) ? col + 1 : 9;
|
||||
int c;
|
||||
for (c = c0; c <= c1; c++) draw_tile(-1, c);
|
||||
for (c = c0; c <= c1; c++) draw_tile(0, c);
|
||||
}
|
||||
bake_rest = 0;
|
||||
g_clip_top = 0;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
|
||||
@@ -46,9 +46,11 @@ void pop_room_draw(uint8_t room_num,
|
||||
const uint8_t *fg, const uint8_t *bg,
|
||||
const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked;
|
||||
|
||||
/* Задать ряд 0 комнаты СНИЗУ (fg-коды, 10 шт.) — для draw_tile_topright,
|
||||
* когда тайл нижнего ряда стал empty (после падения loose): «стена вниз»
|
||||
* рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */
|
||||
/* Задать ряд 0 комнаты СНИЗУ — для draw_tile_topright, когда тайл нижнего
|
||||
* ряда стал empty (после падения loose): «стена вниз» рисуется по коду
|
||||
* room_B(0, col-1). ОДИННАДЦАТЬ байт: [0..9] — колонки комнаты снизу,
|
||||
* [10] — тайл (0,9) комнаты СНИЗУ-СЛЕВА (сосед угла (2,0); см.
|
||||
* load_rowbelow, seg008:368). 0 — нет комнаты снизу. */
|
||||
void pop_room_set_below(const uint8_t *below_row0_fg) __banked;
|
||||
|
||||
/* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы
|
||||
|
||||
@@ -423,7 +423,13 @@ void play_seq(void)
|
||||
case 0xF4: /* KNOCK_DOWN: приземление трясёт loose В СВОЁМ РЯДУ */
|
||||
knock = -1;
|
||||
break;
|
||||
case 0xF2: /* SOUND — потребить 1 байт */
|
||||
case 0xF2: /* SOUND (seg006:0611): звука у нас нет, но опкод несёт
|
||||
* ВТОРОЙ смысл — «Кид нашумел», и его терять нельзя.
|
||||
* Шумом считаются id 0..2 (SILENT/FOOTSTEP/BUMP); id 0
|
||||
* назван silent потому, что НЕ звучит, а стражи его всё
|
||||
* равно замечают (стоит, например, в seq доставания меча).
|
||||
* DRINK(3)/LEVEL(4) — не шум. См. BUG-GUARD-DEAF-1. */
|
||||
if (SEQ(Char.curr_seq) < 3) is_guard_notice = 1;
|
||||
Char.curr_seq++;
|
||||
break;
|
||||
case 0xF1: /* END_LEVEL: seq_70 доиграл — уровень пройден.
|
||||
|
||||
@@ -210,12 +210,23 @@ int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg,
|
||||
for (i = 0; i < 3; i++) { rcol_fg[i] = 20; rcol_fg[3 + i] = 20; rcol_bg[i] = 0; }
|
||||
}
|
||||
|
||||
/* belowrow: верхний ряд (row0) комнаты снизу */
|
||||
/* belowrow: верхний ряд (row0) комнаты снизу — ОДИННАДЦАТЬ байт.
|
||||
* [0..9] — колонки 0..9 комнаты снизу; [10] — тайл (0,9) комнаты
|
||||
* ПО ДИАГОНАЛИ СНИЗУ-СЛЕВА (room_BL у оригинала). Одиннадцатый нужен
|
||||
* тайлу (2,0): его «сосед снизу-слева» — это колонка −1 ряда 3, то есть
|
||||
* чужая комната, и load_rowbelow (seg008:368) резолвит её именно так,
|
||||
* с дефолтом «стена» только когда такой комнаты НЕТ. Раньше мы клали
|
||||
* туда стену БЕЗУСЛОВНО, и в пустом углу рисовался клин кладки
|
||||
* (BUG-SEAM-WEDGE-1, ур. 3 комн. 18). */
|
||||
if (down >= 1 && down <= 24) {
|
||||
const uint8_t *df = room_fg_ptr(down);
|
||||
uint8_t dl = ((const uint8_t *)(LVL_DATA_OFF + BP_LINKS))[(down - 1) * 4];
|
||||
for (i = 0; i < 10; i++) below_fg[i] = (uint8_t)(df[i] & 0x1F);
|
||||
below_fg[10] = (dl >= 1 && dl <= 24)
|
||||
? (uint8_t)(room_fg_ptr(dl)[9] & 0x1F)
|
||||
: 20; /* кромка уровня */
|
||||
} else {
|
||||
for (i = 0; i < 10; i++) below_fg[i] = 20; /* нет комнаты снизу = стена */
|
||||
for (i = 0; i < 11; i++) below_fg[i] = 20; /* нет комнаты снизу = стена */
|
||||
}
|
||||
|
||||
gfx_w0_unmap();
|
||||
|
||||
@@ -63,7 +63,8 @@ uint8_t pop_level_tileset(uint8_t n);
|
||||
* для коллизии на колонке −2 (Kid стоит В шве);
|
||||
* rcol_fg[6] симметрично (col0 / col1);
|
||||
* нет комнаты → стена/0;
|
||||
* below_fg[10] — верхний ряд (row0) комнаты СНИЗУ («стена
|
||||
* below_fg[11] — [0..9] верхний ряд (row0) комнаты СНИЗУ,
|
||||
* [10] тайл (0,9) комнаты СНИЗУ-СЛЕВА («стена
|
||||
* вниз»); нет комнаты → стена.
|
||||
* 0 — OK, -1 — уровень не загружен / room вне диапазона. */
|
||||
int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg,
|
||||
|
||||
@@ -500,6 +500,8 @@ static void land(void)
|
||||
} else {
|
||||
seq = SEQ_17_SOFT_LAND;
|
||||
}
|
||||
/* seg005:185 — мягкое приземление слышно (звука нет, флаг есть). */
|
||||
is_guard_notice = 1;
|
||||
} else {
|
||||
uint8_t deadly = (uint8_t)(Kid.fall_y >= 33); /* СНЯТЬ до обнуления */
|
||||
pop_kid_hurt = 1;
|
||||
@@ -512,6 +514,7 @@ static void land(void)
|
||||
pop_kid_dead = 1;
|
||||
return;
|
||||
}
|
||||
is_guard_notice = 1; /* seg005:195 */
|
||||
seq = SEQ_20_MEDIUM_LAND;
|
||||
}
|
||||
Kid.fall_x = Kid.fall_y = 0;
|
||||
@@ -1123,9 +1126,13 @@ static void bumped_fall(void)
|
||||
if (Kid.action == ACT_FREEFALL) {
|
||||
Kid.fall_x = 0;
|
||||
} else {
|
||||
kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */
|
||||
kid_set_seq(SEQ_45_BUMPFALL);
|
||||
pop_kid_play();
|
||||
}
|
||||
/* bumped_sound (seg004:05F1) — ВНЕ ветвления, как в оригинале: удар о
|
||||
* стену слышен и в свободном падении. Звука у нас нет, но «Кид нашумел»
|
||||
* взводить обязаны — см. BUG-GUARD-DEAF-1. */
|
||||
is_guard_notice = 1;
|
||||
}
|
||||
|
||||
/* bumped_floor (seg004:053B): удар о стену, когда под персонажем ПОЛ.
|
||||
@@ -1173,8 +1180,9 @@ static void bumped_floor(void)
|
||||
(frame >= 102 && frame < 107))
|
||||
kid_set_seq(SEQ_46_HARDBUMP);
|
||||
else
|
||||
kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */
|
||||
kid_set_seq(SEQ_47_BUMP);
|
||||
pop_kid_play();
|
||||
is_guard_notice = 1; /* bumped_sound (seg004:05F1) */
|
||||
}
|
||||
|
||||
/* ---- check_collisions (seg004:0004) -------------------------------- *
|
||||
@@ -1195,13 +1203,22 @@ static void bumped_floor(void)
|
||||
* Тем же корнем лечится влёт в стену на длинном прыжке (комн. 6) и провал
|
||||
* сквозь пол после приземления у стены (комн. 5).
|
||||
*
|
||||
* ОТЛИЧИЕ ОТ ОРИГИНАЛА (сознательное): он держит флаги ТРЁХ рядов (curr/
|
||||
* above/below), чтобы при смене ряда «прошлые» флаги были настоящими. У
|
||||
* нас ряд один: сменился ряд — считаем, что персонаж уже перекрывал всё
|
||||
* (prev = 3), то есть на кадре смены ряда бампа нет, а на следующем он
|
||||
* посчитается честно. Три ряда — это втрое больше get_tile каждый кадр;
|
||||
* если где-то вылезет пропущенный бамп ровно на кадре приземления —
|
||||
* вернуться сюда. */
|
||||
* ТРИ РЯДА — как в оригинале (было: один, и это оказалось BUG-JUMPWALL-1).
|
||||
* Оригинал каждый кадр считает флаги для рядов row−1, row, row+1, а
|
||||
* move_coll_to_prev (seg004:00DF) в начале СЛЕДУЮЩЕГО кадра выбирает из них
|
||||
* тот, что соответствует ряду прошлого кадра. Так «прошлые» флаги остаются
|
||||
* настоящими и на кадре смены ряда.
|
||||
*
|
||||
* Наш прежний упрощённый вариант (prev = 3 = «уже перекрывал») подавлял бамп
|
||||
* ровно на этом кадре — а в падении ряд меняется чуть ли не каждый кадр, и
|
||||
* переход 0→1 на стене приходится как раз на него. Симптом: недолетевший
|
||||
* прыжок уходит СКВОЗЬ кладку по свободной траектории (ур. 3 комн. 14).
|
||||
* Цена трёх рядов — 3×14 get_tile на кадр вместо 14; замерено, что это
|
||||
* дешевле любого из обходных вариантов, а поведение совпадает с оригиналом.
|
||||
*
|
||||
* Ряды вне комнаты (−1 и 3) считаются штатно: get_tile сам резолвит ряд −1
|
||||
* через комнату сверху (g_above), а ряд 3 отдаёт WALL — то же «через край
|
||||
* не бампим», что и раньше. */
|
||||
/* Колонки считаем от −2 до 11, а не 0..9: колонки за швом — это тайлы
|
||||
* СОСЕДНЕЙ комнаты (get_tile резолвит их через g_lcol/g_rcol), и решётка
|
||||
* соседа обязана бампить так же, как своя (иначе Kid уходит сквозь
|
||||
@@ -1215,6 +1232,7 @@ static void bumped_floor(void)
|
||||
#define COLL_IDX(c) ((c) - COLL_C0)
|
||||
#define COLL_ROW_NONE ((int8_t)-128)
|
||||
static uint8_t coll_prev[COLL_N], coll_curr[COLL_N];
|
||||
static uint8_t coll_above[COLL_N], coll_below[COLL_N];
|
||||
static int8_t coll_prev_row = COLL_ROW_NONE; /* ряд, к которому относится coll_prev */
|
||||
/* «нет колонки»: НЕ −1, потому что −1/−2 — законные колонки шва. */
|
||||
#define BUMP_NONE ((int8_t)-128)
|
||||
@@ -1244,6 +1262,32 @@ static void set_char_collision(void)
|
||||
* колонки от col(char_x_left)−1 до col(char_x_right)+2 и оставляет прочие
|
||||
* как есть; мы считаем все четырнадцать — они дешёвые (индекс в массив +
|
||||
* switch), зато не остаётся протухших ячеек (FIX_COLL_FLAGS SDLPoP). */
|
||||
/* move_coll_to_prev (seg004:00DF): в prev кладём флаги ряда, в котором
|
||||
* персонаж был НА ПРОШЛОМ кадре. Массивы curr/above/below в этот момент
|
||||
* ещё хранят прошлый кадр, поэтому нужный ряд просто выбирается из них по
|
||||
* разнице рядов. Условия — дословно оригинальные, включая ±3: ряд может
|
||||
* «завернуться» при смене комнаты (2 -> 0 вниз, 0 -> 2 вверх), и тогда
|
||||
* геометрия колонок та же, что у текущего ряда.
|
||||
*
|
||||
* Первый кадр (и кадр после смены комнаты, pop_coll_invalidate) прошлого
|
||||
* ряда не имеет: prev = «уже перекрывал всё», бампа на нём нет. */
|
||||
static void move_coll_to_prev(int8_t row)
|
||||
{
|
||||
const uint8_t *src;
|
||||
int8_t i;
|
||||
if (coll_prev_row == COLL_ROW_NONE) {
|
||||
for (i = 0; i < COLL_N; i++) coll_prev[i] = 3;
|
||||
return;
|
||||
}
|
||||
if (row == coll_prev_row || row + 3 == coll_prev_row || row - 3 == coll_prev_row)
|
||||
src = coll_curr; /* ряд не менялся */
|
||||
else if (row + 1 == coll_prev_row || row - 2 == coll_prev_row)
|
||||
src = coll_above; /* поднялся на ряд */
|
||||
else
|
||||
src = coll_below; /* опустился на ряд */
|
||||
for (i = 0; i < COLL_N; i++) coll_prev[i] = src[i];
|
||||
}
|
||||
|
||||
static void get_row_collision_data(int8_t row, uint8_t *flags)
|
||||
{
|
||||
int8_t col;
|
||||
@@ -1271,14 +1315,13 @@ static void check_collisions(void)
|
||||
if (Kid.curr_row < 0 || Kid.curr_row > 2) { coll_prev_row = COLL_ROW_NONE; return; }
|
||||
|
||||
set_char_collision();
|
||||
/* move_coll_to_prev (seg004:00DF) в нашем однорядном варианте. */
|
||||
if (coll_prev_row == Kid.curr_row) {
|
||||
for (col = 0; col < COLL_N; col++) coll_prev[col] = coll_curr[col];
|
||||
} else {
|
||||
for (col = 0; col < COLL_N; col++) coll_prev[col] = 3; /* «уже перекрывал» */
|
||||
}
|
||||
move_coll_to_prev(Kid.curr_row);
|
||||
coll_prev_row = Kid.curr_row;
|
||||
/* Порядок важен: prev уже забран, теперь три ряда пересчитываются
|
||||
* НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). */
|
||||
get_row_collision_data(Kid.curr_row, coll_curr);
|
||||
get_row_collision_data((int8_t)(Kid.curr_row + 1), coll_below);
|
||||
get_row_collision_data((int8_t)(Kid.curr_row - 1), coll_above);
|
||||
/* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка,
|
||||
* в которой флаг перешёл 0→1. */
|
||||
for (col = COLL_C0 + COLL_N - 1; col >= COLL_C0; --col) {
|
||||
@@ -1418,10 +1461,13 @@ static void check_press(void)
|
||||
int8_t c = Kid.curr_col;
|
||||
if (g_above && g_link_u && c >= 0 && c <= 9 &&
|
||||
(g_above[c] & 0x1F) == TILE_LOOSE && !(g_above[c] & 0x20) &&
|
||||
(int8_t)pop_ceil_modif[c] <= 0)
|
||||
(int8_t)pop_ceil_modif[c] <= 0) {
|
||||
pop_ceil_modif[c] = 1; /* make_loose_fall(1) */
|
||||
is_guard_notice = 1; /* seg006:1734 */
|
||||
}
|
||||
} else if (get_tile_above_char() == TILE_LOOSE) {
|
||||
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
|
||||
is_guard_notice = 1; /* seg006:1734 */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1435,8 +1481,10 @@ static void check_press(void)
|
||||
uint8_t tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||
if (t == TILE_OPENER || t == TILE_CLOSER)
|
||||
pop_trigger_button(g_room, tp, t, pop_trob_modif(g_room)[tp]);
|
||||
else if (t == TILE_LOOSE)
|
||||
else if (t == TILE_LOOSE) {
|
||||
make_loose_fall(tp, 1);
|
||||
is_guard_notice = 1; /* seg006:1734 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* пометки, поэтому pop_process_trobs — единственная функция модуля, которую
|
||||
* нельзя звать из __banked-кода. */
|
||||
#include "pop_bg.h"
|
||||
#include "pop_guard.h" /* is_guard_notice — нажатая кнопка слышна стражу */
|
||||
|
||||
/* Коды тайлов (fg & 0x1F), которые анимируются. */
|
||||
#define TILE_SPIKE 0x02
|
||||
@@ -284,8 +285,12 @@ void pop_trigger_button(uint8_t room, uint8_t tilepos,
|
||||
uint8_t link_timer = get_doorlink_timer(modifier);
|
||||
if (link_timer != 0x1F) { /* не заклинено */
|
||||
set_doorlink_timer(modifier, 5);
|
||||
if (link_timer < 2)
|
||||
pop_add_trob(room, tilepos, 1); /* кнопка нажимается (+ TODO sound) */
|
||||
if (link_timer < 2) {
|
||||
pop_add_trob(room, tilepos, 1); /* кнопка нажимается */
|
||||
/* seg007:766: щелчок кнопки — шум, страж оборачивается. Звука у
|
||||
* нас нет, флаг обязан быть (BUG-GUARD-DEAF-1). */
|
||||
is_guard_notice = 1;
|
||||
}
|
||||
do_trigger_list(modifier, button_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ static uint8_t room_fg[30], room_bg[30];
|
||||
/* lcol_fg/rcol_fg — ШЕСТЬ записей: [0..2] кромочная колонка соседа (col9/col0),
|
||||
* [3..5] следующая за ней (col8/col1) для коллизии, когда Kid стоит В шве и
|
||||
* смотрит наружу (колонка −2 / 11). См. pop_level.h. */
|
||||
static uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[10];
|
||||
static uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
|
||||
static uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
|
||||
static uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
||||
static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
|
||||
|
||||
@@ -25,6 +25,8 @@ OBJS_phys := build/eng_pop_geom.rel build/eng_pop_kid.rel \
|
||||
|
||||
# Пробный набор (разведка, без эталонов) линкуется тем же составом.
|
||||
OBJS_grab := $(OBJS_phys)
|
||||
# t_wall — удар о стену в воздухе (BUG-JUMPWALL-1), состав тот же.
|
||||
OBJS_wall := $(OBJS_phys)
|
||||
|
||||
include $(TESTKIT)/host-tests.mk
|
||||
|
||||
|
||||
@@ -194,3 +194,7 @@ kframe pop_gframe;
|
||||
int8_t can_guard_see_kid;
|
||||
int8_t guard_refrac, kid_sword_strike;
|
||||
int8_t offguard, holding_sword;
|
||||
/* «Кид нашумел» — физика взводит его на ударе о стену, приземлении и
|
||||
* обрушенной плите (BUG-GUARD-DEAF-1); читает только автопилот стража,
|
||||
* которого в наборе нет. */
|
||||
int8_t is_guard_notice;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* t_wall.c — удар о стену В ВОЗДУХЕ (BUG-JUMPWALL-1).
|
||||
*
|
||||
* Наблюдение с приёмки уровня 3 (комната 14): Кид прыгает, не долетает и
|
||||
* вместо удара о кладку с последующим сползанием ВДОЛЬ неё пролетает
|
||||
* сквозь стену «по свободной траектории». Класс отказа шире одной
|
||||
* комнаты, поэтому проверяем его отдельным набором, а не сценарием в
|
||||
* t_phys: там характеризация (трасса == эталон), здесь — утверждение о
|
||||
* поведении («сквозь кладку нельзя»), которое не должно молча
|
||||
* переснашиваться вместе с эталонами.
|
||||
*
|
||||
* Оригинал: bumped_fall (seg004:04E4) на ударе гасит `Char.fall_x`, то есть
|
||||
* горизонтальный снос обнуляется и дальше Кид падает вертикально. Кто
|
||||
* решает, что удар был, — check_collisions (seg004:0004) + check_bumped.
|
||||
*
|
||||
* Проверка — свипом по стартовому X, а не одним прыжком: исход зависит от
|
||||
* фазы, и «с одной X работает» ничего не доказывает (ровно так и вышло с
|
||||
* первым сценарием — из центра плиты стена держит).
|
||||
*/
|
||||
#include "tcheck.h"
|
||||
#include "scene.h"
|
||||
#include "stubs.h"
|
||||
#include "pop_kid.h"
|
||||
#include "pop_map.h"
|
||||
|
||||
#define E 0
|
||||
#define F 1
|
||||
#define W 20
|
||||
|
||||
/* Комната 14 уровня 3 с одной правкой: шахте колонок 6–7 подложено дно
|
||||
* (ряд 2), потому что соседних комнат у сцены нет, а в игре Кид улетает
|
||||
* вниз в комнату 13. Остальное — как в данных: площадка (0,8), провал
|
||||
* (0,6)/(0,7), СТЕНА в колонке 5 рядов 1–2, площадка (0,5) за ней. */
|
||||
static const uint8_t room14[30] = {
|
||||
F, F, F, E, E, F, E, E, F, E,
|
||||
W, W, W, E, E, W, E, E, W, E,
|
||||
W, W, W, F, F, W, F, F, W, W,
|
||||
};
|
||||
|
||||
/* Плита (0,8) по X: колонка 8 — это [177, 191), плюс шов с пустой (0,9). */
|
||||
#define X_LO 177
|
||||
#define X_HI 196
|
||||
|
||||
/* Прыгнуть влево со стартовой X. Возврат — код исхода:
|
||||
* 'L' — долетел до площадки (0,5) и стоит на ней (ряд 0) — законно;
|
||||
* '.' — не долетел и упал на дно шахты колонок 6–7 — тоже законно;
|
||||
* цифра — колонка, в которой Кид оказался ЛЕВЕЕ стены, то есть прошёл
|
||||
* сквозь кладку колонки 5 (это и есть баг). */
|
||||
static char jump_left_from(uint8_t x)
|
||||
{
|
||||
sc_room(room14, 14);
|
||||
sc_kid_at_x(8, 0, x, -1 /* лицом влево */);
|
||||
sc_trace_clear();
|
||||
/* Клавиши держим только на сам прыжок: если удерживать их всю сцену,
|
||||
* Кид, ДОЛЕТЕВШИЙ до (0,5), тут же прыгает с неё второй раз — и исход
|
||||
* говорит уже не о стене, а о втором прыжке. */
|
||||
sc_run(SC_L | SC_U, 6);
|
||||
sc_run(0, 34);
|
||||
if (!sc_len) return '?';
|
||||
{
|
||||
int8_t col = sc_trace[sc_len - 1].col;
|
||||
int8_t row = sc_trace[sc_len - 1].row;
|
||||
if (row == 0) return 'L'; /* остался на верхнем ряду */
|
||||
if (col >= 6) return '.'; /* сполз вдоль стены в шахту */
|
||||
return (char)('0' + (col < 0 ? 0 : col));
|
||||
}
|
||||
}
|
||||
|
||||
static void put(char c)
|
||||
{
|
||||
if (tc_result.logn < TC_LOGSZ) tc_result.log[tc_result.logn++] = (uint8_t)c;
|
||||
}
|
||||
static void puts_(const char *s) { while (*s) put(*s++); }
|
||||
|
||||
TC_TEST(wall_stops_undershot_jump)
|
||||
{
|
||||
uint8_t x, bad = 0;
|
||||
/* Карта исходов по всей ширине плиты: 'L' — долетел до (0,5), '.' —
|
||||
* сполз вдоль стены в шахту, цифра — просочился сквозь кладку. */
|
||||
puts_("x=177..196: ");
|
||||
for (x = X_LO; x <= X_HI; x++) {
|
||||
char r = jump_left_from(x);
|
||||
put(r);
|
||||
if (r != 'L' && r != '.') bad++;
|
||||
}
|
||||
put('\n');
|
||||
TC_EQ(bad, 0);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
sc_init();
|
||||
TC_RUN(wall_stops_undershot_jump);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user