5f5eefc9fc
Комната 12, вис и подтягивание на кромке loose-плиты над дырой от соседней упавшей: плита рисовалась ПОД Кидом — он лез на неё «с переднего края» вместо проёма. Не хватало двух кусков draw_tile: 1. draw_loose (seg008:0A38) кладёт нижнюю грань плиты (loose_fram_bottom) В ОБЕ таблицы — backtable И foretable, безусловно. Это единственный кусок тайла с таким поведением: у обычного пола bottom идёт только в backtable, а fore_id = 0. Добавлено в fore_tile (наш проход foretable по тайлам футпринта Кида); ceiling-случай это уже делал отдельно. 2. draw_tile_base (seg008:0A8E) подставляет id: у loose верх плиты берётся из loose_fram_left, у opener'а без пола слева — 148. В нашем midtable-оверлее (overlay_mid_tile) стоял голый tile_table.base_id, а у loose он 0 — верх плиты в оверлей не попадал, и поверх Кида ложилась только передняя грань. Перенесён draw_tile_base целиком. Проверено в MAME: кадр виса на кромке целой плиты (frame 89, x=95, col 2) — плита закрывает Кида, наружу торчат только пальцы, как в SDLPoP. Голова стоящего Кида поверх падающей НА НЕГО плиты — артефакт САМОГО оригинала (сверено с SDLPoP v1.24), не чинить: записано в bug_list.md (раздел «НЕ БАГИ») и в memory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
464 lines
35 KiB
Markdown
464 lines
35 KiB
Markdown
# 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.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 | пофикшено | вис/подтягивание на кромке loose-плиты: плита рисовалась ПОД Кидом. Не хватало двух кусков `draw_tile`: (а) `draw_loose` кладёт нижнюю грань плиты И в foretable (поверх персонажа), (б) `draw_tile_base` подставляет верх плиты из `loose_fram_left`, а в нашем midtable-оверлее стоял голый `base_id` (у loose он 0). Голова Кида поверх падающей на него плиты — см. «НЕ БАГИ» |
|
||
| 13 | недостижима в игре | |
|
||
| 14 | | |
|
||
| 15 | | |
|
||
| 16 | | |
|
||
| 17 | | |
|
||
| 18 | недостижима в игре | |
|
||
| 19 | | |
|
||
| 20 | | |
|
||
| 21 | | |
|
||
| 22 | | |
|
||
| 23 | | |
|
||
| 24 | недостижима в игре | |
|