Комната 9: портал на уровень 2 рисовался чёрным проёмом — не был
портирован draw_leveldoor (seg008:1D29). Дверь рисуется при обработке
ПРАВОЙ половины (tile_left = 0x10), все куски со сдвигом +8 px:
99 низ лестницы (всегда),
144 марш лестницы за створкой (когда створка тронулась),
33 слайс створки — повторяется вниз с шагом 4 px до y = ybottom-modif
(modif 0 = закрыто на всю высоту, 43 = открыто, остаётся кромка),
34 верх коробки.
Анимация: animate_leveldoor (seg007:05F1) — type 0..2 открытие (modif++
до 43), type>=3 быстрое закрытие со скоростями {0,5,17,99}. Кнопка
заводит trob через trigger_1 (seg007:0999): дверь открывается ОДИН раз,
при modif != 0 кнопка уже ничего не делает.
Перерисовка створки — pop_leveldoor_redraw: draw_tile правой половины С
ЗАПЕЧКОЙ в ОЗУ-копию (банк TRANSPARENT), иначе kid_heal возвращал бы из
фона закрытую створку. Куски двери непрозрачные, wipe не нужен.
Спрайты 33/34/99/144 не попадали в атлас: сбор идёт прогоном
render_room.py, а он draw_leveldoor не реализует — добавлены явным
набором LEVELDOOR_ENV_IDS (как STUCK_ENV_IDS для нажатой кнопки).
Проверено в MAME (комната 9): закрытая дверь = решётка как в оригинале;
после нажатия кнопки (0,0) створка едет вверх, открывая лестницу.
Вход в дверь (кадры 217..228 + переход на уровень 2) НЕ делался.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
36 KiB
roomtest — список известных багов (Фаза B и смежное)
Найдено при тестировании Фазы B (кнопки/ворота) в MAME. НЕ решаем сейчас
— задача следующего этапа. Правило проекта: механику сверять с
applications/PoP/SDLPoP/src/ ДО кодинга.
Статус на момент записи: 2026-07-21. Тестовый старт — комната 6 (START_ROOM
в roomtest.c). Кнопка room6(0,2, opener) → ворота room8(0,9), видны в левом
шве room6.
BUG-1. Боковой переход через ворота: Kid проваливается на row 1 — Critical
Симптом: при проходе через ОТКРЫТЫЕ ворота в соседнюю комнату (через шов) Kid оказывается на ряду row 1 вместо row 0 — «проваливается» на этаж ниже (стоит в дверном проёме на неправильной высоте).
Где: pop_map.c check_leave() (порт seg002 leave_room) — боковой уход
pop_leave_dir; репроекция координат Kid.x ∓= 140 есть, но Y/curr_row
при боковом переходе НЕ репроецируются (в отличие от check_leave_below,
где Kid.y -= 189; curr_row = y_to_row). При входе в соседнюю комнату у
шва Kid, видимо, попадает в позицию, где determine_col/физика следующего
кадра сбрасывают его на row 1 (нет пола на row 0 у входа, либо неверный y).
Сверить с SDLPoP: goto_other_room (seg002) — как именно
пересчитываются Char.curr_row/Char.y при side-переходе (left/right).
Для вертикального перехода мы это сделали, для бокового — нет.
BUG-2. Возврат из комнаты назад: Kid отбрасывается обратно (ping-pong) — Critical
Симптом: после перехода в соседнюю комнату попытка сразу вернуться обратно приводит к тому, что Kid снова закидывается в ту же комнату — переход «пинг-понгует», выйти нельзя.
Где: pop_map.c check_leave(). После перехода Kid репроецирован к
ПРОТИВОПОЛОЖНОЙ кромке новой комнаты (x ∓ 140). На СЛЕДУЮЩЕМ же кадре
передний край снова за порогом ухода в обратную сторону → pop_leave_dir
взводится опять → мгновенный обратный переход. Нужен guard «только что
перешёл» (подавить повторный leave на N кадров / пока Kid не отойдёт от
кромки), как в оригинале (next_room/leave обрабатываются раз за кадр +
Kid входит достаточно внутрь).
Сверить с SDLPoP: порядок leave_room/goto_other_room + на какой
X ставится персонаж при входе (должен быть НЕ на самой кромке, иначе
мгновенный обратный триггер).
BUG-3. Climb-up на тайл-кнопку: неправильная окклюзия (Kid поверх кнопки) — Critical
Симптом: когда Kid подтягивается (climb-up) на тайл, верх которого — КНОПКА (opener/closer), а не обычный пол, отрисовка неверная: Kid рисуется ПОВЕРХ кнопки, хотя передняя грань пола/кнопки должна перекрывать нижнюю часть Kid во время подъёма.
Где: pop_bg.c climb_overlay_tile() — floor-overlay при подъёме
рисуется только для floor-подобных тайлов code ∈ {1,3,5,19}. Кнопка
(opener 0x0F / closer 0x06) в этот набор НЕ входит → пол-оверлей кнопки не
рисуется поверх Kid. Нужно: для нажатой кнопки (draw как floor/stuck)
трактовать её как floor-тайл в climb-overlay (учесть подстановку из
get_tile_to_draw), и/или добавить opener/closer в набор.
Сверить с SDLPoP: draw_floor_overlay (seg008) + get_tile_to_draw
(нажатая кнопка → tiles_1_floor / tiles_5_stuck) — оверлей должен идти по
ПОДСТАВЛЕННОМУ типу тайла.
Оптимизации (отложено)
OPT-1. Хирургический редрой левого шва (ворота соседа) — 2026-07-22
Возможность: pop_room_redraw_seam_left() (pop_bg.c) на каждое изменение
openness рисует bar(BLACK) по всему col0 + полный draw_tile(0,0)
(стены, topright, wall_pattern с prandom() — десятки блитов). Реально
анимируются только бары решётки — draw_gate_back (~9 env_b).
Стоимость: seam-блок (синий io_border) занимает ~30-50% кадрового периода,
но только пока openness меняется — т.е. во время открытия и медленного
авто-закрытия (~5 сек после схода с кнопки). В покое (ворота открыты-держим
кнопку / закрыты / FF) — 0%. Замерено в MAME: брейк на
_pop_room_redraw_seam_left (0x5A54) срабатывает ⟺ сегмент дорогой.
Приём (как у пик, но сложнее — heal НЕ годится: печёные бары устаревшие,
поэтому и стоит bar(BLACK)+redraw): bar(BLACK) только по полосе баров
(x=0, gate_top..gate_bot) + draw_gate_back(lmod,...) + дорисовать статику
тайла (0,0), задетую полосой. Ожидаемо срезает бóльшую часть 30-50%.
Пиксель-чувствительно — полоса баров пересекается со статикой col0,
обязательна выверка в MAME по кромкам.
Решение (2026-07-22): оставляем как есть — стоимость транзиентная, в бюджет помещаемся. Делать, если упрёмся в кадровый бюджет на сценах с воротами.
Исправлено
-
Спуск с кнопки (room8, кромка (0,6)): Кид просвечивал в щель, ближняя рука срезана до одного пикселя — ИСПРАВЛЕНО 2026-07-28. Две причины: (а) не был портирован
clip_char()(seg006:1749) — верхняя обрезка спрайта поy_clip[curr_row+1], когда тайл над головой стена/пол; сделано (pop_clip_char_topв pop_map.c +gfx_blit_cols_partв libbgi, heal чистит уже обрезанный прямоугольник); (б)climb_overlay_tileвыбирал веткуdraw_floor_overlay(seg008:1E3A) по СЫРОМУ коду тайла — а нажатая кнопка вget_tile_to_draw(seg008:240) подменяется на floor/stuck. Тайл-кнопка не проходил тест floor, уходил вdraw_other_overlayи закрашивал Kid ЦЕЛЫМ тайлом вместо узкой кромкиfloor_left_overlay[frame-137]. Фикс —tile_code_drawn()(одна подстановка на все слои). Проверено покадрово в MAME (кадры 148..138). -
Шов ворот жёг 50% кадра в покое (закрытая решётка) — ИСПРАВЛЕНО 2026-07-22. Причина — баг кодогенератора SDCC z80 (memory
sdcc_z80_cmp_store_a_bug):if (m[9] != seam_sig) seam_sig = m[9];компилировался вsub (seam_sig)(A ← разность) +ld (seam_sig),a— сохранял РАЗНОСТЬm[9]-seam_sig, неm[9].seam_sigосциллировала (напр. 25↔231),m[9]!=seam_sigистинно каждый кадр →draw_tile(0,0)каждый кадр даже у неподвижной решётки. Фикс: store-до-сравнения (seam_sig = g;из чистогоgДОsub), подтверждён в .asm. Прочёс всех модулей PoP: других случайных compare-then-store нет. -
Пики: 2×полный
draw_tileна кадр анимации → хирургический редрой, 2026-07-22.pop_spike_redraw:heal_offуже возвращает всю печёную статику (база 127, пол, грани); поверх анимируются только два острия (SPIKES_FRAM_LEFTв своей ячейке +SPIKES_FRAM_RIGHTв соседней). Заменили 2×draw_tileна 2×env_b— пиксель-в-пиксель тот же результат, в разы дешевле (шахта из нескольких пик больше не съедает полный кадр). -
Отрисовка нажатой кнопки (0,2)/(0,3) — ИСПРАВЛЕНО. Причина:
fore_tile(pop_bg.c, fore-слой поверх Kid) рисовал переднюю грань КНОПКИ (bottom_id 149) поверх уже нарисованной грани пола (43) — «остаток нажатой кнопки». Фикс:fore_tileприменяет ту же подстановку нажатой кнопки, что иdraw_tile(opener→floor / closer→stuck при таймере связи >1). Плюсpop_button_redraw— wipe своей ячейки + правой грани (дальний угол в 0,3), низ строго yb+64 (не залезать в стену ряда 1).
НЕ БАГИ (кривая картинка, но совпадает с оригиналом — НЕ чинить)
- Голова стоящего Кида поверх падающей на него loose-плиты. Комната 12: зацеп не удался, Кид остался стоять, сбитая плита падает прямо на него — голова рисуется ПОВЕРХ плиты. Сверено покадрово с SDLPoP v1.24 (2026-07-29): там ровно то же самое. Артефакт оригинального движка (порядок midtable), а не наш баг; «починка» увела бы от эталона. Отличать от соседних случаев, которые БЫЛИ нашими багами и исправлены: вис/подтягивание на кромке плиты и падение вместе с плитой — там плита обязана быть поверх Кида.
Заметки
- Тестовые клавиши осторожного шага: J = шаг влево, L = шаг вправо
(эмуляция Shift+стрелка), см.
pop_ctrl.cKBD_DBG_STEP*. Первый шаг в сторону = разворот (как в оригинале safe_step), движение со второго. - Респавн после смерти — по ↑ (или авто через
RESPAWN_DELAY). START_ROOM=6— временный тестовый старт; вернуть наpop_level_start_room()после отладки объектов.
BUG-CEIL-1. Прыжок вверх: руки Kid рисуются ПОВЕРХ потолка — Medium
Симптом (2026-07-22): при прыжке вверх (SEQ up, кадры 67..79) руки/голова Kid заходят в полосу кладки у потолка (row -1) и рисуются ПОВЕРХ неё — потолок должен окклюдить руки (как передняя грань).
Где: pop_bg.c — полоса потолка (draw_tile(-1)) сейчас только фон
(запечена в pop_room_draw), не окклюдер поверх Kid. Нужен ceiling-overlay в
pop_fore_over_kid (перерисовать срез полосы над Kid поверх спрайта), по
аналогии с fore-тайлами. Инфраструктура g_clip_top/gfx_blit_part уже есть.
BUG-CEIL-2. Тряска/разбитие loose-плиты в потолке (row -1) не работает — Medium
Симптом (2026-07-22): loose-плита в ряду 2 верхнего соседа (room5 (2,5) → потолок room6 над (0,5)) при прыжках Kid на (0,5) НЕ трясётся и не разбивается.
Где: верхний сосед не «живой» — above_fg берётся из статики уровня,
loose-состояние соседней комнаты не тянем (per-room loose/modif не персистятся,
P0). check_press/knock работают только по текущей комнате. Нужно: (а) пробой
loose сверху через границу комнаты, (б) персистентное loose-состояние соседа +
его отрисовка в полосе. Пока полоса рисует loose статически (кадр покоя).
BUG-OCCL-1. Тень дальней колонны перекрывает Kid в (0,4)-(0,5) — Medium
Симптом (2026-07-22): Kid, стоя/прыгая у (0,4)-(0,5), частично перекрыт тёмной штриховкой — это ТЕНЬ/боковая грань ДАЛЬНЕЙ колонны (сзади по глубине), которая НЕ должна окклюдить персонажа. Over-occlusion fore-слоя.
Где: pop_bg.c pop_fore_over_kid/fore_tile — рисует fore футпринт-тайлов
без учёта глубины (какая грань РЕАЛЬНО перед Kid). Порт seg003 redraw_at_char
окклюдит только передние грани; дальняя колонна (за Kid) не должна. Сверить с
SDLPoP redraw_at_char (какие тайлы/грани идут в fore vs back по y-сортировке).
BUG-CEIL-3. Анимация ворот стирает потолок над ними — Medium
Симптом (2026-07-22): при опускании/поднятии решётки шва (анимация openness) полоса кладки у потолка НАД воротами пропадает (см. потолок над левым швом).
Где: pop_bg.c pop_room_redraw_seam_left (и pop_button_redraw/
pop_spike_redraw — общий класс): чёрный bar по col0 row0 стирает потолочную
полосу (row-1), а redraw рисует только сам гейт, не восстанавливая полосу.
Нужно: при перерисовке шва/тайла у потолка перерисовать и срез потолочной полосы
(draw_tile(-1,col) с g_clip_top) над затронутыми колонками.
BUG-SEAM-PINGPONG (#4). Пинг-понг drawn_room у шва с закрытыми воротами — РЕШЁН 2026-07-22
Статус: 2026-07-22, вечер. НАСТОЯЩИЙ корень найден потиковой трассой ЖИВОГО SDLPoP 1.23 (lldb-брейкпоинты на leave_room/bumped/safe_step с логом Char + char_x_left/right; fixes выключены = vanilla). Все прежние гипотезы ниже оставлены для истории — они НЕ были причиной.
КОРЕНЬ (подтверждён трассой + исходником)
set_char_collision (seg006:0723): char_x_right = obj_x/2 + 58, где
load_frame_to_obj (seg008:1728) считает obj_x = 2*char_dx_forward(dx) - 116
и добавляет +1 для кадров «чётного пикселя»:
if ((sbyte)(cur_frame.flags ^ obj_direction) >= 0) ++obj_x;
(бит 0x80 флагов кадра XOR направление; вправо: +1 если бит НЕ стоит).
Деление obj_x/2 — C-усечение К НУЛЮ, поэтому при e = x+dx <= 57
(obj_x < 0, зона левого шва) поправка +1 даёт char_x_right = e+1, а при
e >= 58 формула сокращается к чистому e.
Итог: Kid, осевший после отскока от ворот шва на x=57 (frame15, флаги 0x43 —
бит 0x80 не стоит), имеет char_x_right = 58 и порога leave-left (<=57)
НЕ достигает. Наш движок считал передний край как Kid.x + dx без поправки
→ 57 → ложный leave → пинг-понг.
Эталонный цикл SDLPoP (сн normalized по трассе): стойка x=61 → тап вправо → safe_step(d=0) → step, на первом dx(1) x=62 → bump (edge-триггер) → align 61 → seq47 dx(-4) → x=57 (скрыт за кромкой) → кадры 50/51/52 (cxr 61/60/58, у всех бит 0x80 снят, e>57 — без сдвига) → стойка cxr=58 → leave НЕ срабатывает; тап → safe_step d=3 → x=60 (1/3 видно); тап → step1 → x=61 (2/3 видно); тап → bump → 57 … по кругу. Char.room и drawn_room НЕ меняются вообще.
Фикс (pop_map.c)
char_x_forward_edge(): e = char_dx_forward(dx); if (((flags ^ (dir<0 ? 0x80 : 0)) & 0x80) == 0 && e <= 57) e++; — используется в char_front_coll
(коллизия/bump/edge_distance) и в check_leave (порог ухода). Плюс порт
doortop-гарда leave-right из leave_room (тайл (9,row) = doortop → правого
выхода нет). pop_leave_timer (exit_room_timer) оставлен — он реален в
seg002/seg003.
Гипотезы, ОТПАВШИЕ после трассы (история диагноза)
Симптом (подтверждён пользователем + инструментально)
Kid стоит за решёткой закрытых ворот шва (левый сосед room8 виден в кромке room6). При удержании/нажатии ВПРАВО экран пинг-понгует между двумя состояниями:
- A: показывается room6, Kid у левой кромки за решёткой (спрайт виден на 2/3);
- B: показывается room8, Kid у его правой кромки.
Эталон SDLPoP (описан пользователем): drawn_room всегда остаётся room6, Kid осциллирует у кромки (1/3→2/3→отступил→по кругу), в room8 экран НЕ переключается. То есть у нас лишний свитч A→B.
Тестовый контекст
START_ROOM=6. Ворота room8(0,9) закрыты, видны в col0 room6. Kid-структура
kframe: см. pop_kid.h (frame,x,y,direction,curr_col,curr_row,action,...).
Адрес _Kid и статиков — в .sprinter-cc-roomtest/roomtest.map (сдвигается от
сборки к сборке). MAME: memory mame_mcp_bridge/mame_hdd_test_disk.
Отладочные: J/L — короткий шаг влево/вправо (эмуляция Shift+стрелка).
Инструментальный диагноз (watchpoint на pop_leave_dir)
В момент лишнего свитча A→B: pop_leave_dir=1 (LEFT), Kid frame=15 (СТОЯ,
не transient!), Kid.x=57 (до репроекции +140). То есть:
char_x_right = char_dx_forward(kid_cur_dx()) = Kid.x + frame15.dx = 57+0 = 57.- Порог leave-left (взгляд вправо):
char_x_right <= 57→ срабатывает РОВНО на 57. - Грань ворот (где их держит коллизия) = 61 (
wall_dist_from_left[1]=10 + coll_tile_left_xpos=51). Между 57 и 61 — зазор 4px: Kid НЕ удержан воротами (d=61−57=4≥0 → check_bumped не бампит), но уже на пороге ухода. - Kid оседает на 57 из-за recoil отскока: seq_47 =
act(bumped), dx(-4), frame_50, 51, 52; SEQ_DX(−4) двигает Char.x на −4 суммарно; frame_50.dx=4 компенсирует ТОЛЬКО точку коллизии НА кадре 50, но при возврате в стойку (frame15, dx=0)char_x_right = Char.x = aligned−4 = 57.
Что ИСКЛЮЧЕНО (сверено с исходниками SDLPoP, НЕ причина)
- Формула char_x:
char_x_right = obj_x/2+58 = Char.x+frame.dx(seg006 set_char_collision) — совпадает с нашим char_dx_forward. - Позиция грани ворот:
get_left_wall_xpos = wall_dist_from_left[1](10) + xpos_in_drawn_room(x_bump[9+5])+7 = 10+(184−140)+7 = 61— совпадает с нашим (x_bump[−1+5]=44, +7, +10 = 61). - Порог leave: SDLPoP leave_room looking-right
char_x_right<=57— совпадает. - Данные кадров: frame_50 (image=49,dx=4,flags=0x67) и frame_15 (image=14,dx=0,flags=0x43,sword=9) — БАЙТ-В-БАЙТ как в SDLPoP frame_table_kid.
- seq_47 (act bumped, dx(-4), frame 50/51/52) — совпадает.
Почему точечные фиксы НЕ работают
- Гард в check_leave (подавить leave на закрытых воротах) — это ОТСЕБЯТИНА, не SDLPoP (в leave_room такого нет); откачено.
exit_room_timer=2(порт seg002 exit_room — РЕАЛЬНЫЙ механизм, оставлен как pop_leave_timer): блокирует leave 2 кадра после входа в комнату. НЕ спасает: положение Char.x=57 устойчивое (Kid стоит), а не transient — после истечения таймера leave срабатывает.- По коду SDLPoP raw-рассуждением раз за разом выходит, что leave-left ДОЛЖЕН сработать и там (Char.x=57), но эталон показывает, что нет. Значит различие не в этих кусках, а в ДИНАМИКЕ: где именно оседает Char.x у шва И как вычисляется drawn_room.
КОРЕНЬ (гипотеза)
Наш движок держит drawn_room == комната_Kid ВСЕГДА, а шов подделан:
- коллизия — самодельные снапшоты кромок соседа (g_lcol/g_rcol, 1 колонка) +
x_bump[tcol=-1], а НЕ портcurr_row_coll_room[]/get_all_coll_row/xpos_in_drawn_roomиз seg004; - уход — мгновенный
enter_room(сосед)при пороге char_x, а НЕ отложенный drawn_room черезnext_room/check_the_end.
Из-за упрощённой коллизии Char.x у шва оседает иначе, чем в SDLPoP (у нас ровно 57 = порог), и/или drawn_room переключается там, где в оригинале нет.
План S2/S3 (порт coll_room + отложенный drawn_room) — НЕ потребовался для
этого бага (трасса показала: в эталоне Char.room/drawn_room вообще не
меняются). Остаётся как задел под стражей/двух персонажей в кадре.
Правило проекта: SDLPoP — источник истины, переводить 1:1, НЕ рассуждать «как должно быть». Нужно портировать связку целиком:
-
Раздельные комнаты.
Char.room(реальная комната персонажа) ≠drawn_room(отрисованная). У нас уже естьkid_room(S1) + рендер-смещениеpop_kid_set_render_dx(∓140)(порт xpos_in_drawn_room для СПРАЙТА). -
Коллизия по Char.room через coll_room (seg004, S2). Портировать
check_collisions→get_row_collision_data(строки curr/below/above) →get_left_wall_xpos/get_right_wall_xpos(поxpos_in_drawn_room(x_bump[ col+FIRST_ONSCREEN_COLUMN]), ±140 когда колонка принадлежит соседу) → массивыcurr_row_coll_room[]/_flags[],bump_col_left_of_wall/bump_col_right_of_wall→check_bumped_look_left/right→bumped(). Тайлы берутся из РЕАЛЬНОЙ комнаты колонки (get_tile(room,col,row) для любой комнаты), НЕ из снапшота. Именно это определяет, где Char.x оседает у шва. -
Отрисовка по drawn_room, персонаж со сдвигом. Рисуем drawn_room; Kid (и будущие стражи) — со смещением ±140, если их комната ≠ drawn_room.
-
Отложенная смена drawn_room (seg002/seg000, S3). Уход =
leave_room(seg002:0504, ПОРТ ЦЕЛИКОМ, включая up/down/side, guard'ы climb/standup/turn, doortop-check для right) →goto_other_room(seg002:0486: Char.room=сосед, x±140/y±189).exit_room(seg002:03C7):exit_room_timer+next_room = Char.room.check_the_end(seg000:0FBD):if (next_room != 0 && next_room != drawn_room) drawn_room = next_room; redraw. drawn_room меняется ТОЛЬКО так, а не по нашему прямому enter_room.
Ключевой момент, который надо переводить БУКВАЛЬНО (а не выводить): порядок
вызовов в play_frame (seg000:863 play_frame: … play_kid_frame → exit_room →
check_the_end) + set_char_collision перед leave_room в exit_room.
Карта сегментов: seg002 (leave/goto/exit_room), seg004 (коллизия/coll_room/ bumped), seg006 (set_char_collision/play_seq/frame_table_kid), seg000 (play_frame/check_the_end/draw). Наши файлы: pop_map.c (коллизия/физика/leave), roomtest.c (enter_room/drawn_room/главный цикл), pop_kid.c (кадры/рендер-сдвиг).
Реализовано на 2026-07-22: S1 (kid_room + render_dx) + pop_leave_timer. S2/S3 отложены — баг закрыт odd-pixel поправкой (см. КОРЕНЬ выше). Память: pop_seam_room_model, sdlpop_odd_pixel_char_x.
TODO: оптимизация отрисовки (записано 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. 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-1 idle-skip не требует ничего дополнительного для пик: раз Кида не перерисовываем — heal'а нет — стирать пики нечем — редрой не нужен, он отпадает сам.
Обход всех 24 комнат уровня 1 — поиск багов отрисовки
Инструмент: #define ROOMNAV в roomtest.c — +/- (цифровой блок либо
=/- основного ряда) переключают комнату по номеру (1..24, с обёрткой),
Kid ставится на первый пол, pop_trob_reset() возвращает пики/ворота в
исходное. Номер комнаты — полосками в верхнем борте: слева десятки, справа
единицы (|| |||| = 24). Убирается закомментированием одной строки.
Связность уровня 1 (проверено по res2001.bin, links @1952). Обход графа
от стартовой комнаты 1 показывает: комнаты 13, 18, 24 НЕДОСТИЖИМЫ в
обычной игре. У всех трёх ссылки наружу есть, а на них не ссылается никто
(24: L→9, но у 9 R=0; 13 и 18 связаны только друг с другом). Признак
«комнату выкинули из компоновки, связи не почистили» — несимметричные ссылки
ровно у этих трёх, у остальных 21 симметрия полная:
13 L→22, у 22 R=16 | 18 L→15, у 15 R=12 | 24 L→9, у 9 R=0
13 R→16, у 16 L=22 | 18 R→12, у 12 L=15
| 18 D→19, у 19 U=12
Следствие для обхода: в 13/18/24 возможен «мусор в шве» — наш рендер кромки читает крайнюю колонку соседа ПО ССЫЛКЕ, а сосед этих комнат соседом себя не считает. Это свойство данных, а не наш баг. Приоритет багов в этих трёх комнатах — низкий (в игре их не видно).
| комната | статус | что не так |
|---|---|---|
| 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). Голова Кида поверх падающей на него плиты — см. «НЕ БАГИ» |
| 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 | недостижима в игре |