Files
Sprinter-SDCC/applications/PoP/roomtest/bug_list.md
T
Александр Петров dc8b2b7115 Приёмка ур. 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>
2026-08-05 21:29:37 +03:00

49 KiB
Raw Blame History

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

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

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

Ревизия списка: 2026-08-03 (прогон всех комнат уровня 1). Одиннадцать наблюдений с прогона свелись к шести корням; все шесть закрыты и проверены в MAME — разбор в bug_closed.md: BUG-LVLSTATE-1 (уровень стал мутабельным), BUG-RESPAWN-1 (респавн перезагружает уровень), BUG-DEATH-1 (смерть от меча), BUG-GATE-ANIM-1 (анимация ворот), BUG-COLL-1 (полный порт check_collisions), BUG-STANDUP-1 (лишний guard в bumped_floor). Клавиатурный BUG-KBD-3 доведён до конца во второй волне (см. ниже); из первой волны открытым остался косметический BUG-SEAM-DRAW-1.

Ревизия 2026-08-03, вечер (вторая волна с прогона): все четыре наблюдения закрыты — BUG-KBD-4 (фантомный отжим Shift), BUG-RESPAWN-2 (стражи не оживают), BUG-DRAWORDER-1 (Кид поверх тела стража) и BUG-LOOSE-2 (осколки только от одной из двух плит). У BUG-DRAWORDER-1 остался артефакт «ноги поверх головы», сверенный с SDLPoP и записанный в «НЕ БАГИ». Тогда же закрыт как невоспроизводящийся BUG-SEAM-DRAW-1 (Кид в шве рисуется) и заведён BUG-GATE-PASS-1 — однократное наблюдение прохода сквозь закрывшуюся решётку, которое ЖДЁТ надёжного сценария.

ID что тип статус
BUG-SPIKE-1 пробег по выдвинутым пикам не убивает: модификатор застревает в «полностью вышли» (h=1), а это в оригинале не смертельно для бегущего Major открыт, замер есть, заведён 2026-08-05
BUG-GUARD-DEAF-1 страж не оборачивается на вернувшегося Кида: is_guard_notice не взводился Major фикс есть, ждёт игровой проверки
BUG-GRAB-1 ур. 2 комн. 9: прыжок с места через 3 тайла — нет зацепа за кромку Major причина найдена (клавиатура, BUG-KBD-5), фикс есть, ждёт игровой проверки
BUG-GATEMOD-1 ворота стартуют закрытыми, хотя в уровне открыты Major фикс есть, ждёт проверки
Уровень 2 остальные баги с приёмки принимаются по ходу
BUG-GATE-PASS-1 проход сквозь закрывшуюся решётку (0,9) комнаты 5 Major перепроверить после BUG-GATEMOD-1 — та же решётка стартовала не в том состоянии
T-1 пики перерисовываются безусловно оптимизация открыт
T-2 Кид перерисовывается в покое оптимизация открыт
обход 24 комнат таблица заполнена на 5 строк из 24 ревизия открыт

Уровень 2 — баги с приёмки (заведено 2026-08-04)

Уровень 2 заиграл 2026-08-04 (см. TASKS.md запись L2). Пользователь при первом прогоне увидел несколько багов отрисовки и подаст их списком отдельно — раздел заведён под них заранее, чтобы наблюдения не растеклись по переписке.

Закрыто с этой волны: BUG-LOOSE-3 — чёрный бар под упавшей плитой-потолком (запечка восстанавливала не всё, что стёрла).

Порядок работы по уровню 2 (2026-08-05): наблюдения СНАЧАЛА копятся здесь целиком — фиксы берём, когда уровень пройден до конца. Причина: косметика отрисовки почти всегда сводится к общему корню (ср. волну BUG-CEIL-1/2/3 + BUG-OCCL-1), и чинить по одному наблюдению — значит чинить один и тот же корень трижды.

Карта содержимого уровня (что где стоит по данным res2002.bin, какие кнопки какие ворота открывают) — в TASKS.md, запись L2-PASS: по ней видно, «механика не сработала» это или «так и задумано».

Перед заведением записи — грепнуть bug_closed.md по симптому: почти вся косметика окклюзии уровня 1 уже разбиралась (BUG-CEIL-1/2/3, BUG-OCCL-1, BUG-DOOR-CLIP, BUG-DRAWORDER-1), и уровень 2 вводит ровно три новых фоновых тайла — большая колонна (низ 8 / верх 9) и верх двери (12); если артефакт рядом с ними, это первый подозреваемый.

