Files
Sprinter-SDCC/applications/PoP/roomtest/bug_list.md
T
snark13 08ac38ce3d Пункт 0: отсев кладки по окну fore-клипа; закрыт BUG-CHEAT-FIGHT-1
Три шага пункта 0 из docs/perf_backlog.md.

1. Ранний выход из wall_pattern_palace по окну fore-клипа: узор целиком
   лежит в x [xh*8, xh*8+32), y [dmy-59, dby], и если окно его не задевает
   — возврат до первой заливки.  Замер: от входа в узор до конца всего
   прохода 6 055 тактов вместо ~60 000 на тайл.
2. Отсев КАЖДОГО декаля (wp_blit) по реальному габариту.  pop_blit_b тоже
   отсеивает до маппинга страницы, но по заведомо большему 64x64 — куски
   кладки высотой 3..12 px он пропускал и платил полный
   atlas_image + gfx_w0_map (~9 760 тактов на блит), чтобы там обнаружить,
   что рисовать нечего.  Размеры сняты из каталогов атласов и заданы верхней
   границей по группе.
3. То же для ПОДЗЕМЕЛЬЯ: ранний выход wall_pattern (габарит выше — левая
   марка уходит на dby+POP_YOFF-67) плюс wp_blit на RNDBLOCK, обоих
   разделителях и обеих марках.

Замер (уровень 4, комната 18, Кид сдвигается читом ] по пикселю, skip
выключен, шесть тайлов в fore-окне, три из них — дворцовая стена):
тайл стены ~12 700 вместо 60 000-74 000, fore-проход целиком 70 681 вместо
171 693, работа за кадр 419 839, период 3 растровых кадра вместо 4.
Уровень 1 (подземелье) проверен снимком — кладка, марки, разделители на
месте.

Остаток в проходе — пролог pop_char_fore 17 208 тактов (пункт 1 backlog'а).
Отдельно записано: на ШВЕ пролог разбухает до 134 730, причина не разобрана.

BUG-CHEAT-FIGHT-1 (заведён 2026-08-07) закрыт по своему же плану: ветка
ROOMNAV после kid_init/pop_kid_hp_reset гасит состояние схватки
(Kid.sword = SWORD_0_SHEATHED, holding_sword, offguard, guard_refrac).
Меч в инвентаре не теряется.  Это болезнь чита: в оригинале телепорта между
комнатами нет и в режим боя без соперника попасть нечем.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:24:27 +03:00

25 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 (чит) ЗАКРЫТ 2026-08-10bug_closed.md
BUG-SPIKE-1 пики залипают выдвинутыми рядом с Кидом низкий маловоспроизводимый: ни сценарием, ни попиксельной подгонкой X не поднимается
BUG-CHOMP-JUMP-1 прыжок с места вплотную к чомперу: кадр с отступом назад низкий маловоспроизводимый: на повторе не поднялся; на пререлиз
T-1 пики перерисовываются безусловно оптимизация открыт
T-2 Кид перерисовывается в покое оптимизация ЗАКРЫТ 2026-08-08DRAW-COST шаг 1
BUG-GATE-PASS-1 проход сквозь закрытую решётку шва Major ЗАКРЫТ 2026-08-09bug_closed.md, смоук уровня 1 пройден
BUG-GUARD-IX-1 «зависание» в бою со стражем: затёрт IX главного цикла Blocker ЗАКРЫТ 2026-08-10bug_closed.md
BUG-GATE-SEAM-ROW1 решётка в шве не анимируется, если ворота не в ряду 0 Major ЗАКРЫТ 2026-08-10bug_closed.md
BUG-LOOSE-BUTTON-1 упавшая плита не нажимает кнопку под собой Major ЗАКРЫТ 2026-08-10bug_closed.md
BUG-GATE-FF-1 ворота «открыты навсегда» непроходимы (0xFF перегружен) Major ЗАКРЫТ 2026-08-10bug_closed.md
BUG-TORCH-CHOMP-1 чомпер стирает пламя соседнего факела Minor ЗАКРЫТ 2026-08-10bug_closed.md
DIED-ON-BUTTON died_on_button (seg007:776) не портирован порт открыт

Уровень 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-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.


Оптимизация отрисовки (записано 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.

DIED-ON-BUTTON. died_on_button (seg007:776) не портирован

Обнаружено при разборе BUG-LOOSE-BUTTON-1. check_press (seg006:1707) разбирает ЛЮБОГО мёртвого Char, не только Кида:

if (curr_tile2 == tiles_15_opener || curr_tile2 == tiles_6_closer) {
    if (Char.alive < 0) trigger_button(1, 0, -1);   /* жив */
    else                died_on_button();           /* мёртв */
}

died_on_button на opener делает тайл обычным полом и форсирует button_type = tiles_14_debris — ворота открываются насовсем; на любой другой кнопке ставит tiles_5_stuck (заклинена, link_timer == 0x1F, связь мертва).

У Кида эффект живёт до перезапуска уровня (смерть → is_restart_levelplay_level заново зовёт load_level(), тайлы перечитываются), а вот когда на кнопке умирает СТРАЖ — перезапуска нет, и кнопка заклинена до конца уровня.

Что нужно: сам порт died_on_button, константа TILE_STUCK = 5 и тайл заклиненной кнопки в атласе фона (сейчас его там нет).