Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём. Разбор каждого — bug_closed.md.
Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
(POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
роняло Кида сквозь пол.
Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
(redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.
По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
нарисовался поверх передних столбов (kid_fore_clip_restore);
2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
настоящей X. Теперь колонка выводится из X, как в оригинале;
3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
«упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
габарита, поэтому бегущий Кид уходит за объекты справа. Считается
для обоих персонажей — enter_guard ставит action=1 и стражу.
Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража. make size-check — роста нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
25 KiB
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 (стражи не оживают) — закрыты и проверены в MAME; BUG-DRAWORDER-1 (Кид поверх тела стража) и BUG-LOOSE-2 (осколки только от одной из двух плит) — исправлены по SDLPoP, но их сценарии автоматикой не воспроизведены (см. таблицу ниже).
| ID | что | тип | статус |
|---|---|---|---|
| BUG-DRAWORDER-1 | Кид рисуется поверх тела стража (в оригинале — за телом) | Minor | фикс есть, сценарий не проверен |
| BUG-LOOSE-2 | осколки только от одной из двух падающих плит (комната 12) | Minor | фикс есть, гонка не воспроизведена |
| BUG-SEAM-DRAW-1 | у решётки на шве экран переключается, а Кид в шве НЕ рисуется | Minor | открыт |
| T-1 | пики перерисовываются безусловно | оптимизация | открыт |
| T-2 | Кид перерисовывается в покое | оптимизация | открыт |
| обход 24 комнат | таблица заполнена на 5 строк из 24 | ревизия | открыт |
Ручная перепроверка фиксов (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 |
| 8 | комната 6: бег справа налево от (0,9), длинный прыжок (0,6)→(0,7) | не влетает внутрь стены |
| 9 | комната 5: с кнопки (0,6) падение на (2,7), присед, вставание | остаётся в комнате 5 (проверено трассой: fr=111 x=177 → seq_47 → fr=15 x=173) |
| 10 | комната 5: нажать кнопку (0,4) | поднимаются ОБЕ решётки — (0,5) видно на экране, (0,9) проверять из комнаты 1 |
| 11 | страж (комнаты 3, 21) убивает Кида | смерть доигрывается, респавн в стартовой позиции уровня; цикла «убил-воскрес» нет |
| 12 | клавиатура: долгая игра с Shift+стрелка | ↑ и Shift не залипают; осторожный шаг не превращается в бег (доработано во второй волне — п.17) |
Вторая волна (2026-08-03, вечер)
| # | что проверить | ожидаемо |
|---|---|---|
| 13 | [BUG-DRAWORDER-1] убить стража там, где через тело можно пройти С ЛЕВОЙ стороны (не у самой кромки), и провести Кида сквозь тело | Кид проходит ЗА телом, когда его тайл в обходе РАНЬШЕ (левее по колонке или ниже по ряду), и поверх — когда позже |
| 14 | [BUG-LOOSE-2] комната 12: разбежаться по плитам (0,1)/(0,2) и уйти в 15 ДО того, как долетит вторая; вернуться | осколки на ОБОИХ — (1,1) и (1,2) |
| 15 | то же в комнате 7 (плиты 0,5/0,6 → 2,5/2,6) | не сломалось: по-прежнему двое осколков |
| 16 | плита, улетевшая из комнаты вниз, при возврате | лишнего щебня не появилось |
| 17 | [BUG-KBD-4] долго играть Shift+стрелка, много тапов подряд | Shift не «отваливается»; если однажды залипнет — снимается первым же нажатием стрелки |
| 18 | [BUG-RESPAWN-2] убить стража (комнаты 3/21), умереть, вернуться в ту же комнату | страж снова жив, стоит на исходном тайле, HP полные |
Отдельно стоит посмотреть на регрессии от порта check_collisions — он
трогает всю горизонтальную коллизию: бамп в стену на бегу и в прыжке,
осторожный шаг у стены, проход через ОТКРЫТЫЕ ворота, разворот в проёме
решётки, переходы через швы (старый BUG-SEAM-PINGPONG).
Открытые баги уровня 1
BUG-DRAWORDER-1. Кид рисуется поверх тела стража
Симптом. В оригинале Кид проходит ЗА телом убитого стража; у нас — перед ним.
Корень. Мы рисовали безусловно pop_guard_draw() → kid_draw(), то есть
Кид всегда сверху. В оригинале «кто поверх» никем не назначается: и Кид, и
страж кладутся в midtable при обработке СВОЕГО тайла
(set_objtile_at_char, seg006:13F3), а тайлы обходятся строго —
redraw_needed_tiles (seg008:1B06) идёт рядами 2,1,0 и внутри ряда
колонками 0..9. Нарисованный позже оказывается сверху. Если тайлы совпали,
решает sort_curr_objs (seg008:203C): у обычных объектов (не тень, не плита)
ниже по obj_y — значит позже.
Фикс (roomtest.c): guard_over_kid() считает ключ обхода
(2 - row) * 10 + col для обоих персонажей (с поправкой
set_objtile_at_char на лазание/полёт/вис — колонка левее), сравнивает, при
равенстве сравнивает obj_y, и порядок вызова pop_guard_draw/kid_draw
выбирается по результату.
Известное упрощение. Ветку actions_1_run_jump (там оригинал берёт
нижний ряд и левую колонку ГАБАРИТА, а не curr_row/curr_col) не
воспроизводим: она требует полного футпринта, а влияет только когда
прыгающий Кид делит тайл со стражем — там порядок всё равно разводит obj_y.
Не проверено. Сценарий не воспроизведён: в комнате 21 страж падает у самой левой кромки (col 0), слева от тела встать нельзя, а «Кид поверх» при Кид.col=1 > страж.col=0 — это как раз ПРАВИЛЬНЫЙ порядок и бага не показывает. Нужен ручной прогон, п.13 таблицы выше.
BUG-LOOSE-2. Осколки только от одной из двух падающих плит
Симптом. Комната 12, соседние падающие плиты (0,1) и (0,2): после пробежки осколки появляются только на (1,2), на (1,1) — нет. В комнате 7 (плиты 0,5/0,6 → 2,5/2,6) обе дают осколки.
Корень (гипотеза автора отчёта подтвердилась чтением кода). Наш
падающий кусок был привязан к ТЕКУЩЕЙ комнате: pop_loose_reset() на смене
комнаты звал pop_loose_mob_reset() и гасил всё, что ещё летит, а тайл под
куском читался из карты отрисованной комнаты. Кид убегает в комнату 15
раньше, чем долетает плита (0,1) — кусок уничтожается, loose_land не
случается, щебень не кладётся. В комнате 7 обе плиты успевают сесть до
ухода, поэтому там всё выглядело правильно.
В оригинале mobs[] чистится ТОЛЬКО в start_level (seg003:88), do_mobs
(seg007:1063) прокручивает все куски независимо от drawn_room, move_loose
работает по curmob.room, а redraw_at_cur_mob (seg007:132C) сверяется с
drawn_room лишь для ОТРИСОВКИ.
Фикс. У куска появилось поле room (curmob.room); на смене комнаты
зовётся pop_loose_mob_room_changed() — сбрасывает только heal-историю;
тайл читается через mob_tile_at() (своя комната — tile_code, чужая —
pop_level_tile); приземление в покинутой комнате пишет щебень прямо в
данные уровня; отрисовка и check_loose_fall_on_kid (там оригинал начинает с
Char.room == curmob.room) — только для своей комнаты. Настоящий
mobs_count = 0 переехал в pop_start_level.
Не проверено. Гонка «уйти из комнаты раньше, чем долетит плита» автоматикой не воспроизведена. Ручной прогон — пп. 14–16 таблицы выше.
BUG-SEAM-DRAW-1. Кид, упершийся в решётку на шве, не рисуется
Симптом. Комната 5: Кид добегает до закрытой решётки (0,9), решётка его
ДЕРЖИТ (проверено: x встаёт на 60 и дальше не растёт), но экран
переключается на комнату 1, и в ней Кид не виден — он стоит на x = 57..60,
левее кромки комнаты (col 0 начинается с 58).
Разбор. Переключение экрана само по себе штатное: leave_room
(seg002:423) уводит вправо при char_x_right >= 201, а левая грань решётки
как раз 201 — то есть в оригинале экран тоже перелистнётся, если персонаж
встанет ровно на грань. Не штатно то, что персонажа в шве не рисуют:
update_kid_render_dx даёт смещение только когда kid_room != cur_room, а
enter_room сразу выравнивает kid_room = cur_room. Это остаток
непортированной модели straddle (S3 в pop_kid_plan).
Что делать. Довести S3: держать Kid.room отдельно от drawn_room и
рисовать персонажа со смещением ±140, как xpos_in_drawn_room (seg004:0405).
Оптимизация отрисовки (записано 2026-07-29)
Не баги — план работ. Оба пункта про одно: у оригинала пометка тайла к перерисовке стоит копейки (бит в таблице, которая всё равно чистится каждый кадр), а у нас каждая такая пометка превращается в реальный heal (копию из ОЗУ-копии акселератора) плюс блиты. Поэтому буквальный порт «перерисовываем безусловно» корректен, но дорог.
T-1. Пики: перерисовывать по причине, а не безусловно
Сейчас: pop_process_trobs зовёт pop_spike_redraw каждый кадр для
каждой живой пики в комнате (порт redraw_21h, который animate_spike
вызывает вне всяких if). Это корректно, но лишнее для пик, до которых
Киду дела нет.
Надо: перерисовывать тайл пики, только если
- сменился её видимый кадр (шаг выдвижения/уборки), ЛИБО
- её кто-то стёр — а стереть у нас может только 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_heal → kid_draw → pop_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_y−3), подъём по 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.cKBD_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 | закрыт |