Уровень 3 — баги с приёмки (заведено 2026-08-05)

Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал четыре наблюдения. Два из них — не баги, а неначатые задачи, и записаны как таковые:

наблюдение что это на самом деле
к.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

Остальные два были настоящими багами и закрыты в тот же день — разбор корней в bug_closed.md: BUG-JUMPWALL-1 (флаги коллизий считались для одного ряда вместо трёх — бамп терялся на кадре смены ряда) и BUG-SEAM-WEDGE-1 (сосед угла (2,0) безусловно считался стеной).

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 (сколько кадров модификатор успевает пройти, пока Кид добегает).

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

Это стыкуется с замером: пока Кид стоит рядом, 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).


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..641play_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 и вернуться бегом — на первом же шаге страж обязан обернуться и достать меч. Подкрасться к нему СТОЯ (не двигаясь) по-прежнему можно — это механика оригинала, а не баг.


BUG-GRAB-1. Прыжок с места через провал в 3 тайла: зацепа нет — ПРИЧИНА НАЙДЕНА, ФИКС ЕСТЬ, ЖДЁТ ИГРОВОЙ ПРОВЕРКИ

Итог 2026-08-05. Физика тут ни при чём — виновата клавиатура. Зажатый Shift снимался автоповтором зажатой стрелки, поэтому к кадрам 102…106 (окно зацепа) движок видел Shift отпущенным. Полный разбор и фикс — BUG-KBD-5; поведение Shift в MAME проверено замером карты _kbdraw_down. Осталось подтвердить сам зацеп живой игрой; версии 2 и 3 ниже проверять только если он всё ещё не выйдет.

Симптом. Уровень 2, комната 9. Перепрыгнув на (1,1), Кид должен вернуться обратно: разбегаться негде, поэтому он встаёт на самый край плиты, прыгает с места и цепляется руками за (1,5), после чего подтягивается. У нас Кид с зажатым Shift всё равно срывается.

Что уже точно известно (и не надо перепроверять).

  1. Физика прыжка у нас совпадает с оригиналом кадр в кадр. Сверено по логу SDLPoP против трассы харнесса при одинаковом старте x=95:

    кадр   16   18   22   23   24   25   102  103  104  105
    SDLPoP 95   97  105  112  121  126  128  130  131  133
    наш    95   97  105  112  121  126  128  130  131  133
    

    Совпадает и по y, и по колонке/ряду, и по приземлению на 107–108.

  2. В оригинале зацеп срабатывает на кадре 106, а не 102..105. check_grab зовётся из ДВУХ мест: ветка «в воздухе» в check_action (кадры 102..105) и do_fall (seg005) для actions_4_in_freefall. Успешная попытка из лога:

    GRAB try  f=106 x=135 y=166 col=4 row=2 fall_y=18
    GRAB probe x=127 col=4 through=0 front_above=3 modif=0
    GRAB      can_grab=1
    GRAB OK   dist=9
    -> f=91 x=136 y=181 col=5 row=2 act=2   (повис)
    

    Наш do_fall (pop_map.c) check_grab() из этой ветки тоже зовёт — то есть структура на месте, расходится что-то внутри.

  3. По харнессу зацеп у нас РАБОТАЕТ: окно стартовых x = 91…95, и короткий шаг ставит Кида ровно туда (91 после первого нажатия, 95 после второго). Зафиксировано тестом t_grab.

Отсюда главный вопрос был: почему харнесс говорит «работает», а живая машина — «нет». Расхождение между ними и оказалось уликой; версии выдвигались по убыванию правдоподобия, и сработала первая:

  • Shift не доезжает до движка — ПОДТВЕРЖДЕНО, это и была причина. Харнесс подменяет клавиатуру и потому этот путь не проверяет вовсе, а у нас есть история проблем ровно с «Shift + стрелки» (KBD-1, BUG-KBD-3/4). Замер в MAME: при зажатом Shift и зажатой стрелке бит LSh в _kbdraw_down стоял в нуле. Разбор — BUG-KBD-5.
  • Сцена харнесса не равна комнате 9. Там изолированная комната (соседи — стена), а в игре слева комната 8; кромки шва участвуют в get_tile. Проверять чтением Kid.x в момент прыжка: попал ли он в окно 91…95 вообще.
  • Расхождение в check_grab. Наш вариант зовёт determine_col() там, где оригинал зовёт load_fram_det_col() (перезагрузка кадра + колонка). Для Кида это обычно одно и то же (cur_frame в фазе физики принадлежит ему), но проверить стоит.

