L4-MIRROR шаги 3 и 5 + левый клип колонок в libbgi

libbgi: новая gfx_blit_cols_part_wx — обрезка СЛЕВА (skipw) в дополнение к
верхней (skip/rows) и правой (maxw).  По подсказке пользователя сделано
примитивом, а не обходным путём «нарисовать и вернуть фон поверх лишнего»:
для column-major левая обрезка стоит ровно столько же, сколько правая —
колонка это непрерывный кусок ОЗУ, меняется стартовая колонка источника и
экранная X.  Внутри это уже было (так клипается левый край экрана), наружу
не было выведено.  Полное тело блита колонками переехало туда,
gfx_blit_cols_part_w стала обёрткой (skipw=0).  size-check: OK, роста нет
(62 программы, -22..-33 Б на пользователей блита).

Шаг 5 — клип ТЕНИ (seg008:1699): на уровне зеркала она показывается только
СПРАВА от него, obj_clip_left = 137 + (mirror_column-4)*32.

Шаг 3 — ОТРАЖЕНИЕ (check_mirror, seg003:0798): пока Кид стоит на тайле
зеркала, каждый кадр рисуется его зеркальная копия с клипом
left = (curr_col<<5)+9 и top = y_clip[curr_row+1].  Отдельной функцией
pop_mirror_draw, а НЕ третьим слотом Char — это структура самого оригинала:
отражение идёт сокращённым путём load_frame_to_obj + add_objtable(4), без
клинка, брызг, fore-прохода и пропуска кадра; гейтить всё это в общем теле
значило бы добавить ветки в самый горячий путь.  Свой heal (pop_mirror_heal)
рядом с pop_char_heal, в skip-маске не участвует.

tests-host: заглушки pop_mirror_draw/pop_mirror_heal (pop_map теперь на них
ссылается), все 5 наборов прошли.

