a89b8b30c6
Сверено в SDLPoP: Кид, стоящий колонкой правее лежащего трупа, и там рисуется поверх его головы. Значит порт верен, а артефакт врождённый: set_objtile_at_char приписывает персонажа РОВНО ОДНОМУ тайлу, ширина спрайта на выбор тайла не влияет, и всё, что стоит правее, перекрывает выступающую часть тела. Механизма «широкий объект в нескольких тайлах» в оригинале нет — сверено с draw_objtable_items_at_tile, sort_curr_objs и веткой tile_object_redraw == 0xFF (та про оверлеи пола). Закрытая запись перечисляет все четыре корня, которые пришлось снять по очереди: порядок по роли вместо тайла; своя регрессия с общим окном fore-клипа; колонка трупа из тайла вместо X; непортированная ветка actions_1_run_jump. Записано и то, как отличать остаток от этих багов, и цена «починки» остатка (тайл по центру габарита = отход от эталона). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
705 lines
53 KiB
Markdown
705 lines
53 KiB
Markdown
# roomtest — архив закрытых багов
|
||
|
||
Сюда переезжает всё, что **закрыто**: подтверждённые фиксы, снятые
|
||
диагнозы, осознанные решения «не делать». Открытые баги — в
|
||
[`bug_list.md`](bug_list.md), текущие задачи — в [`TASKS.md`](TASKS.md).
|
||
|
||
Файл существует не ради истории как таковой: половина записей ниже — это
|
||
разбор КОРНЯ (odd-pixel арифметика `char_x`, подстановка тайла нажатой
|
||
кнопки, баг кодогенератора SDCC), и он экономит часы, когда похожий симптом
|
||
всплывёт снова. Прежде чем заводить новый баг — грепни здесь по симптому.
|
||
|
||
---
|
||
|
||
## Проверено в MAME 2026-08-01 (ревизия L1-TRIAGE)
|
||
|
||
Три бага стояли как **Critical** с 2026-07-21 и по исходникам выглядели
|
||
закрытыми, но переподтверждены не были. Прогон в MAME (roomtest, `ROOMNAV`,
|
||
чтение `_Kid` через мост) закрыл все три.
|
||
|
||
### BUG-1. Боковой переход через ворота: Kid проваливается на row 1 — **ЗАКРЫТ (не воспроизводится)**
|
||
|
||
**Был симптом:** при проходе через ОТКРЫТЫЕ ворота в соседнюю комнату
|
||
(через шов) Kid оказывался на ряду row 1 вместо row 0.
|
||
|
||
**Проверка 2026-08-01, точный сценарий бага.** Комната 6, Kid на ряду 0;
|
||
осторожный шаг на кнопку (0,2) — решётка room8 (0,9) поднимается; удержание
|
||
← через открытый шов:
|
||
|
||
| момент | `room` | `x` | `y` | `curr_col` | `curr_row` |
|
||
|--------|--------|-----|-----|-----------|-----------|
|
||
| на кнопке в room6 | 6 | 97 | 55 | 2 | 0 |
|
||
| после перехода | **8** | 181 | **55** | 8 | **0** |
|
||
|
||
Ряд и Y сохранены, Kid стоит на полу ряда 0 (скриншот `triage_06.png`).
|
||
Провала нет.
|
||
|
||
**Заодно снят и сам диагноз записи** — он был неверен. В записи стояло:
|
||
«Y/`curr_row` при боковом переходе НЕ репроецируются, в отличие от
|
||
`check_leave_below`». Это не дефект, а **точное поведение оригинала**:
|
||
`goto_other_room` (`SDLPoP/src/seg002.c:390`) для направлений left/right
|
||
меняет ТОЛЬКО `Char.x` (±140), а `Char.y`/`curr_row` трогает исключительно
|
||
для up/down. Наш `check_leave` (`pop_map.c:1402`) делает ровно то же.
|
||
Реальной причиной симптома была, судя по всему, кромочная коллизия — её
|
||
закрыл `char_x_forward_edge` (см. BUG-SEAM-PINGPONG ниже).
|
||
|
||
### BUG-2. Возврат из комнаты назад: Kid отбрасывается обратно (ping-pong) — **ЗАКРЫТ (не воспроизводится)**
|
||
|
||
**Был симптом:** после перехода в соседнюю комнату попытка сразу вернуться
|
||
приводила к тому, что Kid снова закидывался в ту же комнату — выйти нельзя.
|
||
|
||
**Проверка 2026-08-01.** Шов room2↔room3 (ряд 1, без ворот — чистый
|
||
горизонтальный переход), с намеренным разворотом СРАЗУ после пересечения:
|
||
|
||
| действие | `room` | `x` | `curr_col` |
|
||
|----------|--------|-----|-----------|
|
||
| старт в room2 | 2 | 86 | 1 |
|
||
| держим → | **3** | 122 | 3 |
|
||
| сразу держим ← | **2** | 195 | 9 |
|
||
| сразу держим → | **3** | 85 | 1 |
|
||
| сразу держим ← | **2** | 183 | 8 |
|
||
|
||
Комната меняется РОВНО один раз на пересечение, туда и обратно, без
|
||
осцилляции. Механизм на месте: `pop_leave_timer` (порт `exit_room_timer`,
|
||
`pop_map.c:112,1553`) + `char_x_forward_edge` (`pop_map.c:365`).
|
||
|
||
### BUG-3. Climb-up на тайл-кнопку: неправильная окклюзия — **ЗАКРЫТ фиксом от 2026-07-28**
|
||
|
||
**Был симптом:** при подтягивании на тайл, верх которого — кнопка
|
||
(opener/closer), Kid рисовался ПОВЕРХ кнопки вместо того, чтобы быть
|
||
перекрытым её передней гранью.
|
||
|
||
**Почему закрыт.** Запись требовала: «трактовать нажатую кнопку как
|
||
floor-тайл в climb-overlay, учесть подстановку из `get_tile_to_draw`».
|
||
Ровно это и сделано `tile_code_drawn()` — `climb_overlay_tile`
|
||
(`pop_bg.c:1346`) берёт ПОДСТАВЛЕННЫЙ код тайла, а не сырой. То есть
|
||
BUG-3 — дубль пункта «спуск с кнопки (room8, кромка (0,6))» из раздела
|
||
«Исправлено», заведённый до фикса.
|
||
|
||
**Оговорка, чтобы не выдавать желаемое:** покадрово в MAME снимался СПУСК
|
||
(кадры 148..138). Подъём идёт через ту же ветку и ту же таблицу
|
||
`FLOOR_LEFT_OVERLAY[fidx]`, поэтому отдельного дефекта тут быть не может,
|
||
но визуально направление «вверх» не переснималось. Если при сквозном
|
||
прохождении (L1-PASS) увидишь Kid поверх кнопки на подъёме — заводи заново.
|
||
|
||
---
|
||
|
||
## Косметика окклюзии — закрыта кодом, список отставал (сверено 2026-08-01)
|
||
|
||
Четыре записи от 2026-07-22 висели как открытые Medium. Каждая описывала
|
||
недостающий кусок порта; каждый из них с тех пор написан, но записи никто не
|
||
снял. Ниже — что именно закрывает каждую.
|
||
|
||
### BUG-CEIL-1. Прыжок вверх: руки Kid рисуются ПОВЕРХ потолка — **ЗАКРЫТ**
|
||
|
||
**Был симптом:** при прыжке вверх (SEQ up, кадры 67..79) руки/голова Kid
|
||
заходили в полосу кладки у потолка (row −1) и рисовались ПОВЕРХ неё.
|
||
|
||
**Чем закрыт:** `ceil_over_kid_tile()` (`pop_bg.c:1275`) — порт «нижняя грань
|
||
потолка и кадр плиты-потолка идут в FOREtable», т.е. поверх персонажа.
|
||
Зовётся из `pop_fore_over_kid` (`pop_bg.c:1575`) и из fore-прохода стража
|
||
(`:1607`). Комментарий на месте прямо называет причину: «иначе руки
|
||
прыгающего Kid лезут на кромку потолка».
|
||
|
||
### BUG-CEIL-2. Тряска/разбитие loose-плиты в потолке (row −1) — **ЗАКРЫТ**
|
||
|
||
**Был симптом:** loose-плита в ряду 2 верхнего соседа (room5 (2,5) → потолок
|
||
room6 над (0,5)) при прыжках Kid на (0,5) не тряслась и не разбивалась —
|
||
loose-состояние соседней комнаты не тянулось.
|
||
|
||
**Чем закрыт:** отдельное состояние плиты-потолка `pop_ceil_modif[10]`
|
||
(`pop_bg.c:547`, ведёт `pop_map`) + пара `pop_ceil_shake_draw()` /
|
||
`pop_ceil_bake_empty()` (`pop_bg.c:815,827`): дрожание рисуется на текущей
|
||
странице поверх фона, а провал «запекается» в ОЗУ-копию, чтобы heal его
|
||
сохранял. В полосе у потолка виден только НИЗ плиты (`draw_tile_aboveroom`),
|
||
всё выше режется клипом `POP_YOFF` — как в оригинале.
|
||
|
||
Из этого следует, что и запись «требует персистентного per-room modifier
|
||
соседей, это Фаза P0 gates_spikes_plan» больше не верна: понадобился не общий
|
||
механизм, а один массив на 10 байт под конкретный случай.
|
||
|
||
### BUG-CEIL-3. Анимация ворот стирает потолок над ними — **ЗАКРЫТ**
|
||
|
||
**Был симптом:** при анимации решётки шва полоса кладки у потолка НАД
|
||
воротами пропадала — чёрный `bar` по col0 стирал ряд −1, а redraw его не
|
||
восстанавливал.
|
||
|
||
**Чем закрыт:** `pop_room_redraw_seam_left()` (`pop_bg.c:793`) больше не
|
||
трогает полосу потолка — `bar` идёт с `POP_YOFF + 3`, а не с `POP_YOFF`:
|
||
низ полосы кладки на room-space y=2, бары ворот начинаются с y=3
|
||
(`gate_top_y = dby − 62`). Приятный побочный эффект: отпала необходимость
|
||
перерисовывать `draw_tile(-1,0)` на КАЖДОМ кадре анимации решётки — то есть
|
||
фикс не только косметический, но и вдвое дешевле прежнего.
|
||
|
||
### BUG-OCCL-1. Тень дальней колонны перекрывает Kid — **ЗАКРЫТ**
|
||
|
||
**Был симптом:** Kid у (0,4)-(0,5) частично перекрыт тёмной штриховкой —
|
||
боковой гранью ДАЛЬНЕЙ колонны, которая окклюдить персонажа не должна
|
||
(over-occlusion fore-слоя, рисовавшего fore футпринт-тайлов без учёта
|
||
глубины).
|
||
|
||
**Чем закрыт:** разделение слоёв по признаку из оригинала, а не по нашим
|
||
соображениям о глубине (`overlay_mid_tile`, `pop_bg.c:1367`). Ключ,
|
||
подтверждённый трассой оригинала: `draw_tile_right`, `draw_tile_anim_right` и
|
||
`draw_loose` кладут спрайты через `add_backtable` НАПРЯМУЮ, минуя
|
||
`ptr_add_table` — значит правая грань левого соседа («шахматка» столба 93,
|
||
blueline, грани пик/loose соседа, кадр loose) **всегда** рисуется ПОД
|
||
персонажем. Через `ptr_add_table` (→ midtable при `draw_other_overlay`) идут
|
||
только «floor B» 42 при левом соседе-поле, `draw_tile_base`,
|
||
`draw_tile_anim` (свои пики) и `draw_tile_bottom`.
|
||
|
||
---
|
||
|
||
## BUG-DOOR-CLIP. Подъём по лестнице двери уровня: нет обрезки по правому косяку — **ИСПРАВЛЕН 2026-08-01**
|
||
|
||
**Был симптом (найден пользователем сразу после L1-EXIT):** при подъёме по
|
||
лестнице за дверью уровня (кадры 224..228) силуэт Кида вылезал ПРАВЕЕ правого
|
||
косяка проёма. По высоте обрезка была корректна.
|
||
|
||
**Причина — недопортированная половина `clip_char`** (seg006:1231). Для
|
||
кадров двери оригинал ставит ДВА клипа, у нас был только первый:
|
||
|
||
```c
|
||
if (frame >= frame_224_exit_stairs_8 && frame < 229) {
|
||
obj_clip_top = leveldoor_ybottom + 1;
|
||
obj_clip_right = leveldoor_right;
|
||
}
|
||
```
|
||
|
||
Отдельная ловушка: комментарий в SDLPoP говорит «frames 217..228», а КОД
|
||
проверяет `>= frame_224_exit_stairs_8`, то есть **224..228**. Портировано по
|
||
коду.
|
||
|
||
**Почему именно обрезка, а не fore-слой:** створка и косяк уходят в оригинале
|
||
ЦЕЛИКОМ в backtable (`draw_leveldoor`, все `add_backtable`), то есть рисуются
|
||
ПОД персонажем и перекрыть его не могут. Единственный способ спрятать
|
||
поднимающегося — срезать сам спрайт.
|
||
|
||
**Фикс:**
|
||
- libbgi: `gfx_blit_cols_part_w(..., uint8_t maxw)` — обрезка СПРАВА у
|
||
колоночного блита. Для column-major это ровно уменьшение числа колонок,
|
||
то есть внутри ядра механизм уже был (так же клипается край экрана:
|
||
`w = _bgi_maxx + 1 - x`), наружу не было выведено. Тело блита переехало
|
||
туда, `gfx_blit_cols_part` стал тонкой обёрткой (maxw=0) — тем же приёмом,
|
||
каким `gfx_blit_cols` уже обёрнут вокруг `gfx_blit_cols_part`. Работает и
|
||
при flip: первые `maxw` нарисованных колонок всегда ложатся в ЛЕВУЮ часть
|
||
футпринта.
|
||
- PoP: `pop_leveldoor_right` / `pop_leveldoor_ybottom` (порт одноимённых
|
||
глобалов) пишет `draw_leveldoor` в `pop_state` — их читает `clip_char` из
|
||
другого банка; `pop_clip_char_right()` отдаёт границу, `kid_draw`
|
||
превращает её в `maxw` и уводит эти кадры с noclip-пути на общий.
|
||
`kid_lw` (прямоугольник heal) тоже сужается — стираем ровно нарисованное.
|
||
|
||
**Проверено в MAME:** `pop_leveldoor_right = 176` — ровно
|
||
`(draw_xh<<3) + 48` для двери комнаты 9; `pop_leveldoor_ybottom` = 112 у
|
||
закрытой створки и 69 у поднятой (сходится с формулой оригинала).
|
||
Отрисовка подтверждена пользователем на живом подъёме.
|
||
|
||
---
|
||
|
||
## BUG-SEAM-PINGPONG (#4). Пинг-понг drawn_room у шва с закрытыми воротами — **РЕШЁН 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 (нормализовано по трассе): стойка 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 экран НЕ переключается.
|
||
|
||
### Инструментальный диагноз (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.
|
||
|
||
### Задел, который НЕ понадобился: порт coll_room + отложенный drawn_room
|
||
|
||
Трасса показала, что в эталоне `Char.room`/`drawn_room` вообще не меняются,
|
||
поэтому план S2/S3 для этого бага не потребовался. Остаётся заготовкой под
|
||
стражей/двух персонажей в кадре:
|
||
|
||
1. **Раздельные комнаты.** `Char.room` ≠ `drawn_room`. Уже есть `kid_room`
|
||
(S1) + рендер-смещение `pop_kid_set_render_dx(∓140)`.
|
||
2. **Коллизия по Char.room через coll_room (seg004, S2).** Портировать
|
||
`check_collisions` → `get_row_collision_data` → `get_left_wall_xpos`/
|
||
`get_right_wall_xpos` → `curr_row_coll_room[]`/`_flags[]`,
|
||
`bump_col_left_of_wall`/`bump_col_right_of_wall` → `check_bumped_look_*` →
|
||
`bumped()`. Тайлы — из РЕАЛЬНОЙ комнаты колонки, НЕ из снапшота.
|
||
3. **Отрисовка по drawn_room**, персонаж со сдвигом ±140.
|
||
4. **Отложенная смена drawn_room (seg002/seg000, S3):** `leave_room` →
|
||
`goto_other_room` → `exit_room` (`next_room`) → `check_the_end`.
|
||
|
||
Реализовано на 2026-07-22: S1 + `pop_leave_timer`.
|
||
Память: [[pop_seam_room_model]], [[sdlpop_odd_pixel_char_x]].
|
||
|
||
---
|
||
|
||
## Решено НЕ делать
|
||
|
||
### 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 сек после схода с кнопки). В покое — 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), задетую полосой. **Пиксель-чувствительно** — обязательна выверка в
|
||
MAME по кромкам.
|
||
|
||
**Решение:** оставляем как есть — стоимость транзиентная, в бюджет
|
||
помещаемся. Делать, только если упрёмся в кадровый бюджет на сценах с
|
||
воротами.
|
||
|
||
---
|
||
|
||
## Исправлено
|
||
|
||
- **Спуск с кнопки (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). **Этим же
|
||
фиксом закрыт BUG-3** (см. выше).
|
||
|
||
- **Шов ворот жёг 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), а
|
||
не наш баг; «починка» увела бы от эталона. Отличать от соседних случаев,
|
||
которые БЫЛИ нашими багами и исправлены: вис/подтягивание на кромке плиты и
|
||
падение вместе с плитой — там плита обязана быть поверх Кида.
|
||
|
||
- **Ноги стоящего Кида поверх головы лежащего трупа стража.** Комната 21:
|
||
страж убит, комната покинута и открыта заново (у трупа своя запомненная X),
|
||
Кид СТОИТ на одну колонку правее тела — его ноги рисуются поверх головы.
|
||
Проверено в SDLPoP (2026-08-03): там ровно то же.
|
||
|
||
Корень — устройство движка, а не наш порт. `set_objtile_at_char`
|
||
(seg006:13F3) приписывает персонажа РОВНО ОДНОМУ тайлу, и ширина спрайта на
|
||
выбор тайла не влияет никак; дальше `redraw_needed_tiles` (seg008:1B06)
|
||
обходит тайлы рядами 2,1,0 и колонками 0..9, и кто позже — тот поверх.
|
||
Стоящий Кид укладывается примерно в колонку, поэтому у него это незаметно, а
|
||
лежащий труп занимает две-три колонки, но «принадлежит» левой. Всё, что
|
||
стоит правее, перекрывает выступающую часть тела. Механизма «широкий объект
|
||
участвует в нескольких тайлах» в оригинале нет — сверено с
|
||
`draw_objtable_items_at_tile`, `sort_curr_objs` и веткой
|
||
`tile_object_redraw == 0xFF` (последняя про оверлеи пола, не про персонажей).
|
||
|
||
Отличать от того, что БЫЛО нашими багами в этом же месте и исправлено
|
||
(BUG-DRAWORDER-1): колонка трупа считалась из тайла вместо X, ветка
|
||
`actions_1_run_jump` не была портирована, окно fore-клипа затиралось стражем.
|
||
Если когда-нибудь захочется «починить» и это — минимальный вариант — брать
|
||
тайл по ЦЕНТРУ габарита, а не по левой границе; но это осознанный отход от
|
||
эталона, и в других позах порядок поменяется в обратную сторону.
|
||
|
||
- **Комнаты 13, 18, 24 недостижимы в обычной игре** — свойство ДАННЫХ уровня 1,
|
||
не наш баг. Обход графа от стартовой комнаты (по `res2001.bin`, links @1952)
|
||
показывает: у всех трёх ссылки наружу есть, а на них не ссылается никто
|
||
(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 возможен «мусор в шве» — наш рендер кромки читает
|
||
крайнюю колонку соседа ПО ССЫЛКЕ, а сосед этих комнат соседом себя не
|
||
считает. Приоритет багов в этих трёх комнатах — низкий (в игре не видно).
|
||
|
||
---
|
||
|
||
## Проверено в MAME 2026-08-03 (прогон уровня 1: 11 наблюдений → 6 корней)
|
||
|
||
Сырой список наблюдений с обхода всех комнат разобран по корням.
|
||
Проверка — MAME + мост `mame-z80`: чтение `_Kid` по адресу из
|
||
`.sprinter-cc-roomtest/roomtest.noi`, `ROOMNAV` для навигации, потиковые
|
||
трассы, скриншоты.
|
||
|
||
**Оговорка о полноте проверки.** Каждый корень закрыт тем, что его СОБСТВЕННЫЙ
|
||
сценарий больше не воспроизводится; сквозного прохождения уровня и поиска
|
||
регрессий в соседней механике автоматика не делала. Чек-лист для ручной
|
||
перепроверки — в [`bug_list.md`](bug_list.md), раздел «Ручная перепроверка
|
||
фиксов». Особое внимание — порту `check_collisions`: он переписал ВСЮ
|
||
горизонтальную коллизию.
|
||
|
||
### BUG-LVLSTATE-1. Уровень был немутабельным — **ЗАКРЫТ**
|
||
|
||
**Симптомы:** выпитое зелье / поднятый меч / разбитая плита возвращались при
|
||
возврате в комнату; иногда кувшина нет, а пузырёк над ним крутится; в
|
||
комнате 15 раз в 1–2 с мигал контур кладки. «Первое остаётся, остальное
|
||
возвращается».
|
||
|
||
**Корень.** Уровень лежал в EMM-странице только на чтение, `enter_room`
|
||
перезаливал `room_fg[30]` из неё при каждом входе, а персистентность держала
|
||
таблица переопределений на **8 записей** (`OVR_MAX` в `roomtest.c`) — девятая
|
||
и дальше молча терялись. Второй хвост: `pop_trob.c` брал тип тайла из
|
||
СТРАНИЦЫ (`pop_level_tile_raw`), поэтому заводил trob зелья/меча там, где
|
||
предмет уже поднят — отсюда пузырёк без кувшина и мигание от `animate_sword`.
|
||
|
||
**Фикс.** `pop_level_set_tile()` пишет тайл ПРЯМО в страницу уровня (порт
|
||
`curr_room_tiles[…] = …`: `do_pickup` seg006:1671, `remove_loose` seg007:0EB8,
|
||
`loose_land` seg007:11E8). Таблица `ovr_*` удалена. Эталонная копия
|
||
foretable — в той же странице по смещению `0x1000` (страница 16 КБ, данных
|
||
2.3 КБ).
|
||
|
||
**Проверено:** комната 22, зелье (0,6) выпито → выход в 23 → возврат: кувшина
|
||
нет, пузырька нет; ROOMNAV ставит Кида уже на (0,6), потому что тайл стал
|
||
полом.
|
||
|
||
### BUG-RESPAWN-1. Респавн не перезагружал уровень — **ЗАКРЫТ**
|
||
|
||
**Как в оригинале:** цикл `play_level` (seg003:57) на КАЖДОЙ итерации, в том
|
||
числе после смерти, зовёт `load_level()` — уровень читается заново.
|
||
|
||
**Фикс.** `pop_level_reset_tiles()` (восстановление foretable из эталонной
|
||
копии) в `pop_start_level` рядом с `pop_trob_reset()`.
|
||
|
||
**Проверено:** после смерти от стража зелье в комнате 22 снова на месте.
|
||
|
||
### BUG-DEATH-1. Смерть от меча не доводилась до конца — **ЗАКРЫТ**
|
||
|
||
**Симптом:** страж убивает Кида, тот «воскресает» на месте и его убивают
|
||
снова, по кругу.
|
||
|
||
**Корень.** `hurt_by_sword` ставил seq_85 и обнулял HP, но `Kid.alive`
|
||
оставался −1, а `pop_kid_dead` (по нему главный цикл делает респавн) взводил
|
||
только путь пик/падения. В оригинале это первая строка `control_kid`
|
||
(seg006:0CD1): `if (Char.alive < 0 && hitp_curr == 0) Char.alive = 0;`.
|
||
|
||
**Фикс.** Порт этой ветки в начало `pop_ctrl_tick` + `pop_kid_dead = 1`.
|
||
|
||
**Проверено:** страж в комнате 21 убивает Кида → респавн в стартовой позиции
|
||
уровня (комната 1), цикла нет.
|
||
|
||
### BUG-GATE-ANIM-1. Ворота в отрисованной комнате не перерисовывались — **ЗАКРЫТ**
|
||
|
||
**Корень.** `pop_process_trobs` продвигал модификатор ворот, но пометки
|
||
перерисовки не ставил; ворота рисовались только при полной отрисовке комнаты
|
||
и в `pop_room_redraw_seam_left`. В оригинале `animate_door` (seg007:0522)
|
||
заканчивается `draw_trob()` (seg007:01E6).
|
||
|
||
**Фикс.** Вид перерисовки `POP_RD_GATE` + `pop_gate_redraw(row,col)` в
|
||
`pop_bg.c` (wipe зоны решётки в ячейке ПРАВОГО соседа + `draw_tile`), пометка
|
||
из `pop_process_trobs` (текущая страница каждый кадр, обе — на последнем).
|
||
|
||
**На уровне 1 это ровно ОДНА решётка:** room5 (0,5). Все остальные стоят в
|
||
колонке 9, их бары рисуются уже в соседней комнате — там работает
|
||
`pop_room_redraw_seam_left`.
|
||
|
||
**Проверено:** кнопка room5 (0,4) — решётка (0,5) поднимается на экране.
|
||
|
||
### BUG-COLL-1. Bump искал стену только в колонке переднего края — **ЗАКРЫТ**
|
||
|
||
**Симптомы:** пробегание сквозь закрытую решётку (комната 12, room5 (0,9));
|
||
влёт внутрь стены на длинном прыжке (комната 6).
|
||
|
||
**Два корня.**
|
||
1. `check_bumped` брал ОДНУ колонку — ту, в которой оказался передний край.
|
||
Для решётки в col9 окно этой колонки всего 4 px (201..204), беговой кадр
|
||
его перескакивает — бампа нет, а `check_leave` тут же уводит в соседнюю
|
||
комнату. Оригинал (`check_collisions`, seg004:0004) считает флаги
|
||
перекрытия для ВСЕХ колонок ряда и берёт колонку с переходом флага 0→1.
|
||
2. Наш guard `action == FREEFALL || MIDAIR → return` в `check_bumped`. В
|
||
оригинале таких гардов НЕТ: `bumped_fall` (seg004:04E4) специально
|
||
разбирает `actions_4_in_freefall`. Отсюда влёт в стену в прыжке.
|
||
|
||
**Фикс.** Полный порт `check_collisions` + `get_row_collision_data` +
|
||
`is_obstacle` + `bumped(delta, push_dir)`; колонки считаются от −2 до 11,
|
||
чтобы решётки СОСЕДНЕЙ комнаты (швы) бампили как свои; гарды в `check_bumped`
|
||
приведены к оригинальным (только вис и подтягивание).
|
||
|
||
**Проверено:** комната 5, бег вправо в закрытую решётку (0,9) — Кид упирается
|
||
(x встаёт на 60 в системе комнаты 1 и дальше не растёт).
|
||
|
||
**Остаток:** экран при этом перелистывается на соседнюю комнату, и Кид в шве
|
||
не рисуется — отдельный баг BUG-SEAM-DRAW-1 (модель straddle S3), см.
|
||
`bug_list.md`.
|
||
|
||
### BUG-STANDUP-1. Вставание из приседа у стены роняло сквозь пол — **ЗАКРЫТ**
|
||
|
||
**Симптом:** комната 5, падение с кнопки (0,6) на щебень (2,7) с уроном,
|
||
присед — и при вставании провал в комнату 6.
|
||
|
||
**Корень — лишний guard `if (Kid.action == ACT_BUMPED) return;` в
|
||
`bumped_floor`** (в оригинале, seg004:0520, там `if (Char.alive)`). Цепочка:
|
||
вставание двигает Кида на 1 px в стену → бамп → `bumped_floor` прижимает `y`
|
||
к полу, отменяя `dy(−2)` из начала `medland` → наш guard возвращает
|
||
управление вместо `seq_47`, `medland` доигрывает свои `dy(+1)`,`dy(+1)` уже
|
||
ОТ пола → Кид НИЖЕ пола → следующий бамп читает беззнаковую разность как
|
||
«высоко над полом» → `bumped_fall` → выпадение вниз. У оригинала `seq_47`
|
||
обрывает `medland`, лишних `dy` нет.
|
||
|
||
Найдено потиковой трассой + временной диагностикой (колонка бампа = 8, тайл
|
||
после разрешения = 14 щебень, ветка = `bumped_floor`).
|
||
|
||
**Заодно** убран полу-порт опционального `FIX_STAND_ON_THIN_AIR`: у нас была
|
||
взята только его первая часть (кадры вставания 110..119 требуют пол), без
|
||
парной правки seqtbl (`dx(1)→dx(0)`, `dx(−4)→dx(−3)`), которую применить
|
||
нельзя — seqtbl извлечён из данных оригинала. Вернулись к ванильному
|
||
`seg006:909` (только кадр 109).
|
||
|
||
---
|
||
|
||
# Вторая волна прогона 2026-08-03 (вечер)
|
||
|
||
<a id="bug-kbd-4"></a>
|
||
## BUG-KBD-4 (+ BUG-KBD-3). Shift: сначала залипал, потом стал отваливаться — **ЗАКРЫТ**
|
||
|
||
**Симптом (вторая редакция).** Залипание ушло, но появилось обратное: при
|
||
зажатом Shift второй-третий-четвёртый тап стрелки отрабатывал уже не
|
||
осторожным шагом, а бегом. Для PoP это ХУЖЕ залипания: игрок рассчитывает
|
||
на короткий шаг, а Кид убегает в яму или на пики.
|
||
|
||
**Почему обе прежние редакции были неправильны.** Это был размен между
|
||
двумя способами «починить» потерю байта при Rx-overrun SIO (FIFO 3 байта):
|
||
|
||
1. исключать модификаторы из сброса — потерянный break Shift снять нечем,
|
||
Shift залипает навсегда (BUG-KBD-3);
|
||
2. сбрасывать всю карту, как DSS (`KBD_Receiver_Overrun` в `KEYINTER.ASM`
|
||
чистит и `KEYCTRL`, и `KEY_FLG`) — залипания нет, но Shift сносится
|
||
каждым overrun'ом (BUG-KBD-4).
|
||
|
||
Причина, по которой вариант 2 бил так часто, замерена: при зажатом Shift
|
||
клавиатура обёртывает КАЖДЫЙ расширенный код служебной парой, поэтому тап
|
||
стрелки — это не 5 байт, а 10, и overrun почти гарантирован.
|
||
|
||
**Что нашлось (замер в MAME, watchpoint на порт данных SIO + чтение карты
|
||
`_kbdraw_down`).** Эта самая обёртка — «fake shift» — и есть решение, а не
|
||
помеха. Клавиатура шлёт `E0 F0 12` перед расширенным make и `E0 12` после
|
||
его break, и шлёт ТОЛЬКО пока Shift реально зажат. Проверено обоими
|
||
шифтами: правый обёртывается своим кодом `0x59` (bit 1 байта 11 карты), левый
|
||
— `0x12` (bit 2 байта 2). Это непрерывное и прямое свидетельство реального
|
||
состояния Shift — единственное доступное, потому что опросить состояние у
|
||
PS/2 нельзя, а typematic повторяет только ПОСЛЕДНЮЮ нажатую клавишу, то есть
|
||
стрелку, а не Shift.
|
||
|
||
**Фикс.** Оба декодера (`libc/irq/_irq_tramp.c`, `libc/kbd/kbd_raw_poll.c`)
|
||
читают обёртку в обе стороны:
|
||
|
||
- увидели fake shift → реальный Shift ЗАЖАТ → взвести plain-бит (бит кладёт
|
||
общий писатель: достаточно обнулить префиксы, и он попадёт в plain-половину
|
||
карты как make; в расширенную половину не пишем — это не клавиша);
|
||
- расширенный make БЕЗ предшествующей обёртки → Shift ОТПУЩЕН → снять
|
||
plain-биты обоих шифтов.
|
||
|
||
`kbd_raw_sync` вернулся к исключению модификаторов — теперь это безопасно:
|
||
состояние Shift подтверждается независимо от того, что съел overrun, а
|
||
залипание снимается первым же нажатием стрелки.
|
||
|
||
**Побочно найдена и исправлена своя ошибка** в новом коде `kbd_raw_poll`:
|
||
после `bit 1, a` в `A` лежал `pending`, а не скан-код, — обычные
|
||
(нерасширенные) клавиши декодировались бы из мусора. Оба декодера приведены
|
||
к одной логике.
|
||
|
||
**Раскладка трамплина.** Клавиатурный блок перевалил за 127 байт, а `jp`
|
||
внутри трамплина запрещён (код копируется в W2 побайтно, абсолютные
|
||
само-ссылки сломают копию). Поэтому префиксные обработчики переехали вплотную
|
||
к своим `cp`, а посередине тела стоят три ретранслятора (`tr_kbd_hub`,
|
||
`tr_hub_notkbd`, `tr_hub_dss`) — до них дотягиваются `jr` и сверху, и снизу.
|
||
В `kbd_raw_poll` такого ограничения нет, там три перехода стали `jp`.
|
||
|
||
**Проверено в MAME:**
|
||
|
||
- Shift зажат, пять тапов стрелки подряд → `LSh` остаётся `04` во всех пяти,
|
||
`ovr = 0`, расширенная половина карты чистая;
|
||
- штатное отпускание Shift → `LSh = 00`;
|
||
- искусственно залипший Shift (бит записан в карту отладчиком) → снимается
|
||
ПЕРВЫМ же тапом стрелки;
|
||
- `make size-check`: 70 программ, роста нет.
|
||
|
||
<a id="bug-respawn-2"></a>
|
||
## BUG-RESPAWN-2. После respawn стражи остаются мёртвыми — **ЗАКРЫТ**
|
||
|
||
**Вопрос из отчёта:** «после respawn — должны ли оживать стражники?»
|
||
**Ответ по SDLPoP: да.** Цикл `play_level` (seg003:57) на КАЖДОЙ итерации —
|
||
в том числе после смерти Кида — делает `load_level()`, следом `pos_guards()`
|
||
(seg003:83), а затем `Guard.charid = charid_2_guard; Guard.direction =
|
||
dir_56_none`. То есть стражи — такая же часть данных уровня, как тайлы, и
|
||
перезагрузка файла возвращает их всех.
|
||
|
||
**Корень у нас.** `gstate_init()` (живая копия таблицы стражей: тайл,
|
||
направление, мастерство, поза трупа) вызывался ТОЛЬКО при загрузке уровня.
|
||
Рестарт возвращал тайлы (`pop_level_reset_tiles`), но не стражей: в `gstate`
|
||
оставался сохранённый `leave_guard`'ом `seq_hi != 0`, и `pop_guard_enter`
|
||
поднимал стража трупом с `guardhp_curr = 0`.
|
||
|
||
**Фикс.** `pop_level_reset_guards()` (тот же `gstate_init`) + `pop_guard_reset()`
|
||
в `pop_start_level`, рядом с `pop_level_reset_tiles()`. Туда же уехал
|
||
`pop_loose_mob_reset()` — настоящий `mobs_count = 0` из `start_level`.
|
||
|
||
**Проверено в MAME:** комната 21, страж жив (`alive = -1`, hp 3) → убит читом
|
||
`K` (`alive = 0`, hp 0) → Кид убит стражем → респавн → возврат в комнату 21:
|
||
страж снова `alive = -1`, hp 3.
|
||
|
||
<a id="bug-draworder-1"></a>
|
||
## BUG-DRAWORDER-1. Кид рисовался поверх тела стража — **ЗАКРЫТ**
|
||
|
||
**Симптом.** В оригинале Кид проходит ЗА телом убитого стража; у нас — перед ним.
|
||
|
||
**Три разных корня, найденные по очереди.** Стоит того, чтобы перечислить: два
|
||
первых захода были неполны, и каждый следующий кадр от тестера вскрывал новый
|
||
слой.
|
||
|
||
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` = позже.
|
||
Фикс: `guard_over_kid()` в `roomtest.c`.
|
||
|
||
2. **Своя же регрессия: Кид полез поверх передних столбов.** Окно fore-клипа
|
||
(`pop_fore_set_clip`) одно на всех, и его ставит каждый, кто рисует
|
||
персонажа. Пока страж рисовался строго ДО Кида, к моменту
|
||
`pop_fore_over_kid` в окне оказывался Кид и всё сходилось само. Как только
|
||
порядок стал переменным, при «страж поверх» окно оставалось СТРАЖЬИМ, и
|
||
fore-проход Кида отсекался целиком.
|
||
Фикс: `kid_fore_clip_restore()` — окно восстанавливается явно, а не
|
||
«по счастливому порядку вызовов».
|
||
|
||
3. **Колонка трупа была нулевой.** `leave_guard` сохраняет тайл как
|
||
`get_tilepos(0, row)`, то есть колонку 0 всегда, а `enter_guard` у нас брал
|
||
`curr_col` оттуда и следом перетирал X запомненным значением. В памяти это
|
||
было видно прямо: `col=0` при `x=95`. Оригинал (seg002:180) выводит колонку
|
||
ИЗ X: `Char.curr_col = get_tile_div_mod_m7(Char.x)`. У живого стража
|
||
незаметно (X там сама считается из колонки), у запомненного ТРУПА — ломало
|
||
порядок. Колонка нужна не только отрисовке: на неё смотрят коллизия и
|
||
`check_can_guard_see_kid`.
|
||
|
||
4. **Ветка `actions_1_run_jump` оказалась не «упрощаемой».** Сначала я решил,
|
||
что её можно не портировать. Кадр в движении показал `ACT=1`: в беге
|
||
оригинал берёт тайл не из `curr_row`/`curr_col`, а из нижнего ряда и ЛЕВОЙ
|
||
колонки ГАБАРИТА (`char_bottom_row`/`char_col_left` из `set_char_collision`,
|
||
seg006:0723). При беге вправо левая колонка меньше `curr_col` примерно на
|
||
тайл — потому бегущий Кид и уходит за объекты справа. Отсюда «некоторые
|
||
кадры бега рисовали Кида поверх тела». Считается для ОБОИХ персонажей:
|
||
`enter_guard` ставит `action = 1` и стражу, односторонний учёт дал бы
|
||
перекос в другую сторону. Тонкость: `char_col_left` берётся по НЕ
|
||
утоньшённой границе — поправку THIN оригинал применяет только к `*_coll`.
|
||
|
||
**Проверено в MAME:** после возврата в комнату у трупа `col=5` при `x=135`
|
||
(было `col=0`); Кид слева от тела — и стоя, и в кадре бега — рисуется за телом;
|
||
передние столбы снова перекрывают Кида.
|
||
|
||
**Остаток, который НЕ баг.** Ноги стоящего Кида поверх головы трупа, когда он
|
||
стоит колонкой правее тела, — врождённое свойство движка (один спрайт = один
|
||
тайл при спрайте шире колонки). Сверено в SDLPoP: там так же. Подробности — в
|
||
разделе «НЕ БАГИ» выше.
|