Инструменты готовы. В SDLPoP включена отладка (пометка DBG-GRAB): JMP — покадровая трасса прыжка/падения/виса, GRAB try|probe|fail|OK — вход в check_grab и причина отказа. Снимается поиском по DBG-GRAB.

Найдено попутно, отдельным наблюдением. После касания площадки на кадрах 107–108 (x=140) оба движка снова падают, но X расходится: SDLPoP уводит Кида на 134 (колонка 4), мы — на 141 (колонка 5). Похоже на разную отработку in_wall() у стены (2,7). На зацеп не влияет.


BUG-GATEMOD-1. Ворота стартуют закрытыми, хотя в уровне открыты — ФИКС ЕСТЬ, ЖДЁТ ПРОВЕРКИ

Симптом (пользователь, 2026-08-04). Уровень 2, комната 13: решётка между (2,5) и (2,6) обязана быть ОТКРЫТА в начале и захлопнуться, когда Кид нажмёт кнопку (2,4) — после этого назад дороги нет. У нас она закрыта сразу, кнопка бессмысленна, проход не работает.

Корень. Модификатор тайла в ФАЙЛЕ уровня и модификатор в РАНТАЙМЕ — разные величины; оригинал переводит их при загрузке в load_alter_mod (seg008:198E), которую зовёт alter_mods_allrm из load_level:

case tiles_4_gate:  *modif = (*modif == 1) ? 188 : 0;  break;
case tiles_11_loose:*modif = 0;                        break;
case tiles_10_potion:*modif <<= 3;                     break;

Наш pop_trob_modif портировал из неё только зелье. Для ворот bg = 1 — это «открыты» (Table 8 спецификации DAT), а в рантайме открытость измеряется высотой подъёма 0..188; мы клали в рантайм-модификатор сырую единицу, то есть «закрыты на 1/188».

Фикс. Ветки ворот и loose дописаны в ленивую инициализацию pop_trob_modif (pop_trob.c). Ветка СТЕН не портируется намеренно: у нас pop_bg считает связи кладки по типам соседей прямо при отрисовке (wall_modifier), сохранённый модификатор стены не читается.

Что это ещё задевает. Решётка (0,9) комнаты 5 уровня 1 тоже имеет bg = 1, то есть обязана стартовать открытой — Кид сваливается в комнату 1 именно через неё, и она захлопывается у него за спиной. Закрывает её стартовый триггер do_startpos (seg003:167): для уровней с tbl_entry_pose == 1 оригинал ВИРТУАЛЬНО ЖМЁТ кнопку комнаты 5 (0,2) —

// Special event: press button + falling entry
get_tile(5, 2, 0);  trigger_button(0, 0, -1);  seqtbl_offset_char(seq_7_fall);

Замер в SDLPoP (лог по кадрам): gate(5,0,9) идёт 188 → 148 → 88 → 8 → 0, шаги 40/60/80 — это gate_close_speeds, то есть быстрое закрытие (trigger_gate вернул тип 3). У нас этот триггер портирован, и закрытие работает.

Полный список ворот с bg = 1: ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5). Остальные ворота уровней 1–3 имеют bg = 2 → 0, и для них ничего не меняется (при модификаторе 2 и 0 и отрисовка, и can_bump_into_gate дают одно и то же).

Побочная находка: чит обхода комнат отматывал мир. После фикса пользователь увидел «ворота снова открылись», пройдя + в комнату 2 и - обратно. Причина не в воротах: ROOMNAV звал pop_trob_reset() перед enter_room, тот обнулял room_seen[], и pop_trob_modif() перечитывал модификаторы из уровня заново — то есть чит откатывал открытые/закрытые ворота, выдвинутые пики и нажатые кнопки. Пока ворота с bg=1 ошибочно стартовали закрытыми, откат был не виден. pop_trob_reset() из навигации убран: она обязана только телепортировать, исходное состояние даёт перезапуск уровня. Замер, который это показал: room_modif комнаты 5 после +/- = 00 00 0B 00 09 00 08 01 00 BC — последний байт 0xBC = 188, файловое значение.


Ручная перепроверка фиксов (2026-08-03)

Шесть корней закрыты автоматической проверкой в MAME (мост mame-z80: чтение _Kid, потиковые трассы, скриншоты) — этого достаточно, чтобы показать, что конкретный сценарий больше не воспроизводится, но НЕ достаточно, чтобы поймать регрессии в соседней механике. Ниже — сценарии ровно в тех формулировках, в которых баги были заведены.

