# 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). ## Заметки - Тестовые клавиши осторожного шага: **J** = шаг влево, **L** = шаг вправо (эмуляция Shift+стрелка), см. `pop_ctrl.c` `KBD_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, НЕ рассуждать «как должно быть». Нужно портировать связку целиком: 1. **Раздельные комнаты.** `Char.room` (реальная комната персонажа) ≠ `drawn_room` (отрисованная). У нас уже есть `kid_room` (S1) + рендер-смещение `pop_kid_set_render_dx(∓140)` (порт xpos_in_drawn_room для СПРАЙТА). 2. **Коллизия по 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 оседает у шва. 3. **Отрисовка по drawn_room, персонаж со сдвигом.** Рисуем drawn_room; Kid (и будущие стражи) — со смещением ±140, если их комната ≠ drawn_room. 4. **Отложенная смена 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`). Это корректно, но лишнее для пик, до которых Киду дела нет. **Надо:** перерисовывать тайл пики, только если 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. 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 | | | | 10 | | | | 11 | | | | 12 | | | | 13 | недостижима в игре | | | 14 | | | | 15 | | | | 16 | | | | 17 | | | | 18 | недостижима в игре | | | 19 | | | | 20 | | | | 21 | | | | 22 | | | | 23 | | | | 24 | недостижима в игре | |