PoP roomtest: непрозрачные нижние грани + edge-триггер бампа + 2 колонки шва

Три фикса, найденные разбором в MAME.

1. Просвечивание Кида сквозь торец кнопки (подъём/спуск у кромки).
   draw_tile_bottom (seg008:570) рисует нижнюю грань блиттером
   blitters_0_no_transp: пиксель индекса 0 заливается ЧЁРНЫМ, а не
   пропускается.  У граней кнопок (env 96/149) и нижних кадров loose (73/74)
   последняя строка целиком нулевая — наш упаковщик переводил 0 в 0xFF для
   ВСЕХ спрайтов, и она становилась дырой, сквозь которую был виден Kid за
   тайлом (на запечённом фоне не видно — там и так чёрное).
   pop_pack_bg.py: NO_TRANSP_ENV_IDS пакуется с сохранением индекса 0 как
   ЦВЕТА (0x50 = чёрный в палитре env).  Ноль тактов в рантайме.

2. Разворот в проёме опущенной решётки выкидывал Кида на другую её сторону.
   check_bumped проверял столкновение УРОВНЕМ («край зашёл за грань»), поэтому
   разворот на месте внутри тайла-стены менял, какую грань мы меряем, и это
   читалось как новое столкновение → Kid.x = char_dx_forward(d) швырял его
   сквозь решётку.  Оригинал (check_collisions + get_row_collision_data,
   seg004:0004) считает флаги перекрытия от ГАБАРИТА персонажа, БЕЗ
   направления, и зовёт bumped() только на переходе 0 -> 1 (вправо смотрит на
   0x0F, влево на 0xF0).  Порт этого edge-триггера: флаги от габарита + память
   прошлого кадра по колонке, сброс при смене комнаты (в оригинале это
   несовпадение prev_coll_room/curr_row_coll_room).  Проверено: теперь
   разворот даёт уход в комнату 8 (leave_room, взгляд влево, char_x_left<=54)
   — то есть туда, где Kid физически и стоит.

3. Снимок шва расширен до ДВУХ колонок с каждой стороны (lcol_fg[6]/
   rcol_fg[6]: col9+col8 и col0+col1).  Kid, стоящий В шве (curr_col=-1),
   смотрит вперёд на колонку -2, где раньше была мнимая стена (get_tile ->
   TILE_WALL).  Оригинал резолвит любую колонку через find_room_of_tile
   (seg006:005D).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 10:47:58 +03:00