Собрано и лежит в образе (make + make hdd уже прогнаны; libc пересобран, make size-check без роста).

# что проверить ожидаемо
1 комната 22: выпить зелье (0,6), выйти в 16/23 и вернуться кувшина нет, пузырька над пустым местом нет
2 то же для комнат 14 (0,5) и 17 (2,3) так же
3 комната 15: подобрать меч (2,2), выйти и вернуться меча нет; кладка на дальней стене НЕ мигает
4 комната 12: разбить плиты (0,1)/(0,2) и потолок в 16, выйти-вернуться остаются разбитыми, проём не закрывается
5 комната 17 из 23: разбить (1,5)/(1,6), выпить зелье (2,3), вернуться всё остаётся
6 после смерти зайти в те же комнаты ВСЁ восстановлено (в оригинале смерть = load_level)
7 комната 12: разбег в закрытую решётку (0,9) с полушага не проходит насквозь; перелистывание экрана штатно (см. BUG-SEAM-DRAW-1 в bug_closed.md)
8 комната 6: бег справа налево от (0,9), длинный прыжок (0,6)→(0,7) не влетает внутрь стены
9 комната 5: с кнопки (0,6) падение на (2,7), присед, вставание остаётся в комнате 5 (проверено трассой: fr=111 x=177seq_47fr=15 x=173)
10 комната 5: нажать кнопку (0,4) поднимаются ОБЕ решётки — (0,5) видно на экране, (0,9) проверять из комнаты 1
11 страж (комнаты 3, 21) убивает Кида смерть доигрывается, респавн в стартовой позиции уровня; цикла «убил-воскрес» нет
12 клавиатура: долгая игра с Shift+стрелка ↑ и Shift не залипают; осторожный шаг не превращается в бег (доработано во второй волне — п.17)

Вторая волна (2026-08-03, вечер)

# что проверить ожидаемо
17 [BUG-KBD-4] долго играть Shift+стрелка, много тапов подряд Shift не «отваливается»; если однажды залипнет — снимается первым же нажатием стрелки
18 [BUG-RESPAWN-2] убить стража (комнаты 3/21), умереть, вернуться в ту же комнату страж снова жив, стоит на исходном тайле, HP полные

Отдельно стоит посмотреть на регрессии от порта check_collisions — он трогает всю горизонтальную коллизию: бамп в стену на бегу и в прыжке, осторожный шаг у стены, проход через ОТКРЫТЫЕ ворота, разворот в проёме решётки, переходы через швы (старый BUG-SEAM-PINGPONG).


Открытые баги уровня 1

BUG-GATE-PASS-1. Проход сквозь закрывшуюся решётку — ЖДЁТ СЦЕНАРИЯ

Статус: наблюдался один раз, воспроизвести повторно НЕ удалось. Заведён, чтобы наблюдение не потерялось; закрывать нельзя — ни как исправленный, ни как «не баг», пока нет надёжного сценария.

Что наблюдалось (2026-08-03). Комната 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: не перерисовывать Кида, когда ничего не происходит

Сейчас: kid_healkid_drawpop_fore_over_kid идут каждый кадр, даже когда Kid стоит и в его тайлах ничего не меняется. Это ровно поведение оригинала (draw_game_frame, seg000:917 — draw_moving() + draw_tables() безусловно), но у него это дёшево, а у нас нет.

Надо: пропускать heal+draw Кида, когда кадр/поза/координаты не менялись и в его тайлах нет активной анимации.

Осторожно (дабл-буфер): пропускать можно не раньше второго подряд неизменного кадра — иначе одна из двух страниц останется со старым содержимым. Условие «обе страницы уже получили это состояние».

Связь с T-1: после T-2 пики отпадают сами — раз Кида не перерисовываем, heal'а нет, стирать пики нечем, редрой не нужен.

Связь с KBD-1: это ещё и минус DI-окна в самых спокойных кадрах — ровно там, где тапают Shift+стрелку (см. остаток KBD-1 в TASKS.md).


Обход всех 24 комнат уровня 1 — поиск багов отрисовки

Инструмент: #define ROOMNAV в roomtest.c+/- (цифровой блок либо =/- основного ряда) переключают комнату по номеру (1..24, с обёрткой), Kid ставится на первый пол, pop_trob_reset() возвращает пики/ворота в исходное. Номер комнаты — полосками в верхнем борте: слева десятки, справа единицы (|| |||| = 24). Убирается закомментированием одной строки.

