BUG-DOOR-CLIP: обрезка силуэта правым косяком двери уровня

Симптом (нашёл пользователь сразу после 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 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-01 23:43:01 +03:00
parent e86f254b87
commit ba37bd1133
12 changed files with 224 additions and 111 deletions
+9 -9
View File
@@ -6,10 +6,8 @@
а то, **что берём в работу сейчас и в каком порядке**. Каждая запись: что а то, **что берём в работу сейчас и в каком порядке**. Каждая запись: что
сделать, почему именно сейчас, чем подтверждать результат. сделать, почему именно сейчас, чем подтверждать результат.
**Открытые баги** ([`bug_list.md`](bug_list.md)): **Открытых багов нет** ([`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) и незаконченная
двери уровня) + незакрытые оптимизации [T-1](bug_list.md#t-1),
[T-2](bug_list.md#t-2) и незаконченная
[таблица обхода 24 комнат](bug_list.md#обход-всех-24-комнат-уровня-1). [таблица обхода 24 комнат](bug_list.md#обход-всех-24-комнат-уровня-1).
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга; Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
@@ -373,9 +371,9 @@
захлопывает решётку за спиной. Проверено: старт даёт room 1, col 0, падение захлопывает решётку за спиной. Проверено: старт даёт room 1, col 0, падение
на row 1 — как по данным уровня. на row 1 — как по данным уровня.
**Остаток:** `#define ROOMNAV` пока ВКЛЮЧЁН — он нужен, чтобы попадать в `#define ROOMNAV` оставлен ВКЛЮЧЁННЫМ осознанно: это наш чит, которого в
комнату 9 для работы над [BUG-DOOR-CLIP](bug_list.md#bug-door-clip). оригинале не было, — как и `S` (выдать меч), `K`, `I`. Все они со временем
Выключить перед L1-PASS. съедутся в общий блок читов, разрешаемый в настройках (решение 2026-08-01).
### L1-EXIT. Выход с уровня (дверь уровня) — **СДЕЛАНО 2026-08-01** ### L1-EXIT. Выход с уровня (дверь уровня) — **СДЕЛАНО 2026-08-01**
Портированы: ветка двери уровня из `up_pressed` + `go_up_leveldoor` Портированы: ветка двери уровня из `up_pressed` + `go_up_leveldoor`
@@ -397,8 +395,10 @@
Вывод на будущее: геометрию персонажа в этом порте меняет `pop_map` (пишет в Вывод на будущее: геометрию персонажа в этом порте меняет `pop_map` (пишет в
`Kid`), а не диспетчер. `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** ### L1-TRIAGE. Ревизия багов — **ЧАСТЬ 1 СДЕЛАНА 2026-08-01**
+47
View File
@@ -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** ## BUG-SEAM-PINGPONG (#4). Пинг-понг drawn_room у шва с закрытыми воротами — **РЕШЁН 2026-07-22**
Настоящий корень найден потиковой трассой ЖИВОГО SDLPoP 1.23 Настоящий корень найден потиковой трассой ЖИВОГО SDLPoP 1.23
+7 -52
View File
@@ -14,63 +14,17 @@ odd-pixel арифметика `char_x`, подстановка тайла на
починена кодом ещё в июле, список просто отстал — чем именно закрыта каждая, починена кодом ещё в июле, список просто отстал — чем именно закрыта каждая,
расписано в [`bug_closed.md`](bug_closed.md)). расписано в [`bug_closed.md`](bug_closed.md)).
**Открытых багов нет.** Осталось только незакрытое по оптимизации и
незаконченная ревизия комнат:
| ID | что | тип | | ID | что | тип |
|----|-----|-----| |----|-----|-----|
| [BUG-DOOR-CLIP](#bug-door-clip) | Кид не обрезается правым косяком двери уровня | Medium |
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | | [T-1](#t-1) | пики перерисовываются безусловно | оптимизация |
| [T-2](#t-2) | Кид перерисовывается в покое | оптимизация | | [T-2](#t-2) | Кид перерисовывается в покое | оптимизация |
| [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия | | [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия |
--- ---
<a id="bug-door-clip"></a>
## 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) # Оптимизация отрисовки (записано 2026-07-29)
@@ -181,6 +135,7 @@ Kid ставится на первый пол, `pop_trob_reset()` возвращ
- Читы (`pop_cheat.h`): **K** — убить стража, **I** — бессмертие (toggle), - Читы (`pop_cheat.h`): **K** — убить стража, **I** — бессмертие (toggle),
**S** — выдать меч. **S** — выдать меч.
- Респавн после смерти — по **↑** (или авто через `RESPAWN_DELAY`); ставит - Респавн после смерти — по **↑** (или авто через `RESPAWN_DELAY`); ставит
Kid в `START_ROOM`. Kid в стартовую позицию УРОВНЯ (`pop_start_level`, порт do_startpos).
- `START_ROOM=1 / COL 3 / ROW 0`жёстко зашитый тестовый старт; перевести - **ROOMNAV (`=`/`-`)тоже наш чит**, которого в оригинале не было, как и
на `pop_level_start_*` (задача L1-START в [`TASKS.md`](TASKS.md)). `S`. Все они со временем съедутся в общий блок читов, разрешаемый в
настройках; пока просто включены (`pop_cheats = 1` в `roomtest.c`).
+8
View File
@@ -585,6 +585,11 @@ static void draw_leveldoor(uint8_t modif, int xh, int dmy)
int ybottom = dmy - 13; int ybottom = dmy - 13;
int ylo = ybottom - (int)(modif & 3) - 48; /* leveldoor_ybottom */ int ylo = ybottom - (int)(modif & 3) - 48; /* leveldoor_ybottom */
int y = ybottom - (int)modif; 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); env_b(99, x, ybottom);
if (modif) if (modif)
env_b(144, x, ybottom - 4); /* лестница (комната ≠ стартовой) */ 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; if (y > ylo) ylo += 4;
else break; else break;
} }
/* ИМЕННО после цикла: в оригинале clip_char читает уже доведённое
* значение (низ поднятой створки), а не стартовое. */
pop_leveldoor_ybottom = ylo;
env_b(34, x, dmy - 64); env_b(34, x, dmy - 64);
} }
+20 -6
View File
@@ -679,7 +679,8 @@ void kid_draw(void)
uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */ uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */
int bx = obj_x + KID_XOFF; int bx = obj_x + KID_XOFF;
int skip = 0; /* строк спрайта срезано сверху */ int skip = 0; /* строк спрайта срезано сверху */
int ct; int ct, cr;
uint16_t vis_w = w; /* видимая ширина (клип справа) */
if (flip) if (flip)
bx -= (int)w; /* seg008: xpos -= image->w */ bx -= (int)w; /* seg008: xpos -= image->w */
top = obj_y - (int)h + 1 + KID_YOFF; top = obj_y - (int)h + 1 + KID_YOFF;
@@ -692,14 +693,27 @@ void kid_draw(void)
skip = ct - top; skip = ct - top;
if (skip >= (int)h) skip = (int)h; /* обрезан целиком */ 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); /* видео-ОЗУ, без теневой копии */ gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
if (skip < (int)h) { /* +YOFF: центрирование */ if (skip < (int)h && vis_w) { /* +YOFF: центрирование */
if (pop_onscreen_cols(bx, top + skip + POP_YOFF, w, /* noclip-ядро ширину не ограничивает, поэтому при обрезке справа
* идём общим путём: это редкие кадры (подъём в двери уровня раз
* за уровень), цена подготовки тут не видна. */
if (vis_w == w && pop_onscreen_cols(bx, top + skip + POP_YOFF, w,
(uint16_t)((int)h - skip))) (uint16_t)((int)h - skip)))
gfx_blit_cols_part_noclip(bx, top + skip + POP_YOFF, img, flip, gfx_blit_cols_part_noclip(bx, top + skip + POP_YOFF, img, flip,
(uint8_t)skip, 0); (uint8_t)skip, 0);
else 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); gfx_set_bank(GFX_BANK_NORMAL);
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) — /* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
@@ -708,8 +722,8 @@ void kid_draw(void)
/* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над /* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над
* срезом (мусор/дыра в кладке на второй странице дабл-буфера). */ * срезом (мусор/дыра в кладке на второй странице дабл-буфера). */
kid_lx[dp] = bx; kid_ly[dp] = top + skip; kid_lx[dp] = bx; kid_ly[dp] = top + skip;
kid_lw[dp] = w; kid_lh[dp] = (uint16_t)((int)h - skip); kid_lw[dp] = vis_w; kid_lh[dp] = (uint16_t)((int)h - skip);
kid_lvalid[dp] = (skip < (int)h); kid_lvalid[dp] = (skip < (int)h && vis_w != 0);
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */ kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
} }
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх /* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
+23
View File
@@ -1384,6 +1384,21 @@ void pop_loose_tick(void) __banked
* check_spike_below колонки берутся get_tile_div_mod БЕЗ 7. * check_spike_below колонки берутся get_tile_div_mod БЕЗ 7.
* *
* Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */ * Возврат: КОМНАТНЫЙ 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 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 }; 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; int8_t cL, cR, trow, clip_row;
uint8_t t; 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; /* лицом вправо: левый край внутрь */ if (Kid.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
xr = xl + wh; /* char_x_right */ xr = xl + wh; /* char_x_right */
top_y = obj_y - (int)h + 1; /* char_top_y */ top_y = obj_y - (int)h + 1; /* char_top_y */
+4
View File
@@ -122,6 +122,10 @@ void pop_phys_tick(void) __banked;
* Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */ * Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked; 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_type после
* pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */ * pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */
#define EDGE_CLOSER 0 /* край уступа над ямой (дистанция = до кромки) */ #define EDGE_CLOSER 0 /* край уступа над ямой (дистанция = до кромки) */
+6
View File
@@ -17,3 +17,9 @@ uint8_t pop_loose_landed;
/* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара, /* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара,
* но HP не теряет иначе проверять боёвку приходится с трёх попаданий. */ * но HP не теряет иначе проверять боёвку приходится с трёх попаданий. */
uint8_t pop_immortal; 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;
+11
View File
@@ -18,4 +18,15 @@ extern uint8_t pop_loose_landed;
* (боёвка в банке, пики в pop_map), переключает главный цикл. */ * (боёвка в банке, пики в pop_map), переключает главный цикл. */
extern uint8_t pop_immortal; 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 #endif
+4 -43
View File
@@ -9,9 +9,9 @@
* сверху для column-major бесплатна: колонка = непрерывный кусок ОЗУ, старт * сверху для column-major бесплатна: колонка = непрерывный кусок ОЗУ, старт
* просто сдвигается на skip байт, длина accel-блока уменьшается. * просто сдвигается на skip байт, длина accel-блока уменьшается.
* *
* Здесь же живёт полное тело блита колонками gfx_blit_cols() тонкая * Тело (вместе с клип-логикой) в gfx_blit_cols_part_w.c: этот вызов =
* обёртка (skip=0, rows=0), чтобы не дублировать клип-логику. * «без ограничения ширины». Подробности зеркала/клипа см. шапку
* Подробности зеркала/клипа см. шапку gfx_blit_cols.c. * gfx_blit_cols.c.
*/ */
#include <stdint.h> #include <stdint.h>
#include "../_bgi.h" #include "../_bgi.h"
@@ -19,44 +19,5 @@
void gfx_blit_cols_part(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) int skip, int rows)
{ {
const uint8_t *p = (const uint8_t *)img; gfx_blit_cols_part_w(x, y, img, flip, skip, rows, 0);
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();
} }
+70
View File
@@ -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 <stdint.h>
#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();
}
+14
View File
@@ -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, void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip,
int skip, int rows); 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_cols_part так же, как
* gfx_blit_part_noclip к gfx_blit_part. Вызывающий гарантирует: спрайт * gfx_blit_part_noclip к gfx_blit_part. Вызывающий гарантирует: спрайт
* целиком на экране, ширина/высота кадра и y <= 255, skip < высоты. * целиком на экране, ширина/высота кадра и y <= 255, skip < высоты.