Files
Sprinter-SDCC/applications/PoP/roomtest/bug_list.md
T
snark13 9234c03020 BUG-GATE-PASS-1: история флагов коллизии переживает боковой переход
Оригинал (seg004:0004) индексирует флаги перекрытия колонкой ВНУТРИ
разрешённой комнаты и хранит рядом её номер, поэтому решётка комнаты 8
остаётся в своём слоте и после перехода 8->6: переход флага 0->1 виден,
bumped() срабатывает.  У нас индекс — колонка отрисованной комнаты, тот же
тайл менял слот, и enter_room вынужден был выбрасывать историю целиком —
на кадре входа бампа не было, и Кид с разбега уходил сквозь закрытые ворота.

Вариант B (сдвиг вместо тега комнаты): при БОКОВОМ переходе история не
выбрасывается, а перенумеровывается на 10 слотов.  check_leave двигает x
ровно на ∓140 = 10 тайлов, координата грани едет на те же 140 вместе с
габаритом Кида — сами флаги инвариантны, меняется только номер слота.
Сдвигаются curr/above/below (prev на следующем кадре всё равно перезапишет
move_coll_to_prev), освободившиеся слоты = 3 «уже перекрывал».
Вверх/вниз и прочие входы в комнату — по-прежнему полная инвалидация.

Дословный вариант A (10 слотов + массив номеров комнат) не взят: он тянет
за собой сужение окна перебора колонок, то есть отказ от FIX_COLL_FLAGS.
Заведён docs/impl_diff.md — список осознанных расхождений с SDLPoP; правило
«фиксировать расхождение» в обоих CLAUDE.md теперь указывает туда.

tests-host: все 5 наборов прошли.  Приёмка в MAME впереди.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 12:25:38 +03:00

37 KiB
Raw Blame History

roomtest — ОТКРЫТЫЕ баги и незакрытые оптимизации

Только то, что не закрыто. Всё закрытое (и, что важнее, разбор корней) переехало в bug_closed.md: прежде чем заводить новый баг, грепни там по симптому.

Приоритеты работ — в TASKS_OPEN.md (закрытые задачи с протоколами — TASKS_CLOSED.md), а не здесь. Правило проекта: механику сверять с ../SDLPoP/src/ ДО кодинга.

Ревизия списка: 2026-08-07 — прогон ВСЕХ комнат уровней 1 и 2 (пользователь). Крупных багов нет. Поэтому в bug_closed.md уехали разом: чек-листы ручной перепроверки фиксов (2026-08-03, обе волны), таблица обхода 24 комнат уровня 1 и таблица сырых наблюдений того же прогона — все они закрыты этим проходом. С прогона пришли три новые записи, все по уровню 2 (сырые формулировки — bugs_level2.md).

ID что тип статус
BUG-CHEAT-FIGHT-1 +/ в бою с вынутым мечом → Кид теряет управление Major (чит) открыт
BUG-GATE-PASS-1 проход сквозь закрытую решётку шва (комн. 8 -> 6) Major фикс сделан 2026-08-09 (сдвиг истории флагов, вариант B) — ждёт приёмки в MAME
BUG-SPIKE-1 пики залипают выдвинутыми рядом с Кидом низкий маловоспроизводимый: ни сценарием, ни попиксельной подгонкой X не поднимается
BUG-CHOMP-JUMP-1 прыжок с места вплотную к чомперу: кадр с отступом назад низкий маловоспроизводимый: на повторе не поднялся; на пререлиз
T-1 пики перерисовываются безусловно оптимизация открыт
T-2 Кид перерисовывается в покое оптимизация ЗАКРЫТ 2026-08-08DRAW-COST шаг 1

Уровень 2 — баги с приёмки

Приёмка уровня 2 закрыта (L2-PASS): smoke 2026-08-05 + обход всех комнат 2026-08-07. Карта содержимого уровня (что где стоит по res2002.bin, какие кнопки какие ворота открывают) — там же, по ней видно, «механика не сработала» это или «так и задумано».

Закрыто с этой волны: BUG-LOOSE-3 — чёрный бар под упавшей плитой-потолком; BUG-GUARD-DEAF-1 — страж не оборачивался на вернувшегося Кида; BUG-GUARD-SPLASH-1 — «брызги» при попадании по стражу; BUG-SWORD-GHOST-1 — меч, спрятанный посреди боя после перехода комнаты (корень — мнимая стена за краем комнаты в get_tile; кэш соседей расширен до полных комнат); BUG-GUARD-COLOR-1 — цвет стража и его полосы HP теперь берётся из guards_color уровня (2026-08-07).

Ниже — то, что осталось открытым после прогона 2026-08-07.

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, а там единственный выход из режима боя —

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, так что проверить сценарием, а не менять вслепую.

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 построчно и совпадают дословно.

ВТОРОЕ НАБЛЮДЕНИЕ (то же место, сравнение с оригиналом). Кид уронил плиту-потолок и спрыгнул вниз; пики выдвинулись и «спрятались не все — часть артефактов осталась». Скриншоты рядом: наш и SDLPoP в той же позе. У нас из-под щебня торчат белые острия, у оригинала пик не видно ВООБЩЕ.

