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>
This commit is contained in:
@@ -94,6 +94,19 @@ openness рисует `bar(BLACK)` по всему col0 + **полный `draw_t
|
||||
|
||||
## Исправлено
|
||||
|
||||
- **Спуск с кнопки (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).
|
||||
|
||||
- **Шов ворот жёг 50% кадра в покое (закрытая решётка)** — ИСПРАВЛЕНО
|
||||
2026-07-22. Причина — **баг кодогенератора SDCC z80** (memory
|
||||
`sdcc_z80_cmp_store_a_bug`): `if (m[9] != seam_sig) seam_sig = m[9];`
|
||||
|
||||
@@ -91,7 +91,18 @@ static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
}
|
||||
|
||||
/* heal в room-space (со сдвигом POP_YOFF) — все heal слоя фона/loose. */
|
||||
static void heal_off(int x, int y, int w, int h) { gfx_heal(x, y + POP_YOFF, w, h); }
|
||||
static void heal_off(int x, int y, int w, int h)
|
||||
{
|
||||
int sy = y + POP_YOFF;
|
||||
/* Без клипа — линейным ядром (~2.9×, как gfx_blit_noclip): общий
|
||||
* gfx_heal платит за клип/16-бит/split, а не за пиксели (замер:
|
||||
* 11 658 тактов на heal 22×22). */
|
||||
if (x >= 0 && sy >= 0 && w > 0 && h > 0 && w < 256 && h < 256 &&
|
||||
x + w <= 320 && sy + h <= 256)
|
||||
gfx_heal_noclip(x, sy, (uint8_t)w, (uint8_t)h);
|
||||
else
|
||||
gfx_heal(x, sy, w, h);
|
||||
}
|
||||
|
||||
/* Область, куда в этом кадре рисовался ОВЕРЛЕЙ (не футпринт fore, а
|
||||
* draw_other_overlay/floor_overlay/полоса потолка). Оверлей включается
|
||||
@@ -271,6 +282,20 @@ static uint8_t tile_mod(int row, int col)
|
||||
return g_bg[row * 10 + col];
|
||||
}
|
||||
|
||||
/* Тип тайла «как он РИСУЕТСЯ» — порт подстановки нажатой кнопки из
|
||||
* get_tile_to_draw (seg008:240): opener → floor, closer → stuck, когда таймер
|
||||
* связи > 1. Любой слой, который выбирает ветку отрисовки по типу тайла,
|
||||
* обязан смотреть на ПОДСТАВЛЕННЫЙ тип (иначе кнопка, на которой стоит Kid,
|
||||
* ведёт себя как «не пол» — напр. climb-оверлей уходил в draw_other_overlay
|
||||
* и закрашивал Kid целым тайлом вместо узкой кромки floor_left_overlay). */
|
||||
static uint8_t tile_code_drawn(int row, int col)
|
||||
{
|
||||
uint8_t code = tile_code(row, col);
|
||||
if (code == 0x0F) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||
else if (code == 0x06) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||
return code;
|
||||
}
|
||||
|
||||
/* seg008.c:1255 (без FAKE_TILES): сосед — стена, если его тип == 20.
|
||||
* На кромке col0 сосед по умолчанию = стена (как render_room). */
|
||||
static uint8_t wall_modifier(int row, int col)
|
||||
@@ -682,7 +707,7 @@ void pop_torch_draw(int row, int col, uint8_t modif)
|
||||
int x = (col + 1 < 10 ? COL_XH[col + 1] : COL_XH[col] + 4) * 8 + 8;
|
||||
int yb = 63 * row + 62 - 40;
|
||||
if (modif >= 9) return;
|
||||
heal_off(x - 2, yb - 19, 22, 22);
|
||||
heal_off(x - 1, yb - 18, 18, 19);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pot_b((uint8_t)(modif + 1), x, yb);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
@@ -988,12 +1013,10 @@ static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=
|
||||
|
||||
static void fore_only_tile(int row, int col)
|
||||
{
|
||||
uint8_t code = tile_code(row, col);
|
||||
const piece *t;
|
||||
/* Нажатая кнопка → пол/stuck (get_tile_to_draw), иначе поверх Kid лезет
|
||||
* передняя грань КНОПКИ. */
|
||||
if (code == 0x0F) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||
else if (code == 0x06) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||
uint8_t code = tile_code_drawn(row, col);
|
||||
const piece *t;
|
||||
t = &tile_table[code];
|
||||
int x = COL_XH[col] * 8;
|
||||
int dmy = 63 * row + 62;
|
||||
@@ -1055,13 +1078,24 @@ static void fore_tile(int row, int col) /* передний слой одно
|
||||
* 137..144 индексирует floor_left_overlay. */
|
||||
static const uint8_t FLOOR_LEFT_OVERLAY[8] = {32, 151, 151, 150, 150, 151, 32, 32};
|
||||
|
||||
static void other_overlay_tile(int row, int col); /* fwd (draw_floor_overlay -> else) */
|
||||
|
||||
static void climb_overlay_tile(int row, int col, uint8_t fidx)
|
||||
{
|
||||
uint8_t code = tile_code(row, col);
|
||||
/* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую
|
||||
* Kid спускается/подтягивается, закрашивается целым тайлом */
|
||||
uint8_t code = tile_code_drawn(row, col);
|
||||
int x, dmy, dby;
|
||||
const piece *t;
|
||||
if (code != 1 && code != 3 && code != 5 && code != 19) return; /* не floor-тайл */
|
||||
if (tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */
|
||||
if (code != 1 && code != 3 && code != 5 && code != 19) {
|
||||
/* seg008:1E3A draw_floor_overlay: floor-оверлей рисуется только для
|
||||
* floor/pillar/stuck/torch, а для ОСТАЛЬНЫХ тайлов вызывается
|
||||
* draw_other_overlay() — иначе кнопка (opener/closer), за кромку
|
||||
* которой Kid спускается, не перекрывает его. */
|
||||
other_overlay_tile(row, col);
|
||||
return;
|
||||
}
|
||||
x = COL_XH[col] * 8;
|
||||
dmy = 63 * row + 62;
|
||||
dby = dmy + 3;
|
||||
@@ -1082,16 +1116,12 @@ static void climb_overlay_tile(int row, int col, uint8_t fidx)
|
||||
* draw_tile_base, draw_tile_anim (свои пики) и draw_tile_bottom. */
|
||||
static void overlay_mid_tile(int row, int col)
|
||||
{
|
||||
uint8_t code = tile_code(row, col);
|
||||
uint8_t lcode = tile_code(row, col - 1);
|
||||
uint8_t code = tile_code_drawn(row, col);
|
||||
uint8_t lcode = tile_code_drawn(row, col - 1);
|
||||
const piece *t;
|
||||
int x = COL_XH[col] * 8;
|
||||
int dby = 63 * row + 65;
|
||||
int dmy = dby - 3;
|
||||
if (code == 0x0F) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||
else if (code == 0x06) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||
if (lcode == 0x0F) { if ((pop_doorlink2(tile_mod(row, col - 1)) & 0x1F) > 1) lcode = 1; }
|
||||
else if (lcode == 0x06) { if ((pop_doorlink2(tile_mod(row, col - 1)) & 0x1F) > 1) lcode = 5; }
|
||||
t = &tile_table[code];
|
||||
ov_mark(x, 63 * row + 2, 64, 64); /* тайл + правый сосед (грани) */
|
||||
if (code != 20 && lcode == 1) /* draw_tile_right: только 42 */
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <sprite.h>
|
||||
#include "pop_kid.h"
|
||||
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
|
||||
#include "pop_map.h" /* pop_clip_char_top (clip_char) */
|
||||
#include "kid_data.h"
|
||||
|
||||
kid_t Kid;
|
||||
@@ -292,18 +293,32 @@ void kid_draw(void)
|
||||
if (w && h) {
|
||||
uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */
|
||||
int bx = obj_x + KID_XOFF;
|
||||
int skip = 0; /* строк спрайта срезано сверху */
|
||||
int ct;
|
||||
if (flip)
|
||||
bx -= (int)w; /* seg008: xpos -= image->w */
|
||||
top = obj_y - (int)h + 1 + KID_YOFF;
|
||||
uint8_t dp = gfx_get_draw_page(); /* в какую страницу рисуем */
|
||||
/* clip_char (seg006:1749): тайл над головой = стена/пол → всё выше
|
||||
* линии полосы своего ряда НЕ рисуется. Иначе Kid просвечивает
|
||||
* сквозь щели кладки при спуске/висе. */
|
||||
ct = pop_clip_char_top(kid_fp_x, obj_y, w, h);
|
||||
if (ct > top) {
|
||||
skip = ct - top;
|
||||
if (skip >= (int)h) skip = (int)h; /* обрезан целиком */
|
||||
}
|
||||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
||||
gfx_blit_cols(bx, top + POP_YOFF, img, flip); /* +YOFF: центрирование */
|
||||
if (skip < (int)h) /* +YOFF: центрирование */
|
||||
gfx_blit_cols_part(bx, top + skip + POP_YOFF, img, flip, skip, 0);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
|
||||
* узко, только по прямоугольнику Кида и только при выходе. */
|
||||
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
|
||||
kid_lx[dp] = bx; kid_ly[dp] = top;
|
||||
kid_lw[dp] = w; kid_lh[dp] = h; kid_lvalid[dp] = 1;
|
||||
pop_clip_sprite(bx, w, top + skip + POP_YOFF, (int)h - skip);
|
||||
/* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над
|
||||
* срезом (мусор/дыра в кладке на второй странице дабл-буфера). */
|
||||
kid_lx[dp] = bx; kid_ly[dp] = top + skip;
|
||||
kid_lw[dp] = w; kid_lh[dp] = (uint16_t)((int)h - skip);
|
||||
kid_lvalid[dp] = (skip < (int)h);
|
||||
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
|
||||
@@ -1050,6 +1050,54 @@ void pop_loose_tick(void)
|
||||
/* y_to_row_mod4 (seg006): ряд по y с оборотом mod4 (TILE_SIZEY=63). */
|
||||
static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); }
|
||||
|
||||
/* ---- clip_char (seg006:1749), первый блок: обрезка спрайта СВЕРХУ ----- *
|
||||
* Оригинал перед add_objtable кладёт в objtable прямоугольник клипа; блиттер
|
||||
* рисует персонажа только внутри него. Верхняя граница: если тайл НАД
|
||||
* головой (ряд char_top_row) — стена или пол, всё выше линии своей полосы
|
||||
* (y_clip[curr_row+1]) не рисуется. Без этого Kid, ушедший головой под пол
|
||||
* (спуск с кромки/кнопки, вис), просвечивает сквозь щели кладки.
|
||||
*
|
||||
* Хитрость исходника: `X == wall || tile_is_floor(curr_tile2)` — curr_tile2
|
||||
* ставит сам get_tile, т.е. это просто «тайл X = стена ИЛИ пол».
|
||||
*
|
||||
* Метрики габарита — порт set_char_collision (seg006:0723); в отличие от
|
||||
* check_spike_below колонки берутся get_tile_div_mod БЕЗ −7.
|
||||
*
|
||||
* Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */
|
||||
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h)
|
||||
{
|
||||
static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 };
|
||||
int wh = ((int)w + 1) >> 1; /* char_width_half */
|
||||
int xl = obj_x / 2 + 58; /* char_x_left */
|
||||
int xr, top_y, clip_y;
|
||||
int8_t cL, cR, trow, clip_row;
|
||||
uint8_t t;
|
||||
|
||||
if (Kid.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
||||
xr = xl + wh; /* char_x_right */
|
||||
top_y = obj_y - (int)h + 1; /* char_top_y */
|
||||
if (top_y >= 192) top_y = 0; /* весь спрайт ниже комнаты */
|
||||
trow = y_to_row((int16_t)top_y); /* char_top_row */
|
||||
cL = get_tile_div_mod(xl); if (cL < 0) cL = 0;
|
||||
cR = get_tile_div_mod(xr); if (cR > 9) cR = 9;
|
||||
|
||||
t = get_tile(cL, trow);
|
||||
if (t != TILE_WALL && !tile_is_floor(t)) return 0;
|
||||
/* кадр 79 (прыжок вверх) / 81 (зацеп) в стойке клипятся и по одной грани */
|
||||
if (!(Kid.action == 0 && (Kid.frame == 79 || Kid.frame == 81))) {
|
||||
t = get_tile(cR, trow);
|
||||
if (t != TILE_WALL && !tile_is_floor(t)) return 0;
|
||||
}
|
||||
|
||||
clip_row = (int8_t)(Kid.curr_row + 1);
|
||||
if (clip_row < 0 || clip_row > 4) return 0;
|
||||
clip_y = y_clip[clip_row];
|
||||
/* ряд 0 — клип безусловный; иначе только когда линия реально режет спрайт */
|
||||
if (clip_row == 1 || (clip_y < obj_y && clip_y - 15 < top_y))
|
||||
return clip_y;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* check_leave_below (порт leave_room, seg002: leave_dir=3 при chary>=211):
|
||||
* Kid пересёк нижнюю границу комнаты (провал в дыру ИЛИ спуск-зацеп/climbdown
|
||||
* — единый триггер по y). Репроекция в кадр комнаты снизу (goto_other_room
|
||||
|
||||
@@ -93,6 +93,13 @@ extern uint8_t pop_fell_out;
|
||||
* Порядок как в оригинале (seg000 play_kid_frame). */
|
||||
void pop_phys_tick(void);
|
||||
|
||||
/* clip_char (seg006:1749), верхняя граница клипа спрайта персонажа: если тайл
|
||||
* над головой — стена/пол, всё выше линии своей полосы не рисуется (Kid,
|
||||
* ушедший головой под пол при спуске/висе, не просвечивает сквозь щели).
|
||||
* obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер.
|
||||
* Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */
|
||||
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h);
|
||||
|
||||
/* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после
|
||||
* pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */
|
||||
#define EDGE_CLOSER 0 /* край уступа над ямой (дистанция = до кромки) */
|
||||
|
||||
@@ -41,6 +41,12 @@ static uint8_t button_rest[POP_ROOMTILES];
|
||||
* расхождение было именно в безусловном редрое). */
|
||||
static uint8_t trob_drawn[POP_ROOMTILES];
|
||||
|
||||
/* Сколько тайлов ждут отложенного редроя (spike_rest/button_rest). Без
|
||||
* него цикл по 30 тайлам крутился КАЖДЫЙ кадр впустую: замер в MAME
|
||||
* (2026-07-27) — служебная часть pop_process_trobs ~50 000 тактов при
|
||||
* кадре 430 080, то есть ~12 % кадра на пустой проход. */
|
||||
static uint8_t rest_pending;
|
||||
|
||||
/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
|
||||
* выбора следующего кадра пламени. */
|
||||
static unsigned long trob_seed;
|
||||
@@ -57,6 +63,7 @@ void pop_trob_reset(void)
|
||||
uint8_t i;
|
||||
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
|
||||
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
||||
rest_pending = 0;
|
||||
trobs_count = 0;
|
||||
}
|
||||
|
||||
@@ -101,6 +108,7 @@ void pop_trob_room_changed(void)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
||||
rest_pending = 0;
|
||||
}
|
||||
|
||||
/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
|
||||
@@ -304,19 +312,20 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
|
||||
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
|
||||
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
|
||||
{
|
||||
uint8_t row = 0, col = 0;
|
||||
if (rest_pending) {
|
||||
uint8_t row = 0, col = 0, left = 0;
|
||||
for (i = 0; i < POP_ROOMTILES; i++) {
|
||||
if (spike_rest[i]) {
|
||||
pop_spike_redraw(row, col);
|
||||
spike_rest[i]--;
|
||||
if (--spike_rest[i]) left++;
|
||||
}
|
||||
if (button_rest[i]) {
|
||||
pop_button_redraw(row, col);
|
||||
button_rest[i]--;
|
||||
if (--button_rest[i]) left++;
|
||||
}
|
||||
if (++col == 10) { col = 0; row++; }
|
||||
}
|
||||
rest_pending = left;
|
||||
}
|
||||
|
||||
if (trobs_count == 0) return;
|
||||
@@ -398,8 +407,8 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
: (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
|
||||
if (sig != trob_drawn[tp]) {
|
||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
||||
if (code == TILE_SPIKE) { pop_spike_redraw(trow, tcol); spike_rest[tp] = 1; }
|
||||
else { pop_button_redraw(trow, tcol); button_rest[tp] = 1; }
|
||||
if (code == TILE_SPIKE) { pop_spike_redraw(trow, tcol); spike_rest[tp] = 1; rest_pending++; }
|
||||
else { pop_button_redraw(trow, tcol); button_rest[tp] = 1; rest_pending++; }
|
||||
trob_drawn[tp] = sig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ __sfr __at (0xFE) io_border;
|
||||
/* Тест-старт для отладки пик (Фаза S): комната 6 = тестовая (пики в col3/col4
|
||||
* row2, шахта col3). Kid на полу row0 col1, идёт вправо → падает в шахту →
|
||||
* пики. TODO: вернуть на pop_level_start_room (комната 1) после отладки. */
|
||||
#define START_ROOM 6
|
||||
#define START_COL 4
|
||||
#define START_ROOM 8 /* ОТЛАДКА clip_char: спуск с кнопки (0,6) */
|
||||
#define START_COL 8
|
||||
#define START_ROW 0
|
||||
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
|
||||
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
|
||||
|
||||
Reference in New Issue
Block a user