Files
Sprinter-SDCC/applications/PoP/roomtest/bug_list.md
T
snark13 073a6e0264 safe_step по оригиналу + BUG-CHOMP-JUMP-1 в низкоприоритетные, T-2 закрыт
safe_step при distance == 0: возвращена ветка оригинала (seg005:0604) —
seq_39 (шаг 11) вместо нашего «шага-1».  Для СТЕНЫ результат прежний: первый
же dx(1) даёт бамп, ровно как в трассе живого SDLPoP из BUG-SEAM-PINGPONG.
Для ЧОМПЕРА бампа нет (в разомкнутой фазе он не препятствие), и оригинал
уносит Кида на все 11 px — а мы шагали на один.  Это и был «микрошаг вместо
нормального короткого шага» перед челюстями.
ВНИМАНИЕ на приёмке ур.1: это тот самый safe_step из BUG-SEAM-PINGPONG —
проверить комнату 6, осторожный шаг вплотную к воротам шва.

BUG-CHOMP-JUMP-1 (низкий, маловоспроизводим, на пререлиз): прыжок с места
вплотную к чомперу иногда даёт кадр с отступом назад.  В запись сложено всё,
что выяснено: seq_3_standing_jump состоит ТОЛЬКО из положительных dx (значит
отступ даёт bumped, а не анимация); is_obstacle для чомпера у нас совпадает
с оригиналом; прямая трасса (UP+RIGHT одновременно) отката не показала —
главная гипотеза в порядке нажатий (↑ раньше → уводит в up_pressed с
выравниванием x).  Там же метод ловли.

T-2 (idle-skip) закрыт — сделан шире, чем формулировался, как DRAW-COST
шаг 1 (a25ce58).
2026-08-08 19:46:34 +03:00

29 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 проход сквозь закрывшуюся решётку (0,9) комнаты 5 Major ждёт сценария: прогоном 2026-08-07 не воспроизведён
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-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.