ГИПОТЕЗА «ТРИГГЕР СРАБАТЫВАЕТ РАНО» ПРОВЕРЕНА И ОПРОВЕРГНУТА (замер 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 текущего кадра); если разойдутся позиции — это отдельный баг посадки, а пики — его следствие.


Уровень 3 — не баги, а неначатые задачи

Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал четыре наблюдения. Два были настоящими багами и закрыты в тот же день (разбор — в bug_closed.md: BUG-JUMPWALL-1 и BUG-SEAM-WEDGE-1). Оставшиеся два — не баги, а неначатые задачи:

наблюдение что это на самом деле
к.22: чомпер (2,6) не анимируется и вообще не рисуется L3-CHOMP — механики чомперов НЕТ. В таблице тайлов pop_bg.c:211 строка 12 chomper рисует только основание, правую грань и низ; самих челюстей (спрайт из chtab, draw_tile_anim seg008) нет вовсе. Так и должно выглядеть до порта
к.10: скелет не оживает L3-SKELcheck_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

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) хранит флаги так:

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 не будет никогда.

Фикс сделан 2026-08-09 (вариант B — сдвиг истории), ЖДЁТ ПРИЁМКИ В MAME

Рассматривались два пути:

  • A, дословный порт. 10 слотов + параллельный массив номера комнаты, индекс по колонке разрешённой комнаты, бамп только при совпадении номеров; pop_coll_invalidate() тогда не нужен вовсе. Не взят: оригиналу хватает десяти слотов только потому, что он перебирает узкое окно вокруг Кида, а мы намеренно считаем все четырнадцать колонок (FIX_COLL_FLAGS, чтобы не оставалось протухших ячеек) — при таком переборе колонки −2/−1 и 8/9 сядут в одни слоты 8/9. То есть A тянет за собой ещё и сужение окна.
  • B, сдвиг истории (сделано). Индексацию оставляем свою, но при БОКОВОМ переходе историю не выбрасываем, а перенумеровываем на 10 слотов: check_leave двигает x ровно на ∓140 = 10 тайлов, координата грани (pop_x_bump) едет на те же 140 вместе с габаритом Кида, значит сами флаги инвариантны — меняется только номер слота.

Реализация: pop_coll_shift(int8_t dcol) в pop_map.c сдвигает coll_curr/above/below (не prev — его на следующем кадре всё равно перезапишет move_coll_to_prev), освободившиеся слоты = 3 («уже перекрывал», бампа нет — ровно то, что в оригинале даёт несовпадение номера комнаты). enter_room_side зовёт её сразу после pop_map_set_edges: side == 0 (ушёл влево) → +10, side == 1 (вправо) → 10. Переходы вверх/вниз и все прочие входы в комнату остаются на полной инвалидации — там колонки не сдвигаются, но тайлы под ними уже из другой комнаты.

Расхождение с дословным вариантом A записано в ../docs/impl_diff.md (D-1).

Осторожно: это сердце коллизии, вокруг которого разбирался BUG-SEAM-PINGPONG (см. bug_closed.md). Приёмка:

  1. Комнаты 6 ↔ 8 уровня 1, закрытая решётка, обе стороны.
  2. Мелким шагом (упереться) И с разбега (не пройти насквозь) — сценарий выше воспроизводится с Kid.x = 170, комната 8, ряд 0, лицом вправо.
  3. Пинг-понг у шва не вернулся (экран не перескакивает туда-сюда).
  4. make -C tests-host — прошли все 5 наборов (2026-08-09).

Инструмент для сверки готов: 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 (копию из ОЗУ-копии акселератора) плюс блиты. Поэтому буквальный порт «перерисовываем безусловно» корректен, но дорог.

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, отдельно не окупается.

T-2. Idle-skip — ЗАКРЫТ 2026-08-08

Сделан как шаг 1 задачи DRAW-COST (коммит a25ce58), и шире, чем формулировался здесь: пропускается не только Кид, а ЛЮБОЙ персонаж, у которого с прошлой отрисовки этой страницы дабл-буфера не изменился ни один вход отрисовки, — включая труп стража и ждущего стража. Условие «обе страницы уже получили это состояние», которого требовала эта запись, выполнено само собой: снимок входов хранится ПО СТРАНИЦАМ.

Замер: комната 1.3 с трупом стража, Кид стоит — 210 % -> 116 % кадрового периода, ноль вызовов pop_heal_fast за кадр. Контракт — шапка pop_cdraw.h, разбор и что делать дальше — TASKS_OPEN.md#draw-cost.

Связь с T-1 сработала как и предсказано: пока Кида не перерисовываем, heal'а нет, стирать пики нечем. Но T-1 остаётся открытым — трогать пики безусловно мы всё ещё продолжаем, когда Кид рядом ДВИЖЕТСЯ.


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.
  • Комнаты 13, 18, 24 уровня 1 недостижимы в обычной игре — это свойство данных уровня (разбор — «НЕ БАГИ» в bug_closed.md); приоритет багов в них низкий. Аналогично 23/24 на уровне 3.