Цена: _CODE 24501 -> 24629, банк 3 10551, банк 4 8267 -> 9590.
НЕ ПРОВЕРЕНО В MAME.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 22:09:45 +03:00
parent 844fa6d767
commit 8f0362f3d4
8 changed files with 259 additions and 67 deletions
+117 -4
View File
@@ -25,6 +25,7 @@
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида; clip_char */
#include "pop_level.h" /* тип соперника уровня (набор спрайтов) */
#include "pop_tile.h" /* pop_heal_off — клип тени/отражения через фон */
#include "kid_data.h"
#include "pop_guard_pal.h" /* таблица палитр стражей (сгенерена упаковщиком) */
@@ -456,7 +457,7 @@ void pop_char_draw(uint8_t who) __banked
const kframe *fr;
const atlas_t *pages;
const uint8_t *img;
uint8_t npages, page, idx, dp, flip, hurt;
uint8_t npages, page, idx, dp, flip, hurt, lskip;
uint16_t w, h, vis_w;
int obj_x, obj_y, fp_x, fwd, top, bx, skip, rows, ct, cr;
@@ -533,6 +534,7 @@ void pop_char_draw(uint8_t who) __banked
if (flip) bx -= (int)w;
top = obj_y - (int)h + 1;
vis_w = w;
lskip = 0;
skip = 0;
/* clip_char (seg006:1231/1749): тайл над головой — стена/пол → всё
* выше линии полосы своего ряда НЕ рисуется, иначе персонаж
@@ -552,6 +554,18 @@ void pop_char_draw(uint8_t who) __banked
if (avail <= 0) vis_w = 0; /* весь спрайт за косяком */
else if (avail < (int)w) vis_w = (uint16_t)avail;
}
/* КЛИП ТЕНИ СЛЕВА (seg008:1699): на уровне зеркала она может
* показываться ТОЛЬКО СПРАВА от него —
* obj_clip_left = 137 + (mirror_column-4)*32. */
if (ch->charid == CHARID_1_SHADOW &&
pop_current_level == MIRROR_LEVEL && ch->room == MIRROR_ROOM) {
int cl = 137 + (MIRROR_COL - 4) * 32;
if (bx < cl) {
int d = cl - bx;
if (d >= (int)vis_w) vis_w = 0; /* вся тень левее линии */
else lskip = (uint8_t)d;
}
}
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507:
* obj_clip_top = 0, obj_clip_bottom = 192). Персонаж, вышедший за
* него (скелет, падающий в пропасть), обязан обрезаться: ниже поля
@@ -564,7 +578,10 @@ void pop_char_draw(uint8_t who) __banked
/* noclip-ядро ширину не ограничивает, поэтому при обрезке справа
* идём общим путём: это редкие кадры (подъём в двери уровня раз
* за уровень), цена подготовки там не видна. */
if (vis_w == w && pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
if (lskip) /* тень у зеркала: срез слева */
gfx_blit_cols_part_wx(bx, top + POP_YOFF, img, flip, skip, rows,
lskip, (uint8_t)(vis_w - lskip));
else if (vis_w == w && pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip,
(uint8_t)skip, (uint8_t)rows);
else
@@ -574,8 +591,8 @@ void pop_char_draw(uint8_t who) __banked
gfx_set_bank(GFX_BANK_NORMAL);
/* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над
* срезом (мусор/дыра в кладке на второй странице дабл-буфера). */
s->x[dp] = bx; s->y[dp] = top;
s->w[dp] = vis_w; s->h[dp] = (uint16_t)rows;
s->x[dp] = bx + lskip; s->y[dp] = top;
s->w[dp] = (uint16_t)(vis_w - lskip); s->h[dp] = (uint16_t)rows;
s->valid[dp] = (uint8_t)(rows != 0);
if (rows) cd_clip_add(s, bx, top, vis_w, (uint16_t)rows);
s->fpw = w; s->fph = h; /* габарит КАДРА (не обрезанный): */
@@ -614,6 +631,102 @@ void pop_char_fore(uint8_t who) __banked
s->fpx, s->fpy, s->fpw, s->fph);
}
/* ---- ОТРАЖЕНИЕ В ЗЕРКАЛЕ (check_mirror, seg003:0798) ---------------- *
*
* Пока Кид стоит на тайле зеркала, оригинал каждый кадр рисует его
* зеркальную копию. ОТДЕЛЬНОЙ функцией, а не третьим слотом pop_cd — и это
* не экономия, а структура САМОГО оригинала: отражение идёт сокращённым
* путём `load_frame_to_obj(); reset_obj_clip(); add_objtable(4)`, а НЕ через
* add_kid_to_objtable. У него нет ни клинка, ни брызг, ни fore-прохода, ни
* пропуска неизменившегося кадра — гейтить всё это в общем теле значило бы
* добавить ветки в самый горячий путь ради двух секунд за игру.
*
* Char к моменту вызова уже ОТРАЖЁН вызывающим (pop_check_mirror ->
* mirror_image), поэтому здесь никакого «зеркалим» нет — обычная математика
* кадра, та же, что в pop_char_draw (см. там load_frame_to_obj, seg008:1728;
* шесть строк намеренно продублированы, а не вынесены: трогать горячее тело
* ради холодного вызова дороже, чем держать их синхронными по комментарию).
*/
static int mi_x[2], mi_y[2];
static uint16_t mi_w[2], mi_h[2];
static uint8_t mi_valid[2];
void pop_mirror_heal(void) __banked
{
uint8_t p = gfx_get_draw_page() & 1;
if (!mi_valid[p]) return;
pop_heal_fast(mi_x[p], mi_y[p] + POP_YOFF, mi_w[p], mi_h[p]);
mi_valid[p] = 0;
}
void pop_mirror_draw(int clip_top) __banked
{
uint8_t dp = gfx_get_draw_page() & 1;
const kframe *fr = &kid_frame;
const uint8_t *img;
uint8_t page, idx, flip;
uint16_t w, h, vis_w;
int fwd, obj_x, obj_y, bx, top, skip = 0, rows, cl;
uint8_t lskip = 0;
if (fr->image == 255) return;
page = (uint8_t)(fr->image >> 3);
idx = (uint8_t)(fr->image & 7);
if (page >= kid_npages) return;
fwd = (int)Char.x + ((Char.direction < 0) ? -fr->dx : fr->dx);
obj_x = (fwd << 1) - 116;
obj_y = (int)Char.y + fr->dy;
if ((int8_t)(fr->flags ^ Char.direction) >= 0) obj_x++;
obj_x = scr_x(obj_x);
img = (const uint8_t *)atlas_image(&kidp[page], idx);
gfx_w0_map(kidp[page].page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
if (!w || !h) { gfx_w0_unmap(); return; }
flip = (uint8_t)(Char.direction >= 0);
bx = flip ? obj_x - (int)w : obj_x;
top = obj_y - (int)h + 1;
/* Клип СВЕРХУ — y_clip[curr_row+1] оригинала: отражение не должно
* вылезать из проёма зеркала вверх. Значение считает вызывающий
* (таблица y_clip живёт в pop_map). */
if (clip_top > top) { skip = clip_top - top; top = clip_top; }
rows = (int)h - skip;
if (top < 0) { skip += -top; rows += top; top = 0; }
if (top + rows > 192) rows = 192 - top;
vis_w = w;
if (rows <= 0) { gfx_w0_unmap(); return; }
/* Клип СЛЕВА — obj_clip_left = (curr_col<<5)+9: отражение видно только
* внутри проёма зеркала. Настоящей обрезкой колонок
* (gfx_blit_cols_part_wx), а не «нарисовать и вернуть фон»: для
* column-major левая обрезка стоит столько же, сколько правая. */
cl = ((int)Char.curr_col << 5) + 9;
if (bx < cl) {
int d = cl - bx;
if (d >= (int)vis_w) { gfx_w0_unmap(); return; } /* целиком за краем */
lskip = (uint8_t)d;
}
gfx_set_bank(GFX_BANK_SPRITE);
if (lskip)
gfx_blit_cols_part_wx(bx, top + POP_YOFF, img, flip, skip, rows,
lskip, (uint8_t)(vis_w - lskip));
else if (pop_onscreen_cols(bx, top + POP_YOFF, vis_w, (uint16_t)rows))
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip,
(uint8_t)skip, (uint8_t)rows);
else
gfx_blit_cols_part_w(bx, top + POP_YOFF, img, flip, skip, rows, 0);
gfx_set_bank(GFX_BANK_NORMAL);
gfx_w0_unmap();
mi_x[dp] = bx + lskip; mi_y[dp] = top;
mi_w[dp] = (uint16_t)(vis_w - lskip); mi_h[dp] = (uint16_t)rows;
mi_valid[dp] = 1;
}
/* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- *
* Оригинал рисует их в статус-полосе на y = 194 (ниже поля тайлов, внутри
* тех же 200 строк экрана PoP): HP Кида слева направо шагом 7, HP стража