Спуск Кида с кнопки (room8, кромка (0,6)) рисовался неверно: Кид просвечивал в щель между кнопкой и ближним столбом, а ближняя рука была срезана до одного оторванного пикселя. Две независимые причины. 1. Не был портирован clip_char() (seg006:1749) — оригинал перед add_objtable обрезает спрайт персонажа сверху по y_clip[curr_row+1], когда тайл над головой стена или пол. Порт: pop_clip_char_top() (pop_map.c, метрики по set_char_collision) + новый примитив gfx_blit_cols_part() в libbgi (блит column-major с пропуском skip верхних строк; обрезка сверху бесплатна — колонка непрерывна в ОЗУ, сдвигается только старт). gfx_blit_cols стал тонкой обёрткой над ним. kid_heal чистит уже ОБРЕЗАННЫЙ прямоугольник, иначе стирается кромка пола над срезом. 2. climb_overlay_tile (порт draw_floor_overlay, seg008:1E3A) выбирал ветку по СЫРОМУ коду тайла, а get_tile_to_draw (seg008:240) подменяет нажатую кнопку на floor/stuck. Тайл-кнопка не проходил тест floor → уходил в draw_other_overlay, который кладёт поверх Кида ВЕСЬ тайл вместо узкой кромки floor_left_overlay[frame-137]. Фикс — tile_code_drawn(): одна подстановка на все слои (fore_only_tile/overlay_mid_tile перестали её дублировать, W3 −293 Б). Проверено покадрово в MAME (шаг gv + снимок): кадры спуска 148..138 чистые, после приземления на кнопке мусора нет. Заодно: gfx_heal_noclip() (пара к gfx_blit_noclip; heal 11658 -> 8982 тактов) и rest_pending в pop_trob — холостой проход по 30 тайлам только когда есть отложенные редрои пик/кнопок (pop_process_trobs 89-111К -> 70-92К тактов). START_ROOM временно = 8 (отладка спуска с кнопки), вернуть на 6/старт уровня. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.4 KiB
План: порт clip_char() — обрезка спрайта персонажа
Статус: в работе с 2026-07-28. Контекст: баг «спуск Кида с кнопки в комнате 8» (Kid просвечивает в щель между кнопкой и ближним столбом, мусор на кромке).
Симптом
room 8, Kid спускается (climbdown) с тайла-кнопки у ближней колонны. Спрайт Кида нарисован ЦЕЛИКОМ, включая часть, которая в оригинале обрезана по линии пола: видно «просвет» Кида в щели между кнопкой и колонной и мусор на кромке.
Корень
Не портирован clip_char() (SDLPoP seg006.c:1749, вызывается из
add_kid_to_objtable/add_guard_to_objtable, seg008.c:1671/1690, ПОСЛЕ
set_char_collision/set_objtile_at_char/redraw_at_char* и ПЕРЕД
add_objtable). Оригинал кладёт в objtable не только позицию спрайта, но и
прямоугольник клипа obj_clip_{top,bottom,left,right}; блиттер рисует только
внутри него. Мы рисуем без клипа вообще.
Что делает оригинал (дословно)
reset_obj_clip() → left=0, top=0, right=320, bottom=192.
Дальше (упрощая C-трюк с глобалью curr_tile2, которую ставит каждый
get_tile: X == wall || tile_is_floor(curr_tile2) — это «тайл X = стена
ИЛИ пол»):
T_L = get_tile(room, char_col_left, char_top_row)
T_R = get_tile(room, char_col_right, char_top_row)
if (T_L — стена или пол) &&
( (action == stand && (frame == 79 || frame == 81)) /* прыжок вверх / зацеп */
|| (T_R — стена или пол) )
{
clip_row = Char.curr_row + 1;
clip_y = y_clip[clip_row]; /* y_clip[] = {-60, 3, 66, 129, 192} */
if (clip_row == 1 || (clip_y < obj_y && clip_y - 15 < char_top_y))
obj_clip_top = char_top_y = clip_y;
}
Смысл: y_clip[row+1] — верхняя граница СВОЕЙ полосы ряда. Если над головой
пол/стена — всё, что выше этой линии, не рисуется (персонаж «уходит под пол»).
Для ряда 0 (clip_row == 1) клип применяется БЕЗУСЛОВНО.
Метрики из set_char_collision (seg006:0723):
char_x_left = obj_x/2 + 58(и-= char_width_half, если смотрит вправо)char_x_right = char_x_left + char_width_half,char_width_half = (w+1)/2char_top_y = obj_y - h + 1; если>= 192→0char_top_row = y_to_row_mod4(char_top_y)char_col_left = MAX(get_tile_div_mod(char_x_left), 0),char_col_right = MIN(get_tile_div_mod(char_x_right), 9)
Второй блок clip_char — obj_clip_right (doortop / стена / зеркало для
виса-полёта-подъёма при взгляде влево, кадры 137..139) и ветка кадров 224..228
(выход в дверь уровня). Фаза 2, не в этом заходе.
Наши точки касания
roomtest/pop_kid.ckid_draw()— сам считаетobj_x/obj_y/w/hи блитит черезgfx_blit_cols(bx, top + POP_YOFF, img, flip); запоминает прямоугольник вkid_l{x,y,w,h}[page]дляkid_heal().roomtest/pop_map.c— тамget_tile(),tile_is_floor(),y_to_row(),get_tile_div_mod_m7(), Kid-структура. Аналогичный расчёт габарита уже есть вcheck_spike_below()(строка ~1198) — брать за образец.libbgi/common/gfx_blit_cols.c— клип по экрану есть (приy < 0пропускаетsyверхних строк колонки), но произвольной верхней границы нет.
Шаги
- libbgi:
gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip, int sy, int h)— «пропуститьsyверхних строк спрайта, нарисоватьh». Реализация = телоgfx_blit_colsс предустановленнымиsy/h(источник+= sy, экранныйy += sy). Прототип вlibbgi/include/gfx.h. Стоимость: 0 в общем пути (обычныйgfx_blit_colsвызывает то же ядро). - pop_map.c:
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h)— порт первого блокаclip_char; возвращает КОМНАТНЫЙ y (0 = клипа нет). Экспорт вpop_map.h. - pop_kid.c: в
kid_draw()после расчётаtop—ct = pop_clip_char_top(...); еслиct > top→sy = ct - top, рисоватьgfx_blit_cols_part(bx, ct + POP_YOFF, img, flip, sy, h - sy); вkid_l*[page]класть ОБРЕЗАННЫЙ прямоугольник (иначе heal чистит лишнее и стирает кромку пола). То же дляkid_draw_splash— там оригинал делаетreset_obj_clip(), т.е. splash НЕ клипится (ничего не менять). - Сборка:
make -C libbgi,make -C applications/PoP/roomtest,make size-check; вывести свободное место в W2/W3 (порог 512 Б). - Проверка в MAME (
mame_hdd_test_disk, полный рестарт после пересборки образа): комната 6 → переход в 8 → влезть на кнопку → спуск. Сверять с эталоном: живой SDLPoP той же позой (см.pop_check_sdlpop_first).
Риски / что проверить отдельно
char_top_rowсчитаетсяy_to_row_mod4— у насy_to_row()даёт −1 для полосы у потолка;get_tile(row −1)уже умеет ряд 2 верхнего соседа.- Kid ниже комнаты (
char_top_y >= 192→0) — обязателен ресет, иначе клип прыгнет. clip_row == 1(ряд 0) — клип БЕЗУСЛОВНЫЙ: проверить, что не режет Кида в обычной стойке на ряду 0 (условие внешнегоifпро пол/стену над головой должно отсекать).- Клип меняет прямоугольник heal → возможен «хвост» на второй странице дабл-буфера: проверять оба кадра (SPACE — выключить дабл-буфер).
Дальше (Фаза 2, отдельно)
obj_clip_right (doortop/стена/зеркало) — нужен для виса и подъёма при
взгляде влево; и obj_clip_left для зеркала (уровень 4). Требует клипа по
колонкам в gfx_blit_cols (обрезка справа = уменьшить w) — дёшево, но
без тестовой сцены проверять нечем.
См. memory: pop_clip_char_todo, pop_backtable_vs_midtable,
pop_check_sdlpop_first, gfx_blit_noclip_fast.