From ba37bd1133492bc6bc6ce8dc360d11ab4c926989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=9F=D0=B5=D1=82=D1=80=D0=BE=D0=B2?= Date: Sat, 1 Aug 2026 23:43:01 +0300 Subject: [PATCH] =?UTF-8?q?BUG-DOOR-CLIP:=20=D0=BE=D0=B1=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=D0=B8=D0=BB=D1=83=D1=8D=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D1=8B=D0=BC=20=D0=BA=D0=BE=D1=81=D1=8F?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=20=D0=B4=D0=B2=D0=B5=D1=80=D0=B8=20=D1=83?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=BD=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Симптом (нашёл пользователь сразу после L1-EXIT): при подъёме по лестнице за дверью уровня силуэт Кида вылезал ПРАВЕЕ правого косяка проёма; по высоте обрезка была корректна. Причина — недопортированная половина clip_char (seg006:1231). Для кадров двери оригинал ставит ДВА клипа, у нас был только первый: obj_clip_top = leveldoor_ybottom + 1; // было obj_clip_right = leveldoor_right; // не было Отдельная ловушка: комментарий в SDLPoP говорит «frames 217..228», а КОД проверяет >= frame_224_exit_stairs_8, то есть 224..228 — портировано по коду. Fore-слоем это не лечится: створка и косяк уходят в оригинале целиком в backtable (draw_leveldoor, все add_backtable), рисуются ПОД персонажем и перекрыть его не могут. Единственный способ — срезать сам спрайт. libbgi: gfx_blit_cols_part_w(..., uint8_t maxw) — обрезка СПРАВА у колоночного блита. Для column-major это ровно уменьшение числа колонок, то есть внутри ядра механизм уже был (так же клипается край экрана, w = _bgi_maxx + 1 - x), наружу не выводился. Тело блита переехало туда, gfx_blit_cols_part стал тонкой обёрткой (maxw=0) — тем же приёмом, каким gfx_blit_cols уже обёрнут вокруг gfx_blit_cols_part. Работает и при flip: первые maxw нарисованных колонок всегда ложатся в левую часть футпринта. make size-check: роста нет. PoP: pop_leveldoor_right / pop_leveldoor_ybottom (порт одноимённых глобалов) пишет draw_leveldoor в pop_state — их читает clip_char из другого банка; pop_clip_char_right() отдаёт границу, kid_draw превращает её в maxw и уводит эти кадры с noclip-пути на общий. Прямоугольник heal (kid_lw) сужается тоже — стираем ровно нарисованное. Проверено в MAME: pop_leveldoor_right = 176, что есть ровно (draw_xh<<3)+48 для двери комнаты 9; pop_leveldoor_ybottom = 112 у закрытой створки и 69 у поднятой — сходится с формулой оригинала. Отрисовку подтвердил пользователь на живом подъёме. Заодно: ROOMNAV остаётся включённым осознанно — это наш чит, которого в оригинале не было, как и S/K/I; позже сведём в общий блок читов. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/TASKS.md | 18 +++---- applications/PoP/roomtest/bug_closed.md | 47 +++++++++++++++++ applications/PoP/roomtest/bug_list.md | 59 +++------------------ applications/PoP/roomtest/pop_bg.c | 8 +++ applications/PoP/roomtest/pop_kid.c | 28 +++++++--- applications/PoP/roomtest/pop_map.c | 23 ++++++++ applications/PoP/roomtest/pop_map.h | 4 ++ applications/PoP/roomtest/pop_state.c | 6 +++ applications/PoP/roomtest/pop_state.h | 11 ++++ libbgi/common/gfx_blit_cols_part.c | 47 ++--------------- libbgi/common/gfx_blit_cols_part_w.c | 70 +++++++++++++++++++++++++ libbgi/include/gfx.h | 14 +++++ 12 files changed, 224 insertions(+), 111 deletions(-) create mode 100644 libbgi/common/gfx_blit_cols_part_w.c diff --git a/applications/PoP/roomtest/TASKS.md b/applications/PoP/roomtest/TASKS.md index ed1c79f..a6aa8c2 100644 --- a/applications/PoP/roomtest/TASKS.md +++ b/applications/PoP/roomtest/TASKS.md @@ -6,10 +6,8 @@ а то, **что берём в работу сейчас и в каком порядке**. Каждая запись: что сделать, почему именно сейчас, чем подтверждать результат. -**Открытые баги** ([`bug_list.md`](bug_list.md)): -[BUG-DOOR-CLIP](bug_list.md#bug-door-clip) (Кид не обрезается правым косяком -двери уровня) + незакрытые оптимизации [T-1](bug_list.md#t-1), -[T-2](bug_list.md#t-2) и незаконченная +**Открытых багов нет** ([`bug_list.md`](bug_list.md)): остались незакрытые +оптимизации [T-1](bug_list.md#t-1), [T-2](bug_list.md#t-2) и незаконченная [таблица обхода 24 комнат](bug_list.md#обход-всех-24-комнат-уровня-1). Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга; @@ -373,9 +371,9 @@ захлопывает решётку за спиной. Проверено: старт даёт room 1, col 0, падение на row 1 — как по данным уровня. -⏳ **Остаток:** `#define ROOMNAV` пока ВКЛЮЧЁН — он нужен, чтобы попадать в -комнату 9 для работы над [BUG-DOOR-CLIP](bug_list.md#bug-door-clip). -Выключить перед L1-PASS. +`#define ROOMNAV` оставлен ВКЛЮЧЁННЫМ осознанно: это наш чит, которого в +оригинале не было, — как и `S` (выдать меч), `K`, `I`. Все они со временем +съедутся в общий блок читов, разрешаемый в настройках (решение 2026-08-01). ### L1-EXIT. Выход с уровня (дверь уровня) — **СДЕЛАНО 2026-08-01** Портированы: ветка двери уровня из `up_pressed` + `go_up_leveldoor` @@ -397,8 +395,10 @@ Вывод на будущее: геометрию персонажа в этом порте меняет `pop_map` (пишет в `Kid`), а не диспетчер. -⏳ Косметика: [BUG-DOOR-CLIP](bug_list.md#bug-door-clip) — нет обрезки силуэта -правым косяком проёма. +✅ Косметика тоже закрыта: BUG-DOOR-CLIP (обрезка силуэта правым косяком +проёма) — недоставало второй половины `clip_char` (`obj_clip_right`) и +обрезки СПРАВА у колоночного блита. В libbgi добавлен +`gfx_blit_cols_part_w(..., maxw)`; разбор — в [`bug_closed.md`](bug_closed.md). ### L1-TRIAGE. Ревизия багов — **ЧАСТЬ 1 СДЕЛАНА 2026-08-01** diff --git a/applications/PoP/roomtest/bug_closed.md b/applications/PoP/roomtest/bug_closed.md index 9b531f1..90e3a33 100644 --- a/applications/PoP/roomtest/bug_closed.md +++ b/applications/PoP/roomtest/bug_closed.md @@ -150,6 +150,53 @@ blueline, грани пик/loose соседа, кадр loose) **всегда** --- +## BUG-DOOR-CLIP. Подъём по лестнице двери уровня: нет обрезки по правому косяку — **ИСПРАВЛЕН 2026-08-01** + +**Был симптом (найден пользователем сразу после L1-EXIT):** при подъёме по +лестнице за дверью уровня (кадры 224..228) силуэт Кида вылезал ПРАВЕЕ правого +косяка проёма. По высоте обрезка была корректна. + +**Причина — недопортированная половина `clip_char`** (seg006:1231). Для +кадров двери оригинал ставит ДВА клипа, у нас был только первый: + +```c +if (frame >= frame_224_exit_stairs_8 && frame < 229) { + obj_clip_top = leveldoor_ybottom + 1; + obj_clip_right = leveldoor_right; +} +``` + +Отдельная ловушка: комментарий в SDLPoP говорит «frames 217..228», а КОД +проверяет `>= frame_224_exit_stairs_8`, то есть **224..228**. Портировано по +коду. + +**Почему именно обрезка, а не fore-слой:** створка и косяк уходят в оригинале +ЦЕЛИКОМ в backtable (`draw_leveldoor`, все `add_backtable`), то есть рисуются +ПОД персонажем и перекрыть его не могут. Единственный способ спрятать +поднимающегося — срезать сам спрайт. + +**Фикс:** +- libbgi: `gfx_blit_cols_part_w(..., uint8_t maxw)` — обрезка СПРАВА у + колоночного блита. Для column-major это ровно уменьшение числа колонок, + то есть внутри ядра механизм уже был (так же клипается край экрана: + `w = _bgi_maxx + 1 - x`), наружу не было выведено. Тело блита переехало + туда, `gfx_blit_cols_part` стал тонкой обёрткой (maxw=0) — тем же приёмом, + каким `gfx_blit_cols` уже обёрнут вокруг `gfx_blit_cols_part`. Работает и + при flip: первые `maxw` нарисованных колонок всегда ложатся в ЛЕВУЮ часть + футпринта. +- PoP: `pop_leveldoor_right` / `pop_leveldoor_ybottom` (порт одноимённых + глобалов) пишет `draw_leveldoor` в `pop_state` — их читает `clip_char` из + другого банка; `pop_clip_char_right()` отдаёт границу, `kid_draw` + превращает её в `maxw` и уводит эти кадры с noclip-пути на общий. + `kid_lw` (прямоугольник heal) тоже сужается — стираем ровно нарисованное. + +**Проверено в MAME:** `pop_leveldoor_right = 176` — ровно +`(draw_xh<<3) + 48` для двери комнаты 9; `pop_leveldoor_ybottom` = 112 у +закрытой створки и 69 у поднятой (сходится с формулой оригинала). +Отрисовка подтверждена пользователем на живом подъёме. + +--- + ## BUG-SEAM-PINGPONG (#4). Пинг-понг drawn_room у шва с закрытыми воротами — **РЕШЁН 2026-07-22** Настоящий корень найден потиковой трассой ЖИВОГО SDLPoP 1.23 diff --git a/applications/PoP/roomtest/bug_list.md b/applications/PoP/roomtest/bug_list.md index b8fba1e..d3708f0 100644 --- a/applications/PoP/roomtest/bug_list.md +++ b/applications/PoP/roomtest/bug_list.md @@ -14,63 +14,17 @@ odd-pixel арифметика `char_x`, подстановка тайла на починена кодом ещё в июле, список просто отстал — чем именно закрыта каждая, расписано в [`bug_closed.md`](bug_closed.md)). +**Открытых багов нет.** Осталось только незакрытое по оптимизации и +незаконченная ревизия комнат: + | ID | что | тип | |----|-----|-----| -| [BUG-DOOR-CLIP](#bug-door-clip) | Кид не обрезается правым косяком двери уровня | Medium | | [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | | [T-2](#t-2) | Кид перерисовывается в покое | оптимизация | | [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия | --- - -## BUG-DOOR-CLIP. Подъём по лестнице двери уровня: нет обрезки по правому косяку — **Medium** - -**Симптом (2026-08-01, найден пользователем сразу после L1-EXIT):** при -подъёме по лестнице за дверью уровня (seq_70, кадры 224..228) силуэт Кида -вылезает ПРАВЕЕ правого косяка проёма — должен уходить за него. По ВЫСОТЕ -обрезка корректна. - -**Причина — недопортированная половина `clip_char`** (seg006:1231). Для -кадров двери уровня оригинал ставит ДВА клипа, а мы портировали только -первый: - -```c -if (frame >= frame_224_exit_stairs_8 && frame < 229) { - obj_clip_top = leveldoor_ybottom + 1; // ЕСТЬ (pop_clip_char_top) - obj_clip_right = leveldoor_right; // НЕТ -} -``` - -`leveldoor_right = (draw_xh << 3) + 48` считается в `draw_leveldoor` -(seg008:1D29) от колонки ЛЕВОЙ половины двери; для palace-уровней (+8, -`tbl_level_type`) — на уровне 1 не применяется. - -Обратить внимание: комментарий в SDLPoP говорит «frames 217..228», а КОД -проверяет `>= frame_224_exit_stairs_8`, то есть **224..228**. Портировать по -коду. - -**Чего не хватает в движке:** обрезки СПРАВА у колоночного блита. -`gfx_blit_cols_part` (libbgi) умеет только обрезку сверху (`skip`/`rows`) плюс -клип по краю ЭКРАНА (`w = _bgi_maxx + 1 - x`). Для column-major спрайта -обрезка справа — это ровно уменьшение `w`, то есть внутри ядра она уже есть, -не хватает способа задать её снаружи. - -**План (не начат):** -1. libbgi: тело `gfx_blit_cols_part` вынести в вариант с ограничением - ширины (напр. `gfx_blit_cols_part_w(..., uint8_t maxw)`), а нынешнюю - функцию оставить тонкой обёрткой — тем же приёмом, каким `gfx_blit_cols` - уже обёрнут вокруг `gfx_blit_cols_part`. Флип тут не мешает: в кадрах - двери направление всегда dir_FF_left, то есть flip=0. -2. PoP: `pop_clip_char_top` дополнить правым клипом (или завести - `pop_clip_char_right`), считать `leveldoor_right` от колонки двери. -3. `make size-check` (трогаем libbgi) + проверка стоп-кадром в MAME: - поймать кадр 224..228 клавишей `1` и сверить кромку. - -**Пока не починено, дефект косметический** — вход на следующий уровень -работает. - ---- # Оптимизация отрисовки (записано 2026-07-29) @@ -181,6 +135,7 @@ Kid ставится на первый пол, `pop_trob_reset()` возвращ - Читы (`pop_cheat.h`): **K** — убить стража, **I** — бессмертие (toggle), **S** — выдать меч. - Респавн после смерти — по **↑** (или авто через `RESPAWN_DELAY`); ставит - Kid в `START_ROOM`. -- `START_ROOM=1 / COL 3 / ROW 0` — жёстко зашитый тестовый старт; перевести - на `pop_level_start_*` (задача L1-START в [`TASKS.md`](TASKS.md)). + Kid в стартовую позицию УРОВНЯ (`pop_start_level`, порт do_startpos). +- **ROOMNAV (`=`/`-`) — тоже наш чит**, которого в оригинале не было, как и + `S`. Все они со временем съедутся в общий блок читов, разрешаемый в + настройках; пока просто включены (`pop_cheats = 1` в `roomtest.c`). diff --git a/applications/PoP/roomtest/pop_bg.c b/applications/PoP/roomtest/pop_bg.c index 55ed518..e313d2a 100644 --- a/applications/PoP/roomtest/pop_bg.c +++ b/applications/PoP/roomtest/pop_bg.c @@ -585,6 +585,11 @@ static void draw_leveldoor(uint8_t modif, int xh, int dmy) int ybottom = dmy - 13; int ylo = ybottom - (int)(modif & 3) - 48; /* leveldoor_ybottom */ int y = ybottom - (int)modif; + /* leveldoor_right (seg008:1D29): правая кромка проёма — от xh ТЕКУЩЕГО + * тайла, то есть ПРАВОЙ половины двери (сюда мы попадаем из + * draw_tile_anim_right, разбирая левого соседа). +8 у palace-уровней + * (tbl_level_type) — на уровне 1 не применяется. */ + pop_leveldoor_right = xh * 8 + 48; env_b(99, x, ybottom); if (modif) env_b(144, x, ybottom - 4); /* лестница (комната ≠ стартовой) */ @@ -593,6 +598,9 @@ static void draw_leveldoor(uint8_t modif, int xh, int dmy) if (y > ylo) ylo += 4; else break; } + /* ИМЕННО после цикла: в оригинале clip_char читает уже доведённое + * значение (низ поднятой створки), а не стартовое. */ + pop_leveldoor_ybottom = ylo; env_b(34, x, dmy - 64); } diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index 5121ca9..8a0ece6 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -679,7 +679,8 @@ void kid_draw(void) uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */ int bx = obj_x + KID_XOFF; int skip = 0; /* строк спрайта срезано сверху */ - int ct; + int ct, cr; + uint16_t vis_w = w; /* видимая ширина (клип справа) */ if (flip) bx -= (int)w; /* seg008: xpos -= image->w */ top = obj_y - (int)h + 1 + KID_YOFF; @@ -692,14 +693,27 @@ void kid_draw(void) skip = ct - top; if (skip >= (int)h) skip = (int)h; /* обрезан целиком */ } + /* clip_char, вторая половина: правый косяк двери уровня. Дверь + * рисуется ПОД персонажем (вся в backtable), поэтому спрятать его + * можно только срезав спрайт — см. pop_clip_char_right. */ + cr = pop_clip_char_right(); + if (cr) { + int avail = cr - bx; + if (avail <= 0) vis_w = 0; /* весь спрайт за косяком */ + else if (avail < (int)w) vis_w = (uint16_t)avail; + } gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */ - if (skip < (int)h) { /* +YOFF: центрирование */ - if (pop_onscreen_cols(bx, top + skip + POP_YOFF, w, - (uint16_t)((int)h - skip))) + if (skip < (int)h && vis_w) { /* +YOFF: центрирование */ + /* noclip-ядро ширину не ограничивает, поэтому при обрезке справа + * идём общим путём: это редкие кадры (подъём в двери уровня раз + * за уровень), цена подготовки тут не видна. */ + if (vis_w == w && pop_onscreen_cols(bx, top + skip + POP_YOFF, w, + (uint16_t)((int)h - skip))) gfx_blit_cols_part_noclip(bx, top + skip + POP_YOFF, img, flip, (uint8_t)skip, 0); else - gfx_blit_cols_part(bx, top + skip + POP_YOFF, img, flip, skip, 0); + gfx_blit_cols_part_w(bx, top + skip + POP_YOFF, img, flip, + skip, 0, (uint8_t)(vis_w == w ? 0 : vis_w)); } gfx_set_bank(GFX_BANK_NORMAL); /* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) — @@ -708,8 +722,8 @@ void kid_draw(void) /* 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_lw[dp] = vis_w; kid_lh[dp] = (uint16_t)((int)h - skip); + kid_lvalid[dp] = (skip < (int)h && vis_w != 0); kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */ } /* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 75ca0e6..c46430a 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -1384,6 +1384,21 @@ void pop_loose_tick(void) __banked * check_spike_below колонки берутся get_tile_div_mod БЕЗ −7. * * Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */ +/* Вторая половина ветки двери уровня из clip_char (seg006:1231): + * obj_clip_right = leveldoor_right — ЭКРАННЫЙ X, правее которого спрайт не + * рисуется. 0 = обрезки нет. + * + * Почему обрезка, а не fore-слой: створка и косяк уходят в оригинале + * ЦЕЛИКОМ в backtable (draw_leveldoor, все add_backtable), то есть рисуются + * ПОД персонажем и перекрыть его не могут. Единственный способ спрятать + * поднимающегося по лестнице — срезать сам спрайт. */ +int pop_clip_char_right(void) __banked +{ + if (Kid.frame >= 224 && Kid.frame < 229) + return pop_leveldoor_right; + return 0; +} + int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked { static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 }; @@ -1393,6 +1408,14 @@ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked int8_t cL, cR, trow, clip_row; uint8_t t; + /* Кадры подъёма по лестнице двери уровня (clip_char, seg006:1231): + * персонаж режется по низу поднятой створки. ВНИМАНИЕ: комментарий в + * SDLPoP говорит «frames 217..228», а КОД проверяет + * `>= frame_224_exit_stairs_8`, то есть 224..228 — портируем по коду. + * Правую кромку отдаёт pop_clip_char_right (парная половина ветки). */ + if (Kid.frame >= 224 && Kid.frame < 229) + return pop_leveldoor_ybottom + 1; + if (Kid.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */ xr = xl + wh; /* char_x_right */ top_y = obj_y - (int)h + 1; /* char_top_y */ diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index 3d2eeba..0d705ff 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -122,6 +122,10 @@ void pop_phys_tick(void) __banked; * Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked; +/* Парная половина clip_char: ЭКРАННЫЙ X, правее которого спрайт не рисуется + * (правый косяк двери уровня в кадрах подъёма 224..228). 0 = обрезки нет. */ +int pop_clip_char_right(void) __banked; + /* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после * pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */ #define EDGE_CLOSER 0 /* край уступа над ямой (дистанция = до кромки) */ diff --git a/applications/PoP/roomtest/pop_state.c b/applications/PoP/roomtest/pop_state.c index daf98b6..fae7ec3 100644 --- a/applications/PoP/roomtest/pop_state.c +++ b/applications/PoP/roomtest/pop_state.c @@ -17,3 +17,9 @@ uint8_t pop_loose_landed; /* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара, * но HP не теряет — иначе проверять боёвку приходится с трёх попаданий. */ uint8_t pop_immortal; + +/* Габариты последней нарисованной двери уровня (порт leveldoor_right / + * leveldoor_ybottom): пишет draw_leveldoor из банка pop_bg, читает + * clip_char из pop_map. См. pop_state.h. */ +int pop_leveldoor_right; +int pop_leveldoor_ybottom; diff --git a/applications/PoP/roomtest/pop_state.h b/applications/PoP/roomtest/pop_state.h index 85c74c3..239cb46 100644 --- a/applications/PoP/roomtest/pop_state.h +++ b/applications/PoP/roomtest/pop_state.h @@ -18,4 +18,15 @@ extern uint8_t pop_loose_landed; * (боёвка в банке, пики в pop_map), переключает главный цикл. */ extern uint8_t pop_immortal; +/* Габариты ПОСЛЕДНЕЙ нарисованной двери уровня — порт глобалов + * leveldoor_right / leveldoor_ybottom (seg008 draw_leveldoor). Их ставит + * отрисовка (банк pop_bg), а читает clip_char (pop_map, W1/W2): кадры + * подъёма по лестнице (224..228) обрезаются по правому косяку проёма и по + * низу поднятой створки. Дверь рисуется ПОД персонажем, поэтому перекрыть + * его не может — оригинал именно режет спрайт. + * right — экранный X правой кромки; ybottom — room-space Y (без POP_YOFF). + * 0 = двери в отрисованной комнате нет. */ +extern int pop_leveldoor_right; +extern int pop_leveldoor_ybottom; + #endif diff --git a/libbgi/common/gfx_blit_cols_part.c b/libbgi/common/gfx_blit_cols_part.c index a12d181..15f0826 100644 --- a/libbgi/common/gfx_blit_cols_part.c +++ b/libbgi/common/gfx_blit_cols_part.c @@ -9,9 +9,9 @@ * сверху для column-major бесплатна: колонка = непрерывный кусок ОЗУ, старт * просто сдвигается на skip байт, длина accel-блока уменьшается. * - * Здесь же живёт полное тело блита колонками — gfx_blit_cols() тонкая - * обёртка (skip=0, rows=0), чтобы не дублировать клип-логику. - * Подробности зеркала/клипа — см. шапку gfx_blit_cols.c. + * Тело (вместе с клип-логикой) — в gfx_blit_cols_part_w.c: этот вызов = + * «без ограничения ширины». Подробности зеркала/клипа — см. шапку + * gfx_blit_cols.c. */ #include #include "../_bgi.h" @@ -19,44 +19,5 @@ void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip, int skip, int rows) { - const uint8_t *p = (const uint8_t *)img; - int full_w = p[0] | (p[1] << 8); - int full_h = p[2] | (p[3] << 8); - const uint8_t *px = p + 4; - int w = full_w, h; - int sstride = flip ? -full_h : full_h; /* шаг между колонками в ОЗУ */ - int sy; /* пропуск строк сверху колонки */ - int cx = 0; /* пропуск колонок слева экрана */ - const uint8_t *src; - uint8_t *dst; - - if (full_w <= 0 || full_h <= 0) - return; - if (skip < 0) skip = 0; - if (skip >= full_h) - return; /* обрезано целиком */ - sy = skip; - h = full_h - skip; - if (rows > 0 && rows < h) - h = rows; - - /* Клип по экрану — только если хоть один край вылезает. */ - if (x < 0 || y < 0 || x + w > _bgi_maxx + 1 || y + h > _bgi_maxy + 1) { - if (x < 0) { cx = -x; w += x; x = 0; } - if (y < 0) { sy -= y; h += y; y = 0; } - if (x + w > _bgi_maxx + 1) w = _bgi_maxx + 1 - x; - if (y + h > _bgi_maxy + 1) h = _bgi_maxy + 1 - y; - if (w <= 0 || h <= 0) return; - } - - /* Первая ВИДИМАЯ колонка (с учётом флипа) + первая видимая строка. */ - src = flip ? (px + (uint16_t)(full_w - 1) * (uint16_t)full_h) : px; - if (cx) - src += cx * sstride; - src += sy; - - _bgi_begin(); - dst = (uint8_t *)(_gfx_addr_base + (uint16_t)x); - _bgi_blit_cols_raw(src, dst, (uint8_t)w, (uint8_t)h, sstride, (uint8_t)y); - _bgi_end(); + gfx_blit_cols_part_w(x, y, img, flip, skip, rows, 0); } diff --git a/libbgi/common/gfx_blit_cols_part_w.c b/libbgi/common/gfx_blit_cols_part_w.c new file mode 100644 index 0000000..901fc9a --- /dev/null +++ b/libbgi/common/gfx_blit_cols_part_w.c @@ -0,0 +1,70 @@ +/* + * gfx_blit_cols_part_w — блит спрайта column-major с обрезкой СВЕРХУ + * (skip/rows) И ОГРАНИЧЕНИЕМ ШИРИНЫ (maxw): рисуются только первые maxw + * колонок футпринта, считая от x. maxw = 0 — «вся ширина кадра». + * + * Здесь живёт ПОЛНОЕ тело блита колонками; gfx_blit_cols_part (maxw=0) и + * gfx_blit_cols (ещё и skip=rows=0) — тонкие обёртки, чтобы клип-логика не + * дублировалась. Подробности зеркала/клипа — см. шапку gfx_blit_cols.c. + * + * Зачем ограничение ширины. Обрезка СПРАВА для column-major — это ровно + * уменьшение числа колонок, то есть внутри уже есть (так же клипается край + * экрана: `w = _bgi_maxx + 1 - x`), наружу просто не было выведено. + * Понадобилось для порта clip_char в PoP: персонаж, поднимающийся по + * лестнице за дверью уровня, обязан уходить за правый косяк проёма + * (obj_clip_right), а дверь рисуется ПОД ним и перекрыть его не может. + * + * Важно: ограничение работает и при flip. При зеркале источник читается с + * последней колонки к первой (sstride < 0), но первые w нарисованных + * колонок в любом случае ложатся в ЛЕВУЮ часть футпринта [x, x+w) — то + * есть уменьшение w всегда срезает справа, как и требуется. + */ +#include +#include "../_bgi.h" + +void gfx_blit_cols_part_w(int x, int y, const void *img, uint8_t flip, + int skip, int rows, uint8_t maxw) +{ + const uint8_t *p = (const uint8_t *)img; + int full_w = p[0] | (p[1] << 8); + int full_h = p[2] | (p[3] << 8); + const uint8_t *px = p + 4; + int w = full_w, h; + int sstride = flip ? -full_h : full_h; /* шаг между колонками в ОЗУ */ + int sy; /* пропуск строк сверху колонки */ + int cx = 0; /* пропуск колонок слева экрана */ + const uint8_t *src; + uint8_t *dst; + + if (full_w <= 0 || full_h <= 0) + return; + if (maxw && (int)maxw < w) + w = (int)maxw; /* обрезка справа (см. шапку) */ + if (skip < 0) skip = 0; + if (skip >= full_h) + return; /* обрезано целиком */ + sy = skip; + h = full_h - skip; + if (rows > 0 && rows < h) + h = rows; + + /* Клип по экрану — только если хоть один край вылезает. */ + if (x < 0 || y < 0 || x + w > _bgi_maxx + 1 || y + h > _bgi_maxy + 1) { + if (x < 0) { cx = -x; w += x; x = 0; } + if (y < 0) { sy -= y; h += y; y = 0; } + if (x + w > _bgi_maxx + 1) w = _bgi_maxx + 1 - x; + if (y + h > _bgi_maxy + 1) h = _bgi_maxy + 1 - y; + if (w <= 0 || h <= 0) return; + } + + /* Первая ВИДИМАЯ колонка (с учётом флипа) + первая видимая строка. */ + src = flip ? (px + (uint16_t)(full_w - 1) * (uint16_t)full_h) : px; + if (cx) + src += cx * sstride; + src += sy; + + _bgi_begin(); + dst = (uint8_t *)(_gfx_addr_base + (uint16_t)x); + _bgi_blit_cols_raw(src, dst, (uint8_t)w, (uint8_t)h, sstride, (uint8_t)y); + _bgi_end(); +} diff --git a/libbgi/include/gfx.h b/libbgi/include/gfx.h index 8467707..d2a0e0e 100644 --- a/libbgi/include/gfx.h +++ b/libbgi/include/gfx.h @@ -128,6 +128,20 @@ void gfx_blit_cols(int x, int y, const void *img, uint8_t flip); void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip, int skip, int rows); +/* То же + ОГРАНИЧЕНИЕ ШИРИНЫ: рисуются только первые maxw колонок футпринта, + * считая от x (maxw = 0 — вся ширина кадра). То есть обрезка СПРАВА по + * произвольной линии, а не только по краю экрана. + * + * Для column-major это столь же дёшево, как обрезка сверху: колонок просто + * меньше. Работает и при flip — зеркало происходит внутри футпринта, а + * первые maxw нарисованных колонок всегда ложатся в его ЛЕВУЮ часть. + * + * Мотивация — clip_char в PoP (obj_clip_right): персонаж, поднимающийся по + * лестнице за дверью уровня, обязан уходить за правый косяк проёма, а сама + * дверь рисуется ПОД ним и перекрыть его не может. */ +void gfx_blit_cols_part_w(int x, int y, const void *img, uint8_t flip, + int skip, int rows, uint8_t maxw); + /* То же БЕЗ клипа по экрану — пара к gfx_blit_cols_part так же, как * gfx_blit_part_noclip к gfx_blit_part. Вызывающий гарантирует: спрайт * целиком на экране, ширина/высота кадра и y <= 255, skip < высоты.