parent 6582154381
commit 5e09bd9d5a
9 changed files with 104 additions and 21 deletions
+60 -7
View File
@@ -108,12 +108,23 @@ static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */
static uint8_t grab_timer; /* после зацепа (12): блокирует climb-up */
/* Связи комнаты (0 = нет соседа) + кромки швов: g_lcol = col9 левого соседа
* (за col0), g_rcol = col0 правого соседа (за col9). Для коллизии на швах —
/* Состояние edge-триггера бампа (порт prev_coll_flags, seg004): колонка, для
* которой сохранены флаги перекрытия, и сами флаги (бит0 = левая грань стены,
* бит1 = правая). BUMP_COL_NONE — данных нет (смена комнаты/старт). */
#define BUMP_COL_NONE ((int8_t)-128)
static int8_t bump_prev_col;
static uint8_t bump_prev_flags;
/* Связи комнаты (0 = нет соседа) + кромки швов: g_lcol[0..2] = col9 левого
* соседа (за col0), g_lcol[3..5] = его col8 (колонка −2); g_rcol симметрично
* (col0 / col1 правого соседа, колонки 10 и 11). Для коллизии на швах —
* иначе Kid, войдя в соседнюю комнату у самой кромки (curr_col=-1/10), стоял
* бы «в стене» уровня и in_wall выбрасывал бы его. Задаются pop_map_set_edges. */
* бы «в стене» уровня и in_wall выбрасывал бы его. ВТОРАЯ колонка нужна,
* когда Kid стоит В шве и смотрит НАРУЖУ: передняя колонка тогда −2 (или 11),
* и мнимая стена там выпихивала его сквозь решётку (баг разворота у ворот).
* Оригинал резолвит любую колонку через find_room_of_tile (seg006:005D).
* Задаются pop_map_set_edges. */
static uint8_t g_link_l, g_link_r, g_link_u, g_link_d;
static uint8_t g_lcol[3], g_rcol[3];
static uint8_t g_lcol[6], g_rcol[6];
/* ---- Плита-ПОТОЛОК: loose ряда 2 комнаты СВЕРХУ ------------------- *
* В оригинале это тот же loose, просто get_tile ушёл в комнату сверху
@@ -150,8 +161,12 @@ void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
const uint8_t *lcol_fg, const uint8_t *rcol_fg)
{
g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down;
if (lcol_fg) { g_lcol[0] = lcol_fg[0]; g_lcol[1] = lcol_fg[1]; g_lcol[2] = lcol_fg[2]; }
if (rcol_fg) { g_rcol[0] = rcol_fg[0]; g_rcol[1] = rcol_fg[1]; g_rcol[2] = rcol_fg[2]; }
if (lcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_lcol[i] = lcol_fg[i]; }
if (rcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_rcol[i] = rcol_fg[i]; }
/* смена комнаты — колонки перепроецированы, прошлые флаги перекрытия
* недействительны (в оригинале prev_coll_room != curr_row_coll_room и
* бампа на кадре входа нет). */
bump_prev_col = BUMP_COL_NONE;
}
static uint8_t get_tile(int8_t col, int8_t row)
@@ -165,8 +180,12 @@ static uint8_t get_tile(int8_t col, int8_t row)
g_curr_tile = TILE_WALL;
else if (col == -1 && g_link_l) /* левый шов: тайл col9 комнаты слева */
g_curr_tile = g_lcol[row] & 0x1F;
else if (col == -2 && g_link_l) /* на тайл глубже в комнату слева (col8) */
g_curr_tile = g_lcol[3 + row] & 0x1F;
else if (col == 10 && g_link_r) /* правый шов: тайл col0 комнаты справа */
g_curr_tile = g_rcol[row] & 0x1F;
else if (col == 11 && g_link_r) /* на тайл глубже в комнату справа (col1) */
g_curr_tile = g_rcol[3 + row] & 0x1F;
else if (col < 0 || col > 9)
g_curr_tile = TILE_WALL; /* край уровня / нет соседа = стена */
else
@@ -767,7 +786,41 @@ static void check_bumped(void)
tcol = get_tile_div_mod_m7(char_front_coll());
t = get_tile(tcol, Kid.curr_row);
wt = wall_type(t);
if (!wt) return;
/* EDGE-триггер (порт check_collisions + get_row_collision_data, seg004:0004).
* Оригинал считает флаги перекрытия от ГАБАРИТА персонажа, БЕЗ направления:
* 0x0F = left_wall_xpos < char_x_right_coll («в левую грань стены»)
* 0xF0 = right_wall_xpos > char_x_left_coll («в правую грань»)
* и зовёт bumped() ТОЛЬКО когда нужный флаг перешёл 0 -> 1 (взгляд вправо
* смотрит на 0x0F, влево — на 0xF0). Без этого проверка «уровнем» ловит
* ЛЮБОЕ перекрытие: Kid, стоящий В проёме опущенной решётки, при развороте
* на месте получал ложный бамп и выпихивался сквозь неё в соседнюю
* комнату. С флагами от габарита разворот их не меняет — бампа нет. */
{
uint8_t f = 0, prev, need;
if (wt) {
int coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
int lw = wall_dl[wt] + coll_left; /* get_left_wall_xpos */
int rw = coll_left - wall_dr[wt] + TILE_RIGHTX; /* get_right_wall_xpos */
int e = char_x_forward_edge(), wh = ((int)kid_fp_width() + 1) >> 1;
int xl, xr;
if (Kid.direction < 0) { xl = e; xr = e + wh; } /* габарит персонажа */
else { xr = e; xl = e - wh; }
if (kid_cur_flags() & FRAME_THIN) { xl += 4; xr -= 4; }
if (lw < xr) f |= 1;
if (rw > xl) f |= 2;
}
/* прошлый кадр: та же колонка — её флаги; другая — считаем, что не
* перекрывали; «нет данных» (смена комнаты) — считаем, что перекрывали
* всё, чтобы на кадре входа бампа не было (в оригинале это даёт
* несовпадение prev_coll_room и curr_row_coll_room). */
prev = (bump_prev_col == BUMP_COL_NONE) ? 3u
: (bump_prev_col == tcol ? bump_prev_flags : 0u);
bump_prev_col = tcol;
bump_prev_flags = f;
if (!wt) return;
need = (Kid.direction < 0) ? 2u : 1u;
if (!(f & need) || (prev & need)) return; /* не перекрыл / уже перекрывал */
}
if (t == TILE_GATE && gate_passable(tcol, Kid.curr_row))
return; /* открытые ворота (шов/внутри) — Kid проходит (не бамп) */
d = dist_from_wall_forward(t, tcol);