6582154381
Спуск Кида с кнопки (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>
113 lines
7.4 KiB
Markdown
113 lines
7.4 KiB
Markdown
# План: порт `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`; если `>= 192` → `0`
|
||
- `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_char` — `obj_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()` после расчёта `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 НЕ клипится (ничего не менять).
|
||
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 >= 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`.
|