c93a348b4a
Сценарий: ур.1 комн.8, ряд 0, x=170, лицом вправо, решётка шва закрыта — разбежаться вправо и не отпускать. Кид проходит сквозь решётку. Корень (две трассы, наша и оригинала, стартовая позиция совпала до пикселя): смена комнаты в этой точке ШТАТНАЯ и у нас, и в оригинале — leave_room вправо блокируют только doortop. Оригинал держит Кида бампом СРАЗУ ПОСЛЕ перехода, потому что check_collisions хранит флаги по паре (колонка в СВОЕЙ комнате, номер комнаты) и сравнивает prev/curr только при совпадении номеров: решётка комнаты 8 и до, и после перехода лежит в слоте 9 с room=8, история не рвётся. У нас индекс — колонка относительно отрисованной комнаты, при переходе все индексы уезжают на 10, поэтому enter_room зовёт pop_coll_invalidate, тот подавляет бамп на кадре входа, а дальше перехода флага 0->1 уже не будет. Что делать — порт индексации оригинала (10 слотов по колонке своей комнаты + массив её номера); тогда pop_coll_invalidate не нужен вовсе. Осторожно: это сердце коллизии из BUG-SEAM-PINGPONG. SDLPoP инструментирован для сверки: POP_TRACE=1 включает покадровую печать Char + обе пары краёв, плюс маркеры BUMPED и LEAVE. В bug_list записана и команда сборки на macOS (штатный Makefile требует pkg-config, которого нет).
448 lines
35 KiB
Markdown
448 lines
35 KiB
Markdown
# roomtest — ОТКРЫТЫЕ баги и незакрытые оптимизации
|
||
|
||
Только то, что **не закрыто**. Всё закрытое (и, что важнее, разбор корней)
|
||
переехало в [`bug_closed.md`](bug_closed.md): прежде чем заводить новый баг,
|
||
грепни там по симптому.
|
||
|
||
Приоритеты работ — в [`TASKS_OPEN.md`](TASKS_OPEN.md) (закрытые задачи с
|
||
протоколами — [`TASKS_CLOSED.md`](TASKS_CLOSED.md)), а не здесь. Правило
|
||
проекта: механику сверять с `../SDLPoP/src/` ДО кодинга.
|
||
|
||
**Ревизия списка: 2026-08-07 — прогон ВСЕХ комнат уровней 1 и 2**
|
||
(пользователь). Крупных багов нет. Поэтому в [`bug_closed.md`](bug_closed.md)
|
||
уехали разом: чек-листы ручной перепроверки фиксов (2026-08-03, обе волны),
|
||
таблица обхода 24 комнат уровня 1 и таблица сырых наблюдений того же прогона —
|
||
все они закрыты этим проходом. С прогона пришли **три новые записи**, все по
|
||
уровню 2 (сырые формулировки — [`bugs_level2.md`](bugs_level2.md)).
|
||
|
||
| ID | что | тип | статус |
|
||
|----|-----|-----|--------|
|
||
| [BUG-CHEAT-FIGHT-1](#bug-cheat-fight-1) | `+`/`−` в бою с вынутым мечом → Кид теряет управление | Major (чит) | открыт |
|
||
| [BUG-GATE-PASS-1](#bug-gate-pass-1) | проход сквозь закрытую решётку шва (комн. 8 -> 6) | Major | **воспроизведён, корень найден 2026-08-08**: история флагов коллизии не привязана к комнате |
|
||
| [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводимый: ни сценарием, ни попиксельной подгонкой X не поднимается |
|
||
| [BUG-CHOMP-JUMP-1](#bug-chomp-jump-1) | прыжок с места вплотную к чомперу: кадр с отступом назад | **низкий** | маловоспроизводимый: на повторе не поднялся; на пререлиз |
|
||
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт |
|
||
| ~~T-2~~ | Кид перерисовывается в покое | оптимизация | **ЗАКРЫТ 2026-08-08** — [DRAW-COST шаг 1](TASKS_OPEN.md#draw-cost) |
|
||
|
||
---
|
||
|
||
<a id="уровень-2"></a>
|
||
# Уровень 2 — баги с приёмки
|
||
|
||
Приёмка уровня 2 закрыта ([L2-PASS](TASKS_CLOSED.md#l2-pass)): smoke
|
||
2026-08-05 + обход всех комнат 2026-08-07. Карта содержимого уровня (что где
|
||
стоит по `res2002.bin`, какие кнопки какие ворота открывают) — там же, по ней
|
||
видно, «механика не сработала» это или «так и задумано».
|
||
|
||
**Закрыто с этой волны:** [BUG-LOOSE-3](bug_closed.md) — чёрный бар под
|
||
упавшей плитой-потолком; [BUG-GUARD-DEAF-1](bug_closed.md) — страж не
|
||
оборачивался на вернувшегося Кида;
|
||
[BUG-GUARD-SPLASH-1](bug_closed.md#bug-guard-splash-1) — «брызги» при
|
||
попадании по стражу; [BUG-SWORD-GHOST-1](bug_closed.md#bug-sword-ghost-1) —
|
||
меч, спрятанный посреди боя после перехода комнаты (корень — мнимая стена за
|
||
краем комнаты в `get_tile`; кэш соседей расширен до полных комнат);
|
||
[BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1) — цвет стража и его
|
||
полосы HP теперь берётся из `guards_color` уровня (2026-08-07).
|
||
|
||
Ниже — то, что осталось открытым после прогона 2026-08-07.
|
||
|
||
<a id="bug-cheat-fight-1"></a>
|
||
## BUG-CHEAT-FIGHT-1. Чит `+`/`−` в бою: Кид остаётся в режиме боя и теряет управление
|
||
|
||
**Наблюдение (пользователь, 2026-08-07).** Если нажать `+`/`−` (ROOMNAV,
|
||
переход по комнатам) в момент, когда Кид вытащил меч для битвы, — в новой
|
||
комнате Кид не управляется: нажатия стрелок игнорируются.
|
||
|
||
**Корень (прочитан по коду, сверен с seg005).** Чит ROOMNAV
|
||
(`roomtest.c:576`) телепортирует и сбрасывает позу и HP —
|
||
`enter_room()`, `kid_init(SEQ_STAND, …)`, `pop_kid_hp_reset()`, — но **не
|
||
трогает состояние боя**: `Kid.sword` остаётся `SWORD_2_DRAWN`. Диспетчер
|
||
`pop_control` (`pop_ctrl.c:578`) при вынутом мече уходит в
|
||
`control_with_sword`, а там единственный выход из режима боя —
|
||
|
||
```c
|
||
if (Char.frame == FRAME_171_STAND_WITH_SWORD) { /* seg005:987 */
|
||
Char.sword = SWORD_0_SHEATHED;
|
||
seqtbl_offset_char(SEQ_92_PUT_SWORD_AWAY);
|
||
}
|
||
```
|
||
|
||
Стража в новой комнате нет (`can_guard_see_kid` = 0), кадр после
|
||
`kid_init(SEQ_STAND)` — обычная стойка, а не 171, поэтому не срабатывает ни
|
||
`swordfight()`, ни ветка «убрать меч»: `control_with_sword` каждый кадр
|
||
не делает НИЧЕГО, и ввод не доходит до движения. Заклинивание вечное.
|
||
|
||
Это баг **нашего чита**, а не порта: в оригинале телепорта между комнатами
|
||
нет, и в режим боя без соперника попасть нечем.
|
||
|
||
**Как чинить.** В ветке ROOMNAV телепорт трактовать как выход из боя (то же
|
||
самое, что делает `pop_start_level`): `Kid.sword = SWORD_0_SHEATHED`,
|
||
`holding_sword = 0`, сбросить `offguard`/`guard_refrac` и состояние стража
|
||
(`pop_guard_reset()` вызывается по входу в комнату — проверить, что он
|
||
обнуляет `Opp`). Меч в инвентаре (`pop_have_sword`) при этом НЕ терять.
|
||
Второй кандидат на ту же болезнь — чит `K` (убить стража) в момент, когда
|
||
Кид в стойке с мечом, но не в кадре 171: там оригинал сам доводит до 171
|
||
через `swordfight`, так что проверить сценарием, а не менять вслепую.
|
||
|
||
<a id="bug-spike-1"></a>
|
||
## BUG-SPIKE-1. Пики залипают выдвинутыми — НИЗКИЙ ПРИОРИТЕТ, МАЛОВОСПРОИЗВОДИМ
|
||
|
||
> **Статус 2026-08-05, вечер.** Пользователь **повторить не смог**, а замер
|
||
> (ниже) показал, что чистый пробег по убранным пикам убивает штатно. То
|
||
> есть в смертельности бага, похоже, нет вовсе: наблюдался частный случай —
|
||
> пики, УЖЕ выдвинутые полностью (h = 1), для бегущего безвредны и в
|
||
> оригинале. Запись оставлена открытой ровно из-за визуального расхождения
|
||
> со скриншотом SDLPoP (у нас острия торчат, у него убраны) — см. конец.
|
||
>
|
||
> **Как получить состояние нарочно:** подойти к пикам вплотную (выдвинутся),
|
||
> отступить на полшага, НЕ выходя из зоны срабатывания, и пробежать по ним.
|
||
> Пользователь пробовал и это, и попиксельную подгонку X читом `[`/`]` —
|
||
> не поднялось. Вывод для будущего разбора: состояние **не чисто
|
||
> позиционное**, одной шириной габарита его не объяснить; следующий
|
||
> подозреваемый — момент, в который `process_trobs` застаёт модификатор
|
||
> относительно кадра Кида.
|
||
|
||
**Наблюдение (пользователь, 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 построчно и совпадают дословно.
|
||
|
||
**ВТОРОЕ НАБЛЮДЕНИЕ (то же место, сравнение с оригиналом).** Кид уронил
|
||
плиту-потолок и спрыгнул вниз; пики выдвинулись и «спрятались не все — часть
|
||
артефактов осталась». Скриншоты рядом:
|
||
[наш](bugscreens/l2-r6-spikes-ours.png) и
|
||
[SDLPoP](bugscreens/l2-r6-spikes-sdlpop.png) в той же позе. У нас из-под
|
||
щебня торчат белые острия, у оригинала пик не видно ВООБЩЕ.
|
||
|
||
**ГИПОТЕЗА «ТРИГГЕР СРАБАТЫВАЕТ РАНО» ПРОВЕРЕНА И ОПРОВЕРГНУТА (замер
|
||
2026-08-05, MAME, watchpoint на `room_modif[13]` комнаты 6).** Чистый
|
||
пробег по УБРАННЫМ пикам убивает штатно:
|
||
|
||
```
|
||
запись modif=1 : кадр 11 (беговой), x=112, curr_col=2 ← пики пошли вверх
|
||
запись modif=2 : кадр 12 (беговой), x=117, curr_col=3 ← Кид уже НА тайле, h=2
|
||
запись modif=3 : кадр 177 (frame_177_spiked) ← напоролся
|
||
```
|
||
|
||
То есть `check_spike_below`, `check_spiked`, `is_spike_harmful` и тайминг
|
||
выдвижения работают правильно, и «раннего» триггера нет.
|
||
|
||
**Настоящий корень — пики ЗАЛИПАЮТ выдвинутыми.** Пока габарит Кида
|
||
накрывает колонку пики, `check_spike_below` каждый кадр зовёт
|
||
`start_anim_spike`, а тот при отрицательном модификаторе переставляет его
|
||
обратно в 0x8F — отсчёт до уборки не доходит. А выдвинутые пики (h = 1)
|
||
для бегущего БЕЗВРЕДНЫ по правилам оригинала. Отсюда обе жалобы: пробег
|
||
по уже вышедшим пикам не убивает, и они же остаются торчать на экране.
|
||
|
||
**Что осталось выяснить (и это единственный открытый вопрос).** Код
|
||
`start_anim_spike` у нас с оригиналом совпадает дословно, значит оригинал
|
||
тоже удерживал бы пики, стой Кид там же. На скриншоте SDLPoP в похожей
|
||
позе пики УБРАНЫ — то есть его Кид стоит чуть левее и его габарит колонку
|
||
пики уже не задевает. Разница в 2–3 пикселя посадки, а у нас такие
|
||
расхождения по X уже ловились (см. заметку в BUG-GRAB-1: после касания
|
||
площадки SDLPoP уводит Кида на 134, мы — на 141).
|
||
|
||
**Как закрывать:** инструментировать SDLPoP (печать `char_x_left/right`,
|
||
`left/right_checked_col` и модификатора пики каждый кадр), проиграть ту же
|
||
сцену — падение плиты-потолка в комнате 6 и остановку на щебне — и сверить
|
||
с нашей трассой ПОЗИЦИЮ КИДА после приземления. Если позиции совпадут, а
|
||
диапазоны колонок разойдутся — виноват габарит (`kid_fp` против
|
||
`set_char_collision` текущего кадра); если разойдутся позиции — это отдельный
|
||
баг посадки, а пики — его следствие.
|
||
|
||
---
|
||
|
||
<a id="уровень-3"></a>
|
||
# Уровень 3 — не баги, а неначатые задачи
|
||
|
||
Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал четыре наблюдения. Два
|
||
были настоящими багами и **закрыты в тот же день** (разбор — в
|
||
[`bug_closed.md`](bug_closed.md): **BUG-JUMPWALL-1** и **BUG-SEAM-WEDGE-1**).
|
||
Оставшиеся два — не баги, а неначатые задачи:
|
||
|
||
| наблюдение | что это на самом деле |
|
||
|------------|------------------------|
|
||
| к.22: чомпер (2,6) не анимируется и **вообще не рисуется** | [L3-CHOMP](TASKS_OPEN.md#l3-chomp) — механики чомперов НЕТ. В таблице тайлов `pop_bg.c:211` строка `12 chomper` рисует только основание, правую грань и низ; самих челюстей (спрайт из `chtab`, `draw_tile_anim` seg008) нет вовсе. Так и должно выглядеть до порта |
|
||
| к.10: скелет не оживает | [L3-SKEL](TASKS_OPEN.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 |
|
||
|
||
Приёмки уровня 3 (полного обхода комнат) ещё не было — она осмысленна только
|
||
после L3-CHOMP/L3-SKEL.
|
||
|
||
---
|
||
|
||
# Открытые баги уровня 1
|
||
|
||
<a id="bug-gate-pass-1"></a>
|
||
## BUG-GATE-PASS-1. Проход сквозь закрывшуюся решётку — **КОРЕНЬ НАЙДЕН 2026-08-08**
|
||
|
||
> **ВОСПРОИЗВЕДЁН И РАЗОБРАН.** Сценарий: уровень 1, комната 8, ряд 0,
|
||
> `Kid.x = 170`, лицом ВПРАВО, решётка шва (комната 8, колонка 9) ЗАКРЫТА.
|
||
> Разбежаться вправо и НЕ ОТПУСКАТЬ клавишу. Кид проходит сквозь решётку и
|
||
> убегает вглубь комнаты 6.
|
||
>
|
||
> Ниже — новый разбор; всё, что было раньше (гипотеза про x > 205), не
|
||
> подтвердилось и оставлено в конце для истории.
|
||
|
||
### Корень: история флагов коллизии индексируется НЕ ТАК, как в оригинале
|
||
|
||
Сняты две трассы — наша (брейкпоинт на резидентном `kid_tick`) и оригинала
|
||
(SDLPoP, собран с `POP_TRACE=1`, печать в `play_kid_frame` + маркеры в
|
||
`bumped` и `exit_room`). Стартовая позиция совпала до пикселя, поэтому
|
||
сравнение построчное.
|
||
|
||
**Оригинал:**
|
||
|
||
```
|
||
LEAVE dir=1 x=61 cxl=184 cxr=201 room=6 смена комнаты — ШТАТНАЯ
|
||
BUMPED dx=-2 push=-1 x=63 tile=4 col=9 и СРАЗУ бамп о решётку, откат
|
||
KID f=15 x=60 act=0 col=-1 room=6 drawn=6 сел за решёткой, стоит
|
||
```
|
||
|
||
**Мы:**
|
||
|
||
```
|
||
x 170 -> 196 бегом, col 7 -> 8 -> 9, НИ ОДНОГО бампа
|
||
x=198 -> room 8 -> 6
|
||
при удержании ВПРАВО доезжает до col 1 комнаты 6 и бежит дальше
|
||
```
|
||
|
||
Смена комнаты в этой точке правильна и у нас, и у оригинала: `leave_room`
|
||
(seg002:423) вправо блокируют ТОЛЬКО `doortop`-тайлы, решётки в списке нет, а
|
||
порог `char_x_right >= 201` совпадает с плоскостью ворот
|
||
(`x_bump[9+FIRST_ONSCREEN_COLUMN] + TILE_MIDX + wall_dist_from_left[1]`
|
||
= 191 + 10). Оригинал полагается не на запрет ухода, а на бамп СРАЗУ ПОСЛЕ
|
||
перехода.
|
||
|
||
**Почему бамп есть у них и нет у нас.** `check_collisions` (seg004:0004)
|
||
хранит флаги так:
|
||
|
||
```c
|
||
row_coll_flags_ptr[tile_col] = curr_flags; // индекс — колонка В РАЗРЕШЁННОЙ комнате
|
||
row_coll_room_ptr [tile_col] = curr_room; // и рядом — НОМЕР ЭТОЙ КОМНАТЫ
|
||
...
|
||
if (curr_row_coll_room[column] >= 0 &&
|
||
prev_coll_room[column] == curr_row_coll_room[column]) { /* только тогда сравниваем */ }
|
||
```
|
||
|
||
То есть история привязана к паре **(колонка внутри своей комнаты, комната)**.
|
||
Решётка комнаты 8 и до перехода, и после лежит в слоте 9 с `room = 8` —
|
||
переход 8->6 историю НЕ рвёт, переход флага 0->1 виден, бамп срабатывает.
|
||
|
||
У нас массив `coll_prev/coll_curr` индексируется колонкой ОТНОСИТЕЛЬНО
|
||
отрисованной комнаты (−2…11). При смене комнаты все индексы уезжают на 10,
|
||
сопоставить прошлый кадр с текущим нечем, и `enter_room` вынужден звать
|
||
`pop_coll_invalidate()`; тот ставит «прошлые флаги = уже перекрывал» (3), то
|
||
есть ПОДАВЛЯЕТ бамп на кадре входа. А к следующему кадру Кид уже за
|
||
плоскостью ворот — перехода 0->1 не будет никогда.
|
||
|
||
### Что делать
|
||
|
||
Порт индексации оригинала: хранить флаги в 10 слотах ПО КОЛОНКЕ РАЗРЕШЁННОЙ
|
||
комнаты плюс параллельный массив её номера, и сравнивать prev/curr только при
|
||
совпадении номеров. Тогда `pop_coll_invalidate()` не нужен вовсе — история
|
||
сама «не совпадает» там, где колонка сменила комнату.
|
||
|
||
**Осторожно:** это сердце коллизии, вокруг которого разбирался
|
||
BUG-SEAM-PINGPONG (см. `bug_closed.md`). После правки обязателен прогон
|
||
швов: комнаты 6/8 первого уровня в обе стороны, осторожный шаг и разбег, плюс
|
||
проверка, что пинг-понг не вернулся.
|
||
|
||
**Инструмент для сверки готов:** SDLPoP собран с трассой, включается
|
||
`POP_TRACE=1` (правки в `seg000.c` play_kid_frame, `seg004.c` bumped,
|
||
`seg002.c` exit_room, чтение переменной — `main.c`). Сборка на macOS:
|
||
`make CFLAGS="-std=gnu99 -O2 -I/opt/homebrew/include -Wno-implicit-function-declaration" LIBS="-L/opt/homebrew/lib -lSDL2 -lSDL2_image"`
|
||
(в системе нет pkg-config, штатный Makefile без этого не собирается).
|
||
|
||
---
|
||
|
||
### История: прежний разбор (гипотеза НЕ подтвердилась)
|
||
|
||
**Статус: наблюдался один раз (2026-08-03), воспроизвести не удалось ни
|
||
тогда, ни прогоном всех комнат уровня 1 (2026-08-07).** Заведён, чтобы
|
||
наблюдение не потерялось; закрывать нельзя — ни как исправленный, ни как
|
||
«не баг», пока нет надёжного сценария. Оговорка: BUG-GATEMOD-1, из-за
|
||
которого решётка стартовала не в том состоянии, с тех пор закрыт, и
|
||
наблюдение могло быть его следствием.
|
||
|
||
**Что наблюдалось.** Комната 5: Кид стоял НА тайле решётки (0,9)
|
||
и ждал, пока она опустится. После закрытия пошёл вправо — прошёл в комнату 1
|
||
и упал на (1,1).
|
||
|
||
**Что уже измерено и в чём загвоздка.** Сразу после наблюдения повторить не
|
||
получилось: в том же месте Кид стоит на `x = 196`, `col = 9`, и решётка его
|
||
ДЕРЖИТ — то есть штатно.
|
||
|
||
Арифметика оригинала объясняет разницу. `is_obstacle` (seg004) ставит
|
||
плоскость блокировки в `x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX`, для
|
||
колонки 9 это **x = 205**. При этом «колонка 9» по `get_tile_div_mod_m7` —
|
||
это `x ∈ [191, 205)`. Пока `curr_col == 9`, Кид гарантированно левее
|
||
плоскости и обязан блокироваться; чтобы пройти, он должен оказаться **правее
|
||
205**, то есть уже на дальней стороне решётки, — и тогда уход вправо законен:
|
||
решётка закрылась у него за спиной, в оригинале она блокирует плоскость, а не
|
||
весь тайл.
|
||
|
||
Отсюда рабочая гипотеза: в момент наблюдения Кид стоял правее 205 (успел зайти
|
||
по тайлу дальше, пока решётка была поднята), и поведение штатное. Но повторить
|
||
эту позу и снять `x` пока не удалось, поэтому гипотеза НЕ подтверждена.
|
||
|
||
**Что снять в следующий раз** (без этих чисел вопрос не закрыть):
|
||
|
||
1. `Kid.x` и `Kid.curr_col` в момент, когда решётка уже закрылась, а Кид ещё
|
||
стоит на её тайле — до шага вправо;
|
||
2. модификатор решётки (openness) комнаты 5, тайл 9 — `can_bump_into_gate()`
|
||
считает её препятствием только пока `(modif >> 2) + 6 < char_height`, то
|
||
есть пока она опустилась достаточно низко относительно РОСТА кадра;
|
||
3. `Kid.x` покадрово на самом шаге вправо — где именно перестал блокировать.
|
||
|
||
Быстрый способ снять первое: отладочный стоп-кадр (**1** заморозить, **2**
|
||
продолжить), затем чтение `_Kid` из отладчика MAME; подгонка позы по пикселю —
|
||
читы `[`/`]`.
|
||
|
||
**Возможный корень, если гипотеза не подтвердится.** Проверка идёт по колонке,
|
||
которая на шве уже принадлежит СОСЕДНЕЙ комнате (`curr_row_coll_room[]` в
|
||
оригинале); у нас межкомнатная коллизия на шве — исторически проблемное место
|
||
(ср. закрытый BUG-SEAM-PINGPONG). Второй кандидат — `char_height` в
|
||
`can_bump_into_gate()`: если он берётся не от того кадра, решётка может
|
||
перестать считаться препятствием раньше времени.
|
||
|
||
---
|
||
|
||
# Оптимизация отрисовки (записано 2026-07-29)
|
||
|
||
Не баги — план работ. Оба пункта про одно: у оригинала пометка тайла к
|
||
перерисовке стоит копейки (бит в таблице, которая всё равно чистится каждый
|
||
кадр), а у нас каждая такая пометка превращается в реальный heal (копию из
|
||
ОЗУ-копии акселератора) плюс блиты. Поэтому буквальный порт «перерисовываем
|
||
безусловно» корректен, но дорог.
|
||
|
||
<a id="t-1"></a>
|
||
## T-1. Пики: перерисовывать по причине, а не безусловно
|
||
|
||
**Сейчас:** `pop_process_trobs` зовёт `pop_spike_redraw` каждый кадр для
|
||
каждой живой пики в комнате (порт `redraw_21h`, который `animate_spike`
|
||
вызывает вне всяких `if`). Это корректно, но лишнее для пик, до которых
|
||
Киду дела нет.
|
||
|
||
**Надо:** перерисовывать тайл пики, только если
|
||
1. **сменился её видимый кадр** (шаг выдвижения/уборки), ЛИБО
|
||
2. **её кто-то стёр** — а стереть у нас может только heal, то есть тайл
|
||
попал в прямоугольник `kid_heal` этого кадра.
|
||
|
||
Это и есть модель оригинала, просто выраженная флагами: `redraw_at_char`
|
||
(seg003:0576) каждый кадр помечает `set_redraw_fore` тайлы персонажа, причём
|
||
**объединение текущего и предыдущего** прямоугольника
|
||
(`MIN(char_top_row, prev_char_top_row)` и т.д.), а `animate_spike` помечает
|
||
свой тайл. Итог = {тайл сменил кадр} ∪ {тайлы Кида}.
|
||
|
||
**Как:** слой Кида и так считает `cL..cR`/`rT..rB` в `pop_fore_over_kid` —
|
||
пусть публикует их (плюс предыдущие, как в оригинале), а цикл trob'ов
|
||
сравнивает `tilepos` с диапазоном целочисленно. Никаких пересечений
|
||
прямоугольников (см. память `manual_hints_over_auto_detect`).
|
||
|
||
**Приоритет:** отдаётся почти бесплатно ПОСЛЕ T-2, отдельно не окупается.
|
||
|
||
<a id="t-2"></a>
|
||
## T-2. Idle-skip — **ЗАКРЫТ 2026-08-08**
|
||
|
||
Сделан как шаг 1 задачи [DRAW-COST](TASKS_OPEN.md#draw-cost) (коммит
|
||
`a25ce58`), и шире, чем формулировался здесь: пропускается не только Кид, а
|
||
ЛЮБОЙ персонаж, у которого с прошлой отрисовки этой страницы дабл-буфера не
|
||
изменился ни один вход отрисовки, — включая труп стража и ждущего стража.
|
||
Условие «обе страницы уже получили это состояние», которого требовала эта
|
||
запись, выполнено само собой: снимок входов хранится ПО СТРАНИЦАМ.
|
||
|
||
Замер: комната 1.3 с трупом стража, Кид стоит — 210 % -> 116 % кадрового
|
||
периода, ноль вызовов `pop_heal_fast` за кадр. Контракт — шапка
|
||
`pop_cdraw.h`, разбор и что делать дальше — `TASKS_OPEN.md#draw-cost`.
|
||
|
||
Связь с T-1 сработала как и предсказано: пока Кида не перерисовываем, heal'а
|
||
нет, стирать пики нечем. Но T-1 остаётся открытым — трогать пики
|
||
безусловно мы всё ещё продолжаем, когда Кид рядом ДВИЖЕТСЯ.
|
||
|
||
---
|
||
|
||
<a id="bug-chomp-jump-1"></a>
|
||
|
||
## BUG-CHOMP-JUMP-1. Прыжок с места вплотную к чомперу: кадр с отступом назад — НИЗКИЙ ПРИОРИТЕТ, МАЛОВОСПРОИЗВОДИМ
|
||
|
||
> **Статус 2026-08-08.** Наблюдение пользователя на приёмке L3-CHOMP: Кид
|
||
> стоит вплотную СЛЕВА от чомпера лицом вправо, прыжок с места — в анимации
|
||
> проскакивает кадр, где он «чуть отступил назад», и только потом идёт
|
||
> прыжок. В оригинале прыжок идёт прямо с места. **На повторе в тот же
|
||
> заход не воспроизвёлся** — отложено до пререлиза.
|
||
|
||
**Что уже установлено (чтобы не разбирать заново).**
|
||
|
||
1. **Это НЕ анимация.** `seq_3_standing_jump` (SDLPoP `seqtbl.c:382`)
|
||
состоит только из положительных смещений:
|
||
`act(run_jump), f16, f17, dx(2) f18…f22, dx(7) f23, dx(9) f24, dx(5) dy(-6) f25`.
|
||
Ни одного отрицательного `dx` — отката в последовательности нет вовсе.
|
||
Значит отступ даёт `bumped()`, то есть физика посчитала въезд в
|
||
препятствие и выровняла `Char.x` назад.
|
||
|
||
2. **`is_obstacle` для чомпера у нас совпадает с оригиналом** (seg004:037E):
|
||
препятствие только при `modif == 2`, причём именно `== 2`, БЕЗ маски
|
||
`0x7F` — то есть окровавленный чомпер (`0x82`) в ванили не бампит вовсе.
|
||
Проверено, расхождения нет.
|
||
|
||
3. **Прямая трасса прыжка отката НЕ показала.** Ввод «UP и RIGHT
|
||
одновременно» (mame bridge, `key UP 24` + `key RIGHT 24`) даёт чистое
|
||
движение вперёд: `x` 148 -> 155, кадр становится 178 (перемололо). Ни
|
||
одного кадра с уменьшением `x`.
|
||
|
||
**Главная гипотеза — ПОРЯДОК НАЖАТИЙ.** Если ↑ приходит на кадр раньше →,
|
||
то `control_standing` уходит не в `standing_jump()`, а в `up_pressed()` —
|
||
вертикальный прыжок с зацепом, а он **выравнивает `Char.x`**. Отсюда и
|
||
«отступил, потом прыгнул». Проверять надо `check_jump_up` /
|
||
`jump_up_or_grab` / `grab_up_no_floor_behind` (seg005:0836 и далее), а не
|
||
прыжок с места. Внимание на `can_climb_up` (seg005:0828): там у чомпера
|
||
ЕСТЬ спецкейс (`seq_73_climb_up_to_closed_gate` при взгляде ВПРАВО) — он у
|
||
нас портирован (`pop_map.c`, ветка `TILE_MIRROR || TILE_CHOMPER`), но именно
|
||
вокруг него и стоит копать.
|
||
|
||
**Как ловить.** Брейкпоинт на резидентном `_kid_tick` с
|
||
`{printf "f=%d x=%d act=%d col=%d",b@Kid+0,b@Kid+1,b@Kid+6,b@Kid+4; g}` —
|
||
одна строка на кадр, адрес `_Kid` из `.sprinter-cc-roomtest/roomtest.map`
|
||
(после КАЖДОЙ пересборки другой). Плюс стоп-кадр `1` в момент отступа и
|
||
чтение `Kid` из памяти. Метод — memory `z80_profiling_method`.
|
||
|
||
---
|
||
|
||
## Заметки (отладка)
|
||
|
||
- Тестовые клавиши осторожного шага: **J** = шаг влево, **L** = шаг вправо
|
||
(эмуляция Shift+стрелка), см. `pop_ctrl.c` `KBD_DBG_STEP*`. Первый шаг в
|
||
сторону = разворот (как в оригинале safe_step), движение со второго.
|
||
- Читы (`pop_cheat.h`): **K** — убить стража, **I** — бессмертие (toggle),
|
||
**S** — выдать меч, **Shift+L** — следующий уровень, **`[`/`]`** — сдвиг
|
||
Кида на пиксель по X.
|
||
- Респавн после смерти — по **↑** (или авто через `RESPAWN_DELAY`); ставит
|
||
Kid в стартовую позицию УРОВНЯ (`pop_start_level`, порт do_startpos).
|
||
- **ROOMNAV (`=`/`-`) — тоже наш чит**, которого в оригинале не было, как и
|
||
`S`. Все они со временем съедутся в общий блок читов, разрешаемый в
|
||
настройках; пока просто включены (`pop_cheats = 1` в `roomtest.c`).
|
||
Известный баг этого чита — [BUG-CHEAT-FIGHT-1](#bug-cheat-fight-1).
|
||
- Комнаты **13, 18, 24 уровня 1 недостижимы** в обычной игре — это свойство
|
||
данных уровня (разбор — «НЕ БАГИ» в [`bug_closed.md`](bug_closed.md));
|
||
приоритет багов в них низкий. Аналогично 23/24 на уровне 3.
|