Комнаты 13, 18, 24 недостижимы в обычной игре — это свойство данных уровня, разбор в bug_closed.md («НЕ БАГИ»). Приоритет багов в них низкий.

Таблица заполнена на 5 строк из 24 — доделать в рамках L1-TRIAGE (TASKS.md).

комната статус что не так
1 пофикшено падающая плита (2,6): правая грань видна через пол (2,7) и перекрывает его переднюю грань — mob_render брал ряд соседа из m->row (счётчик, уже ушедший на ряд вперёд), а не из координаты
2
3
4
5 пофикшено прыжок в решётку: Kid оставался стоять на 6 px ВЫШЕ пола и без приземления-приседания — от bumped() (seg004) был портирован только хвост (seq_47), не хватало bumped_floor (прижатие Y к полу + seq_46_hardbump на кадрах прыжка 24/25/40..42/102..106) и bumped_fall
6
7
8
9 сделано дверь уровня (1,3)-(1,4) рисовалась чёрным проёмом: не был портирован draw_leveldoor (seg008:1D29) — створка (слайсы 33 + верх 34), лестница за ней (99/144) и анимация подъёма по кнопке (animate_leveldoor, seg007:05F1, modif 0→43). Спрайты 33/34/99/144 добавлены в атлас явным набором (render_room.py дверь не рисует)
10
11
12 пофикшено вис/подтягивание на кромке loose-плиты: плита рисовалась ПОД Кидом. Не хватало двух кусков draw_tile: (а) draw_loose кладёт нижнюю грань плиты И в foretable (поверх персонажа), (б) draw_tile_base подставляет верх плиты из loose_fram_left, а в нашем midtable-оверлее стоял голый base_id (у loose он 0). Голова Кида поверх падающей на него плиты — см. «НЕ БАГИ» в bug_closed.md
13 недостижима в игре
14
15 сделано меч (2,2) не рисовался: тайл 22 в draw_tile_anim не был портирован. Добавлены отрисовка предмета (chtab_1 id 10/11 на draw_main_y3), подъём по Shift (check_get_item/get_item/do_pickup: присед → seq_91 pickupsword → меч исчезает с пола) и статус pop_have_sword
16
17
18 недостижима в игре
19
20
21
22
23
24 недостижима в игре

Заметки (отладка)

  • Тестовые клавиши осторожного шага: J = шаг влево, L = шаг вправо (эмуляция Shift+стрелка), см. pop_ctrl.c KBD_DBG_STEP*. Первый шаг в сторону = разворот (как в оригинале safe_step), движение со второго.
  • Читы (pop_cheat.h): K — убить стража, I — бессмертие (toggle), S — выдать меч.
  • Респавн после смерти — по (или авто через RESPAWN_DELAY); ставит Kid в стартовую позицию УРОВНЯ (pop_start_level, порт do_startpos).
  • ROOMNAV (=/-) — тоже наш чит, которого в оригинале не было, как и S. Все они со временем съедутся в общий блок читов, разрешаемый в настройках; пока просто включены (pop_cheats = 1 в roomtest.c).

Сырые наблюдения (прогон 2026-08-03) → корень

Формулировки с прогона, как записаны. Закрытые корни — в bug_closed.md.

# наблюдение (кратко) корень статус
1 кувшин выпит, а пузырёк рисуется / кувшин возвращается (14, 22, 17) BUG-LVLSTATE-1 закрыт
2 меч возвращается в 15 / мигает контур кладки BUG-LVLSTATE-1 закрыт
3 комната 12: пробегает сквозь закрытую решётку (0,9) BUG-COLL-1 закрыт
4 после respawn плиты остаются разбитыми, зелья выпитыми BUG-RESPAWN-1 закрыт
5 плита/зелье возвращаются и БЕЗ respawn (12→16, 17, 22) BUG-LVLSTATE-1 закрыт
6 комната 6: длинный прыжок (0,6)→(0,7) — влёт в стену, респавн BUG-COLL-1 закрыт
7 залипает ↑ BUG-KBD-3 закрыт (см. bug_closed.md)
8 залипает Shift BUG-KBD-3 / BUG-KBD-4 закрыт (см. bug_closed.md)
9 комната 5: кнопка (0,4) не открывает решётку (0,5) BUG-GATE-ANIM-1 закрыт
10 комната 5: с кнопки (0,6) на (2,7), присед — провал в комнату 6 BUG-STANDUP-1 закрыт
11 страж убил Кида → Кид воскресает на месте и его убивают снова BUG-DEATH-1 закрыт