Files
Sprinter-SDCC/applications/PoP/docs/clip_char_plan.md
T
snark13 6582154381 PoP roomtest: clip_char (верхняя обрезка спрайта) + подстановка нажатой кнопки
Спуск Кида с кнопки (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>
2026-07-28 09:50:47 +03:00

7.4 KiB
Raw Blame History

План: порт 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)/2
  • char_top_y = obj_y - h + 1; если >= 1920
  • char_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_charobj_clip_right (doortop / стена / зеркало для виса-полёта-подъёма при взгляде влево, кадры 137..139) и ветка кадров 224..228 (выход в дверь уровня). Фаза 2, не в этом заходе.

Наши точки касания

  • roomtest/pop_kid.c kid_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 верхних строк колонки), но произвольной верхней границы нет.

Шаги

  1. 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 вызывает то же ядро).
  2. 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.
  3. pop_kid.c: в kid_draw() после расчёта topct = pop_clip_char_top(...); если ct > topsy = 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 НЕ клипится (ничего не менять).
  4. Сборка: make -C libbgi, make -C applications/PoP/roomtest, make size-check; вывести свободное место в W2/W3 (порог 512 Б).
  5. Проверка в 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 >= 1920) — обязателен ресет, иначе клип прыгнет.
  • 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.