a3c473d910
Сцена — ур.13 комната 23, шесть плит с потолка (docs/perf_l13_room23.md).
Все замеры сняты одним и тем же прогоном «ESC -> зонды -> roomtest».
было стало
работа за кадр 1 437 150 1 051 332 -27%
зелёная (фон) 805 000 575 730 -28%
циан (спрайты) 631 800 438 546 -31%
синяя 142 830 142 830 в бюджете
Цена одной перерисовки:
RDA_CEIL_GONE (запечь колодец) 251 335 -> 137 975 -45%
RD_FLOOR (щебень на посадке) 198 805 -> 182 124 -8%
RDA_CEIL (дрожащая плита) 48 785 -> 43 543 -11%
C1. Пометки «фон трогали» в mob_render ПОДАВЛЕНЫ (pop_cd_mute): коридор
куска уже помечен одним вызовом в pop_loose_mob_tick, а три блита метили
подмножества того же прямоугольника по 4 502 такта. Плюс пометка в
mob_spawn_copy — кусок, рождённый внутри тика, свою мог не получить
(слот выдаётся с начала таблицы, то есть уже пройденный). -55 тыс.
G2. Контекст тайла — file-scope, а не локали draw_tile (порт
load_curr_and_left_tile, seg008:0339). В функции 57 вызовов, и каждое
живое через вызов значение спиливалось: 26 байт кадра и 513 обращений
`-N(ix)`. Стало 33 обращения, кадра нет, банк 7 -703 Б. -55 тыс.
G1. Окно клипа для точечной перерисовки (pop_t_win_set/clear). В
pop_ceil_bake_empty куски ряда 0 высотой 63 px рисовались целиком, хотя
восстановить надо девять строк: блит стоил 21 447, стал 7 619. -90 тыс.
В pop_floor_bake окно попробовано и ОТКАЧЕНО (там нет клипа, блиты шли
быстрым путём, а окно уводило их в blit_b_clip и не отсеивало ничего:
187 266 -> 198 279) — вернуться после G3, запись в коде.
G3. blit_b_clip: байтовый габарит + file-scope вместо локалей. Одного
байтового габарита НЕ ХВАТИЛО (кадр остался, 211 -> 173 обращений) —
значений, живых через шесть вызовов ядер libbgi, больше, чем регистров.
С file-scope: 51 обращение, кадр 22 -> 12 Б. Клипованный блит подешевел.
C4. Падающий кусок КЛИПУЕТСЯ сам, вместо чистки бортов после. Раньше он
рисовался в борт целиком и взводил border_dirty, а pop_room_clip_borders
стирал две полосы 320x28 — 150 978 тактов в каждом кадре, пока хоть один
кусок торчит выше поля (гряда 13-го рождается ровно у потолка, y=2), то
есть почти весь каскад. Стало 1 722. -138 тыс.
Заодно blit_b_oversize больше не ходит через blit_b_clip (у того габарит
теперь байтовый) — рисует напрямую gfx_blit_part. Это путь под
полноэкранные подложки интро/финала (320x200, docs/perf_l13_room23.md §5).
Проверено: 8 наборов tests-host зелёные; в MAME каскад рисуется корректно
(кадр с летящими плитами, чистые борта, итоговая картинка как до правок).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
679 lines
39 KiB
C
679 lines
39 KiB
C
/*
|
||
* pop_tile.c — общие ЛИСТЬЯ слоя фона: блит куска атласа, чтение тайла
|
||
* комнаты, таблица тайлов. РЕЗИДЕНТ W1 (_CODE).
|
||
*
|
||
* Мотивация раскладки (почему резидент, а не банк) — в шапке pop_tile.h:
|
||
* коротко, эти функции и таблицы нужны ОБЕИМ половинам слоя фона (горячей —
|
||
* fore-проход каждый кадр, холодной — полная отрисовка комнаты), а код и
|
||
* rodata банка видны только пока банк замаплен. W1 замаплено всегда.
|
||
*/
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprite.h>
|
||
#include "pop_bg.h" /* POP_YOFF, POP_PLAYFIELD_H */
|
||
#include "pop_map.h" /* pop_upside — переворот (зелье инверсии) */
|
||
#include "pop_cdraw.h" /* POP_CD_TOUCH — фон тронут, персонажей перерисовать */
|
||
#include "pop_tile.h"
|
||
#include "pop_level.h" /* pop_doorlink2 — подстановка нажатой кнопки */
|
||
#include "_pop_draw.h" /* pop_heal_fast */
|
||
|
||
/* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */
|
||
atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at;
|
||
|
||
/* Загруженный тайлсет: 0 подземелье, 1 дворец (порт tbl_level_type в той
|
||
* части, что касается ОТРИСОВКИ). Ставит pop_bg_load; резидент — потому
|
||
* что по нему расходятся ветки и в банке 7 (полная отрисовка тайла), и в
|
||
* банке 2 (fore-проход), и в банке 3 (модификатор пустой клетки от упавшей
|
||
* плиты, remove_loose seg007:0EB8). Читать напрямую, без трамплина. */
|
||
uint8_t pop_palace;
|
||
|
||
const uint8_t *pop_t_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg;
|
||
const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod;
|
||
uint8_t pop_t_room;
|
||
pop_rnd_t pop_t_seed;
|
||
|
||
int pop_t_clip_top;
|
||
int pop_t_fclip_x0, pop_t_fclip_y0, pop_t_fclip_x1, pop_t_fclip_y1;
|
||
/* ТО ЖЕ ОКНО В ЛОГИЧЕСКИХ (неперевёрнутых) координатах. Экранная пара нужна
|
||
* для клипа самого блита — он режет уже перевёрнутый прямоугольник; а вот
|
||
* ОТСЕВ ТАЙЛОВ идёт по позиции тайла в комнате (63*row + …), и она от
|
||
* переворота не зависит. Пока пара была одна, при pop_upside отсев сравнивал
|
||
* логический y тайла с зеркальным окном и выбрасывал именно те тайлы, что
|
||
* должны лечь ПОВЕРХ персонажа: передняя грань колонны переставала рисоваться
|
||
* и Кид оказывался перед ней (найдено пользователем на уровне 9). */
|
||
int pop_t_fclip_ly0, pop_t_fclip_ly1;
|
||
uint8_t pop_t_fclip_on;
|
||
uint8_t pop_t_bake_rest;
|
||
|
||
/* Позиция склянки внутри тайла зелья — поля fore_x/fore_y записи 0x0A той же
|
||
* таблицы. Вынесены МАКРОСАМИ (одно место правки, дублирования данных нет):
|
||
* пока таблица была `static const`, SDCC сворачивала `tile_table[10].fore_x`
|
||
* в константу, но глобальную (её читают оба банка) уже не сворачивает —
|
||
* чтение полей в рантайме стоило +150 Б и IX-кадр на 13 локалов. */
|
||
#define POT_FORE_X 2
|
||
#define POT_FORE_Y (-3)
|
||
|
||
/* ---- Таблица тайлов (piece, seg008.c:27) --------------------------- */
|
||
const piece pop_tile_table[31] = {
|
||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 00 empty */
|
||
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 01 floor */
|
||
{127, 1, 0, 133, 1, 2, 145, 0, 43, 0, 0, 0}, /* 02 spike */
|
||
{ 92, 1, 0, 93, 1, 2, 0, 94, 43, 95, 1, 0}, /* 03 pillar */
|
||
{ 46, 1, 0, 47, 1, 2, 0, 48, 43, 49, 3, 0}, /* 04 gate */
|
||
{ 41, 1, 1, 35, 1, 3, 145, 0, 36, 0, 0, 0}, /* 05 stuck floor */
|
||
{ 41, 1, 0, 42, 1, 2, 145, 0, 96, 0, 0, 0}, /* 06 close button */
|
||
{ 46, 1, 0, 0, 0, 2, 0, 0, 43, 49, 3, 0}, /* 07 door top w/floor */
|
||
{ 86, 1, 0, 87, 1, 2, 0, 0, 43, 88, 1, 0}, /* 08 big pillar bottom */
|
||
{ 0, 0, 0, 89, 0, 3, 0, 90, 0, 91, 1, 3}, /* 09 big pillar top */
|
||
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 12, POT_FORE_X, POT_FORE_Y}, /* 0A potion */
|
||
{ 0, 1, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0}, /* 0B loose floor */
|
||
{ 0, 0, 0, 0, 0, 2, 0, 0, 85, 49, 3, 0}, /* 0C door top */
|
||
{ 75, 1, 0, 42, 1, 2, 0, 0, 43, 77, 0, 0}, /* 0D mirror */
|
||
{ 97, 1, 0, 98, 1, 2, 145, 0, 43,100, 0, 0}, /* 0E debris */
|
||
{147, 1, 0, 42, 1, 1, 145, 0,149, 0, 0, 0}, /* 0F open button */
|
||
{ 41, 1, 0, 37, 0, 0, 0, 38, 43, 0, 0, 0}, /* 10 leveldoor left */
|
||
{ 0, 0, 0, 39, 1, 2, 0, 40, 43, 0, 0, 0}, /* 11 leveldoor right */
|
||
{ 0, 0, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 12 chomper */
|
||
{ 41, 1, 0, 42, 1, 2, 0, 0, 43, 0, 0, 0}, /* 13 torch */
|
||
{ 0, 0, 0, 1, 1, 2, 0, 2, 0, 0, 0, 0}, /* 14 wall */
|
||
{ 30, 1, 0, 31, 1, 2, 0, 0, 43, 0, 0, 0}, /* 15 skeleton */
|
||
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 16 sword */
|
||
{ 41, 1, 0, 10, 0, 0, 0, 11, 43, 0, 0, 0}, /* 17 balcony left */
|
||
{ 0, 0, 0, 12, 1, 2, 0, 13, 43, 0, 0, 0}, /* 18 balcony right */
|
||
{ 92, 1, 0, 42, 1, 2, 145, 0, 43, 95, 1, 0}, /* 19 lattice pillar */
|
||
{ 1, 0, 0, 0, 0, 0, 0, 0, 2, 9, 0,-53}, /* 1A lattice down */
|
||
{ 3, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1B lattice small */
|
||
{ 4, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1C lattice left */
|
||
{ 5, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1D lattice right */
|
||
{ 97, 1, 0, 98, 1, 2, 0, 0, 43,100, 0, 0}, /* 1E debris w/torch */
|
||
};
|
||
|
||
/* Деление координаты на ширину тайла ТАБЛИЦЕЙ (tile_div_tbl/tile_mod_tbl,
|
||
* seg006:702/23A6) — как в оригинале. Живут в резиденте, потому что нужны
|
||
* и карте (банк 3, колонка персонажа), и слою фона (банк 2, футпринт), а
|
||
* const-таблицу чужого банка не прочитать.
|
||
*
|
||
* Почему не деление: SDCC разворачивает / и % в __divsint + __modsint,
|
||
* причём __modsint внутри снова зовёт __divsint — замер в MAME дал 5 400
|
||
* тактов на один вызов при 13+ вызовах за кадр. Индекс — координата как
|
||
* есть (0..255), смещение -58 уже внутри значений: POP_TILE_DIV[x] =
|
||
* floor((x - 58) / 14), POP_TILE_MOD[x] — остаток. Сверено с SDLPoP
|
||
* побайтно. */
|
||
const int8_t POP_TILE_DIV[256] = {
|
||
-5, -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4,
|
||
-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2,
|
||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1,
|
||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||
1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||
2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||
3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,
|
||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7,
|
||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8,
|
||
8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
|
||
9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
|
||
10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
|
||
11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14,
|
||
};
|
||
const uint8_t POP_TILE_MOD[256] = {
|
||
12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,
|
||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1,
|
||
2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3,
|
||
4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5,
|
||
6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7,
|
||
8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||
10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,
|
||
12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,
|
||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1,
|
||
2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3,
|
||
4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5,
|
||
6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7,
|
||
8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||
10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,
|
||
12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,
|
||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1,
|
||
};
|
||
|
||
const uint8_t POP_COL_XH[10] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36};
|
||
const uint8_t POP_WALL_FRAM_BOTTOM[4] = {7, 9, 5, 3};
|
||
const uint8_t POP_WALL_FRAM_MAIN[4] = {8, 10, 6, 4};
|
||
const uint8_t POP_SPIKES_FRAM_LEFT[10] = {0, 128, 129, 130, 131, 132, 131, 129, 128, 0};
|
||
const uint8_t POP_LOOSE_FRAM_LEFT[12] = {41,69,41,70,70,41,41,41,70,70,70,0};
|
||
const uint8_t POP_LOOSE_FRAM_BOTTOM[12] = {43,73,43,74,74,43,43,43,74,74,74,0};
|
||
|
||
/* ---- Чомпер: номер позы и передние куски (seg008 chomper_fram1/for) ---
|
||
* Резидент, а не банк: позу считает и полная отрисовка тайла (холодная
|
||
* половина, pop_room.c), и fore-проход поверх персонажа (горячая,
|
||
* pop_bg.c) — а const-таблица банка из чужого банка не читается вовсе. */
|
||
static const uint8_t CHOMP_FRAM1[7] = {3, 2, 0, 1, 4, 3, 3};
|
||
const uint8_t POP_CHOMP_FRAM_FOR[5] = {106, 107, 108, 109, 110};
|
||
|
||
uint8_t pop_chomp_pose(uint8_t modif)
|
||
{
|
||
uint8_t m = (uint8_t)(modif & 0x7F);
|
||
if (m > 6) m = 6;
|
||
return CHOMP_FRAM1[m];
|
||
}
|
||
|
||
/* get_loose_frame (seg008:761): индекс дрожащего кадра по модификатору.
|
||
* В режиме ЗАПЕЧКИ фона отдаёт кадр ПОКОЯ — транзиентный кадр нельзя
|
||
* консервировать в ОЗУ-копию (см. pop_t_bake_rest в pop_bg.c). */
|
||
uint8_t pop_loose_frame(uint8_t m)
|
||
{
|
||
if (pop_t_bake_rest) return 0;
|
||
if (m & 0x80) { m &= 0x7F; if (m > 10) return 1; }
|
||
return m;
|
||
}
|
||
|
||
/* ---- «Фон трогали ВОТ ЗДЕСЬ» (DRAW-COST; контракт — pop_cdraw.h) ----- *
|
||
*
|
||
* Метка — БИТОВАЯ МАСКА ТАЙЛОВ, по слову на ряд и по набору на страницу.
|
||
* Раньше здесь лежал ОДИН union-прямоугольник на страницу, и это убивало
|
||
* пропуск перерисовки персонажа: три факела комнаты трогают по пятнышку
|
||
* 16x18 в колонках 1, 6 и 8, а их объединение — полоса x 40..280 на всю
|
||
* комнату. Кид, стоящий где угодно между крайними факелами, попадал в неё
|
||
* и перерисовывался каждый кадр со всем fore-проходом (замер: циан 231 829
|
||
* тактов против 20 071, период 4 растровых кадра против 3).
|
||
*
|
||
* Гранулярность тайла (32 x 63) вместо точного прямоугольника — осознанное
|
||
* огрубление: факел помечает всю свою колонку по высоте ряда. Персонаж и
|
||
* так занимает бОльшую часть высоты ряда, зато касания перестают
|
||
* склеиваться. См. docs/impl_diff.md. */
|
||
uint8_t pop_cd_dirty;
|
||
uint16_t pop_cd_dmask[2][3];
|
||
|
||
/* CD_LOW[n] = n младших единиц: маска пробега колонок c0..c1 считается как
|
||
* CD_LOW[c1+1] & ~CD_LOW[c0] — два чтения таблицы вместо цикла сдвигов. */
|
||
static const uint16_t CD_LOW[11] = {
|
||
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
|
||
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF
|
||
};
|
||
|
||
/* Экранный y -> ряд комнаты 0..2 с клампом (полоса кладки у потолка и низ
|
||
* стены ложатся на крайние ряды). Цепочкой сравнений, а не делением на 63:
|
||
* у SDCC z80 одно деление ~5 400 тактов (memory sdcc_z80_division_hoisting). */
|
||
static uint8_t cd_row_of(int y)
|
||
{
|
||
y -= POP_YOFF;
|
||
if (y < 63) return 0;
|
||
if (y < 126) return 1;
|
||
return 2;
|
||
}
|
||
|
||
/* Колонки прямоугольника [x..x1] (включительно) -> битовая маска. Тайл ровно
|
||
* 32 px и начинается с x=0, поэтому колонка — просто сдвиг. */
|
||
static uint16_t cd_cols_of(int x, int x1)
|
||
{
|
||
uint8_t c0, c1;
|
||
if (x < 0) x = 0;
|
||
if (x1 > 319) x1 = 319;
|
||
if (x1 < x) return 0; /* весь прямоугольник вне экрана */
|
||
c0 = (uint8_t)(x >> 5);
|
||
c1 = (uint8_t)(x1 >> 5);
|
||
return (uint16_t)(CD_LOW[c1 + 1] & ~CD_LOW[c0]);
|
||
}
|
||
|
||
/* Обе страницы помечаются сразу (персонаж чинится на каждой в свой кадр),
|
||
* поэтому цикл развёрнут: индекс-переменная заставляла SDCC считать адрес
|
||
* каждой ячейки сложением. Зовётся с КАЖДОГО блита фона. */
|
||
/* Пакетная пометка (pop_cd_batch_begin/end). Один тайл рисуется пятью-шестью
|
||
* спрайтами, и каждый звал pop_cd_touch со своими четырьмя 16-битными
|
||
* аргументами — 2 866 тактов на спрайт, 15% всей цены блита (замер
|
||
* 2026-08-13). В пакете спрайты копят ОБЩИЙ прямоугольник, а настоящая
|
||
* пометка ставится одна на тайл.
|
||
*
|
||
* Объединение ЗАГРУБЛЯЕТ прямоугольник (накрывает и то, чего между кусками
|
||
* не касались), и это безопасно ровно в одну сторону: лишняя пометка стоит
|
||
* лишней перерисовки персонажа, пропущенная — стёртого и не восстановленного
|
||
* персонажа. Практически загрубление почти нулевое: все куски одного тайла
|
||
* и так лежат в тех же 1-2 колонках сетки 32px и том же ряду. */
|
||
uint8_t pop_cd_batch;
|
||
static int cdb_x0, cdb_y0, cdb_x1, cdb_y1;
|
||
|
||
void pop_cd_batch_begin(void)
|
||
{
|
||
pop_cd_batch = 1;
|
||
cdb_x0 = cdb_y0 = 32767;
|
||
cdb_x1 = cdb_y1 = -32768;
|
||
}
|
||
|
||
void pop_cd_batch_end(void)
|
||
{
|
||
pop_cd_batch = 0; /* СНАЧАЛА снять: ниже настоящий вызов */
|
||
if (cdb_x1 >= cdb_x0 && cdb_y1 >= cdb_y0)
|
||
pop_cd_touch(cdb_x0, cdb_y0, cdb_x1 - cdb_x0 + 1, cdb_y1 - cdb_y0 + 1);
|
||
}
|
||
|
||
/* Подавление на серию блитов — см. шапку в pop_cdraw.h. Отдельным значением
|
||
* того же флага, а не вторым флагом: у pop_cd_touch тогда остаётся ОДНА
|
||
* проверка на общем пути. */
|
||
#define CD_BATCH_MUTE 2
|
||
|
||
void pop_cd_mute(void) { pop_cd_batch = CD_BATCH_MUTE; }
|
||
void pop_cd_unmute(void) { pop_cd_batch = 0; }
|
||
|
||
void pop_cd_touch(int x, int y, int w, int h)
|
||
{
|
||
uint16_t cols;
|
||
uint8_t r0, r1;
|
||
if (pop_cd_batch) { /* копим, не разбирая на колонки/ряды */
|
||
if (pop_cd_batch == CD_BATCH_MUTE) return; /* область помечена вызывающим */
|
||
int x1 = x + w - 1, y1 = y + h - 1;
|
||
if (x < cdb_x0) cdb_x0 = x;
|
||
if (y < cdb_y0) cdb_y0 = y;
|
||
if (x1 > cdb_x1) cdb_x1 = x1;
|
||
if (y1 > cdb_y1) cdb_y1 = y1;
|
||
return;
|
||
}
|
||
cols = cd_cols_of(x, x + w - 1);
|
||
if (!cols) return;
|
||
r0 = cd_row_of(y);
|
||
r1 = cd_row_of(y + h - 1);
|
||
pop_cd_dmask[0][r0] |= cols;
|
||
pop_cd_dmask[1][r0] |= cols;
|
||
if (r1 != r0) {
|
||
pop_cd_dmask[0][r1] |= cols;
|
||
pop_cd_dmask[1][r1] |= cols;
|
||
if (r1 - r0 > 1) { /* прямоугольник накрыл все три ряда */
|
||
pop_cd_dmask[0][1] |= cols;
|
||
pop_cd_dmask[1][1] |= cols;
|
||
}
|
||
}
|
||
pop_cd_dirty = 3;
|
||
}
|
||
|
||
/* Задел ли прямоугольник (ЭКРАННЫЕ координаты, x1/y1 ВКЛЮЧИТЕЛЬНО) то, что
|
||
* трогали на странице p. Резидент: зовёт pop_cdraw.c из банка 4 прямым
|
||
* вызовом, без трамплина. */
|
||
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1)
|
||
{
|
||
uint16_t cols;
|
||
uint8_t r0, r1;
|
||
if (!(pop_cd_dirty & (1 << p))) return 0;
|
||
cols = cd_cols_of(x0, x1);
|
||
if (!cols) return 0;
|
||
r0 = cd_row_of(y0);
|
||
r1 = cd_row_of(y1);
|
||
if (pop_cd_dmask[p][r0] & cols) return 1;
|
||
if (r1 != r0) {
|
||
if (pop_cd_dmask[p][r1] & cols) return 1;
|
||
if (r1 - r0 > 1 && (pop_cd_dmask[p][1] & cols)) return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Страница приведена в порядок — снять с неё метку целиком. */
|
||
void pop_cd_clear(uint8_t p)
|
||
{
|
||
pop_cd_dmask[p][0] = pop_cd_dmask[p][1] = pop_cd_dmask[p][2] = 0;
|
||
pop_cd_dirty = (uint8_t)(pop_cd_dirty & ~(1 << p));
|
||
}
|
||
|
||
/* ---- Блит куска атласа ---------------------------------------------- */
|
||
/* Клипованный путь — ОТДЕЛЬНОЙ функцией. У него девять 16-битных локалей,
|
||
* и SDCC заводит под них кадр IX; пока он жил в общем теле, за этот кадр
|
||
* платили и блиты БЕЗ клипа (`ld -n(ix)` = 19 тактов номинала, у нас ~46).
|
||
* А блитов без клипа большинство: весь фон вне fore-прохода — факелы, зелья,
|
||
* перерисовка тайлов — идёт с pop_t_fclip_on == 0. */
|
||
#define FLIP_TOP(top, h) POP_FLIP_TOP(top, h) /* формула — в pop_bg.h */
|
||
|
||
/* Окно под точечную перерисовку/запечку — контракт в шапке pop_tile.h.
|
||
* Логическая пара нужна для отсева тайлов (он считается из ряда комнаты),
|
||
* экранная — для клипа самого блита; при перевороте они разные. */
|
||
void pop_t_win_set(int x, int ytop, int w, int h)
|
||
{
|
||
pop_t_fclip_x0 = x;
|
||
pop_t_fclip_x1 = x + w;
|
||
pop_t_fclip_ly0 = ytop;
|
||
pop_t_fclip_ly1 = ytop + h;
|
||
if (pop_upside) {
|
||
int t = FLIP_TOP(ytop, h);
|
||
pop_t_fclip_y0 = t;
|
||
pop_t_fclip_y1 = t + h;
|
||
} else {
|
||
pop_t_fclip_y0 = ytop;
|
||
pop_t_fclip_y1 = ytop + h;
|
||
}
|
||
pop_t_fclip_on = 1;
|
||
}
|
||
|
||
void pop_t_win_clear(void) { pop_t_fclip_on = 0; }
|
||
|
||
/* Клипованный путь блита. Габарит и смещения внутри куска — БАЙТЫ, а вся
|
||
* рабочая переменная — file-scope, НЕ локали.
|
||
*
|
||
* Корректность байтового габарита по построению: кадры наших атласов не
|
||
* крупнее 56x63 (просканированы все 109 .atl, docs/perf_l13_room23.md §5), а
|
||
* кадр больше 255 в pop_blit_b уходит в blit_b_oversize и сюда не попадает.
|
||
*
|
||
* Зачем file-scope: с локалями SDCC заводил 22-байтовый стековый кадр и 211
|
||
* обращений `-N(ix)` — при ~46 замеренных тактах на обращение это ~9 700
|
||
* такта из 14 088 цены клипованного блита (замер 2026-08-17,
|
||
* docs/perf_green_phase.md §1.6). Одного байтового габарита НЕ ХВАТИЛО:
|
||
* кадр остался, обращений стало 173 — значений, живых через шесть вызовов
|
||
* ядер libbgi, всё равно больше, чем регистров у Z80. С file-scope
|
||
* спиливать нечего, а адресация абсолютная (13 тактов против 19).
|
||
* Тот же приём и по той же причине применён к draw_tile (pop_room.c).
|
||
*
|
||
* Реентерабельности не было и не требуется: единственный вызывающий —
|
||
* pop_blit_b, и он себя не зовёт.
|
||
*
|
||
* Арифметика клипа считается в int16 (bc_d), в байт уходит только результат —
|
||
* иначе вычитание ушло бы в перенос по модулю 256. */
|
||
static const uint8_t *bc_img;
|
||
static int bc_dx, bc_dy;
|
||
static int16_t bc_d;
|
||
static uint8_t bc_w, bc_h, bc_dw, bc_dh, bc_sx, bc_sy;
|
||
|
||
static void blit_b_clip(const uint8_t *img, int x, int top, uint8_t w, uint8_t h)
|
||
{
|
||
bc_img = img; bc_w = w; bc_h = h;
|
||
bc_dx = x; bc_dy = top;
|
||
bc_dw = w; bc_dh = h; bc_sx = 0; bc_sy = 0;
|
||
if (!pop_upside) {
|
||
if (pop_t_clip_top && bc_dy < pop_t_clip_top) { /* полоса у потолка */
|
||
bc_d = (int16_t)(pop_t_clip_top - bc_dy);
|
||
if (bc_d >= (int16_t)bc_dh) return;
|
||
bc_sy = (uint8_t)bc_d; bc_dy = pop_t_clip_top;
|
||
bc_dh = (uint8_t)(bc_dh - bc_sy);
|
||
}
|
||
} else {
|
||
/* ПЕРЕВОРОТ: то, что в обычном виде торчало ВЫШЕ поля (полоса кладки
|
||
* у потолка), теперь торчит НИЖЕ него — и без обрезки лезет в борт,
|
||
* где живёт полоса HP (баг «мусор ниже 192», найден пользователем).
|
||
* Режем по границам поля ВСЕГДА, а не по pop_t_clip_top: зеркальный
|
||
* спрайт может вылезти и там, где в обычном виде клип не ставили. */
|
||
if (bc_dy < POP_YOFF) {
|
||
bc_d = (int16_t)(POP_YOFF - bc_dy);
|
||
if (bc_d >= (int16_t)bc_dh) return;
|
||
bc_sy = (uint8_t)bc_d; bc_dy = POP_YOFF;
|
||
bc_dh = (uint8_t)(bc_dh - bc_sy);
|
||
}
|
||
if (bc_dy + bc_dh > POP_CLIP_BOTTOM) {
|
||
bc_d = (int16_t)(POP_CLIP_BOTTOM - bc_dy);
|
||
if (bc_d <= 0) return;
|
||
bc_dh = (uint8_t)bc_d;
|
||
}
|
||
}
|
||
if (pop_t_fclip_on) { /* окно fore-слоя / перерисовки */
|
||
if (bc_dx < pop_t_fclip_x0) {
|
||
bc_d = (int16_t)(pop_t_fclip_x0 - bc_dx);
|
||
if (bc_d >= (int16_t)bc_dw) return;
|
||
bc_sx = (uint8_t)(bc_sx + (uint8_t)bc_d);
|
||
bc_dx += bc_d; bc_dw = (uint8_t)(bc_dw - (uint8_t)bc_d);
|
||
}
|
||
if (bc_dy < pop_t_fclip_y0) {
|
||
bc_d = (int16_t)(pop_t_fclip_y0 - bc_dy);
|
||
if (bc_d >= (int16_t)bc_dh) return;
|
||
bc_sy = (uint8_t)(bc_sy + (uint8_t)bc_d);
|
||
bc_dy += bc_d; bc_dh = (uint8_t)(bc_dh - (uint8_t)bc_d);
|
||
}
|
||
if (bc_dx + bc_dw > pop_t_fclip_x1) {
|
||
bc_d = (int16_t)(pop_t_fclip_x1 - bc_dx);
|
||
if (bc_d <= 0) return;
|
||
bc_dw = (uint8_t)bc_d;
|
||
}
|
||
if (bc_dy + bc_dh > pop_t_fclip_y1) {
|
||
bc_d = (int16_t)(pop_t_fclip_y1 - bc_dy);
|
||
if (bc_d <= 0) return;
|
||
bc_dh = (uint8_t)bc_d;
|
||
}
|
||
}
|
||
if (!bc_dw || !bc_dh) return;
|
||
/* ПЕРЕВОРОТ: экранная строка (dy+i) берёт источник (h-1-i), поэтому
|
||
* обрезка СВЕРХУ экрана съедает НИЖНИЕ строки источника. В терминах
|
||
* куска источника это sy' = h - sy - dh (при обрезке только сверху даёт
|
||
* 0, при обрезке только снизу — верхний край). */
|
||
/* Проверки `dw < 256 && dh < 256 && w < 256` убраны как тождественные:
|
||
* это уже байты. */
|
||
if (pop_upside) {
|
||
bc_d = (int16_t)bc_h - (int16_t)bc_sy - (int16_t)bc_dh;
|
||
if (bc_d < 0) bc_d = 0;
|
||
if (bc_dx >= 0 && bc_dy >= 0 &&
|
||
bc_dx + bc_dw <= 320 && bc_dy + bc_dh <= 256)
|
||
gfx_blit_part_noclip_vflip(bc_dx, bc_dy, bc_img, bc_sx,
|
||
(uint8_t)bc_d, bc_dw, bc_dh);
|
||
else
|
||
gfx_blit_part_vflip(bc_dx, bc_dy, bc_img, bc_sx,
|
||
(uint8_t)bc_d, bc_dw, bc_dh);
|
||
} else
|
||
if (bc_dx >= 0 && bc_dy >= 0 &&
|
||
bc_dx + bc_dw <= 320 && bc_dy + bc_dh <= 256) {
|
||
if (bc_sx == 0 && bc_sy == 0 && bc_dw == bc_w && bc_dh == bc_h)
|
||
gfx_blit_noclip(bc_dx, bc_dy, bc_img);
|
||
else
|
||
gfx_blit_part_noclip(bc_dx, bc_dy, bc_img, bc_sx, bc_sy,
|
||
bc_dw, bc_dh);
|
||
} else {
|
||
gfx_blit_part(bc_dx, bc_dy, bc_img, bc_sx, bc_sy, bc_dw, bc_dh);
|
||
}
|
||
/* Кусок ФОНА лёг туда, где может лежать уже нарисованный персонаж —
|
||
* пометить область (DRAW-COST, pop_cdraw.h). Признак «это не fore-проход
|
||
* поверх персонажа» — окно клипа: его ставит только он. */
|
||
if (!pop_t_fclip_on) pop_cd_touch(bc_dx, bc_dy, bc_dw, bc_dh);
|
||
}
|
||
|
||
/* Кадр шире или выше 255 — общий (медленный) путь. Вынесен отдельно, чтобы
|
||
* 16-битная арифметика габарита не жила ни в горячем pop_blit_b, ни в
|
||
* blit_b_clip (у того габарит теперь БАЙТОВЫЙ, см. его шапку). У нас таких
|
||
* кадров в игровом кадре нет вовсе (максимум 56x63), но контракт формата
|
||
* .atl их допускает, а интро/финал такие подложки принесут (320x200) —
|
||
* см. docs/perf_l13_room23.md §5.
|
||
*
|
||
* Клипа полосы у потолка и окна перерисовки здесь нет намеренно: такой кадр
|
||
* — полноэкранная подложка, ей ни то, ни другое не адресовано; за клип по
|
||
* экрану отвечает само ядро gfx_blit_part. */
|
||
static void blit_b_oversize(const uint8_t *img, int x, int ybottom)
|
||
{
|
||
uint16_t w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||
uint16_t h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||
int top = ybottom - (int)h + 1 + POP_YOFF;
|
||
if (pop_upside) top = FLIP_TOP(top, h);
|
||
if (!w || !h) return;
|
||
gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
|
||
if (!pop_t_fclip_on) pop_cd_touch(x, top, (int)w, (int)h);
|
||
}
|
||
|
||
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||
{
|
||
const uint8_t *img;
|
||
uint8_t w, h; /* БАЙТЫ, а не uint16_t — см. ниже */
|
||
int top;
|
||
if (idx >= a->count)
|
||
return;
|
||
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
|
||
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
|
||
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
|
||
* 64×64) и, если он не задевает окно, уходим не заплатив ни за
|
||
* atlas_image, ни за map/unmap. */
|
||
if (pop_t_fclip_on &&
|
||
(x >= pop_t_fclip_x1 || x + 64 <= pop_t_fclip_x0 ||
|
||
ybottom + POP_YOFF + 1 <= pop_t_fclip_ly0 ||
|
||
ybottom + POP_YOFF - 64 >= pop_t_fclip_ly1))
|
||
return;
|
||
pop_dbg_b1(); /* ВРЕМЕННО */
|
||
img = (const uint8_t *)atlas_image(a, idx);
|
||
gfx_w0_map(a->page);
|
||
pop_dbg_b2(); /* ВРЕМЕННО */
|
||
/* Габарит читаем БАЙТАМИ. С `uint16_t w = img[0] | (img[1] << 8)` SDCC
|
||
* разворачивал каждую такую строку в два десятка IX-относительных
|
||
* пересылок: 16-битные w/h не влезали в регистры, и функция целиком
|
||
* уезжала в стековый кадр (пролог `ld iy,#-14 / add iy,sp / ld sp,iy`,
|
||
* дальше всё через -N(ix) по 19 тактов). Замер 2026-08-13: до вызова
|
||
* ядра блита уходило ~6 700 тактов при том, что работы там — прочитать
|
||
* четыре байта заголовка и сравнить границы.
|
||
* Байтовый габарит корректен по построению: обе ветки ниже И ТАК требуют
|
||
* w < 256 && h < 256 (иначе ядро не примет), а кадры наших атласов не
|
||
* крупнее 32x63. Формат .atl допускает больше — такой кадр уходит на
|
||
* общий путь выше. */
|
||
if (img[1] | img[3]) { /* кадр больше 255 — редкий путь */
|
||
blit_b_oversize(img, x, ybottom);
|
||
gfx_w0_unmap();
|
||
return;
|
||
}
|
||
w = img[0];
|
||
h = img[2];
|
||
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
||
/* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно
|
||
* fore-слоя, полоса у потолка, отсев по экрану) считается уже в
|
||
* экранных координатах результата. */
|
||
if (pop_upside) top = FLIP_TOP(top, h);
|
||
if (w && h) {
|
||
if (!pop_t_fclip_on && !pop_t_clip_top) {
|
||
/* БЫСТРЫЙ путь: клипа нет вовсе. Так идут все блиты фона вне
|
||
* fore-прохода. Линейное ядро без клипа — ~2.9× быстрее общего
|
||
* (замер: 4617 против 13288 тактов на спрайт 32×3; gfx_blit
|
||
* платит за клип/16-бит/split, а не за пиксели). W3-скобку
|
||
* ставит сама libbgi: из кода с --w3 её вызывать нельзя. */
|
||
/* В перевёрнутом виде быстрый путь годится, только если спрайт
|
||
* целиком внутри поля: иначе он залезет в борт (см. blit_b_clip). */
|
||
if (pop_upside && (top < POP_YOFF || top + (int)h > POP_CLIP_BOTTOM)) {
|
||
blit_b_clip(img, x, top, w, h);
|
||
gfx_w0_unmap();
|
||
return;
|
||
}
|
||
/* w<256 && h<256 больше не проверяем — гарантировано типом. */
|
||
if (x >= 0 && top >= 0 &&
|
||
x + (int)w <= 320 && top + (int)h <= 256) {
|
||
pop_dbg_b6(); /* ВРЕМЕННО: пометка «шли в noclip» */
|
||
if (pop_upside) gfx_blit_noclip_vflip(x, top, img);
|
||
else gfx_blit_noclip(x, top, img);
|
||
} else {
|
||
if (pop_upside) gfx_blit_part_vflip(x, top, img, 0, 0, (int)w, (int)h);
|
||
else gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
|
||
}
|
||
pop_dbg_b3(); /* ВРЕМЕННО */
|
||
pop_cd_touch(x, top, (int)w, (int)h);
|
||
pop_dbg_b4(); /* ВРЕМЕННО */
|
||
} else {
|
||
blit_b_clip(img, x, top, w, h);
|
||
pop_dbg_b3(); pop_dbg_b4(); /* ВРЕМЕННО: медленный путь целиком в b3 */
|
||
}
|
||
}
|
||
gfx_w0_unmap();
|
||
pop_dbg_b5(); /* ВРЕМЕННО */
|
||
}
|
||
|
||
void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> POP_ENV_SHIFT], (uint8_t)(id & POP_ENV_MASK), x, yb); }
|
||
void pop_wall_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_wall_at, id, x, yb); }
|
||
void pop_fore_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_fore_at, id, x, yb); }
|
||
void pop_pot_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_pot_at, id, x, yb); }
|
||
|
||
/* Heal по КОМНАТНОЙ координате (её и знают точечные перерисовки: чомпер,
|
||
* пики, loose, ворота, полоса потолка). При перевороте прямоугольник
|
||
* обязан отразиться ровно так же, как сам тайл, — иначе heal чистит одно
|
||
* место, а рисует в другое: именно так «ломалась анимация чомперов»
|
||
* (найдено пользователем на первом же прогоне зеркального фона). */
|
||
void pop_bar_black(int x, int top, int w, int h)
|
||
{
|
||
if (pop_upside) top = FLIP_TOP(top, h);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
bar(x, top, x + w - 1, top + h - 1);
|
||
pop_cd_touch(x, top, w, h); /* bar идёт мимо pop_blit_b (DRAW-COST) */
|
||
}
|
||
|
||
void pop_heal_off(int x, int y, int w, int h)
|
||
{
|
||
int top = y + POP_YOFF;
|
||
if (pop_upside) top = FLIP_TOP(top, h);
|
||
pop_heal_fast(x, top, w, h);
|
||
}
|
||
|
||
/* ---- Чтение тайла отрисованной комнаты ------------------------------ */
|
||
uint8_t pop_tile_code(int row, int col)
|
||
{
|
||
if (col < 0) {
|
||
if (pop_t_lfg && row >= 0 && row < 3) return pop_t_lfg[row];
|
||
return 20; /* уровневая кромка = стена */
|
||
}
|
||
if (row < 0) { /* верхняя полоса: ряд 2 комнаты сверху */
|
||
if (row == -1 && pop_t_above && col < 10) return pop_t_above[col];
|
||
return 1; /* нет комнаты сверху → пол (кромка) */
|
||
}
|
||
if (row > 2 || col > 9) return 20;
|
||
return pop_t_fg[row * 10 + col];
|
||
}
|
||
|
||
uint8_t pop_tile_mod(int row, int col)
|
||
{
|
||
if (col < 0) {
|
||
if (pop_t_lbg && row >= 0 && row < 3) return pop_t_lbg[row];
|
||
return 0;
|
||
}
|
||
if (row == -1 && pop_t_abovemod && col < 10) return pop_t_abovemod[col];
|
||
if (row < 0 || row > 2 || col > 9) return 0;
|
||
return pop_t_bg[row * 10 + col];
|
||
}
|
||
|
||
/* Порт подстановки нажатой кнопки из get_tile_to_draw (seg008:240): opener →
|
||
* floor, closer → stuck, когда таймер связи > 1. Любой слой, который
|
||
* выбирает ветку отрисовки по типу тайла, обязан смотреть на ПОДСТАВЛЕННЫЙ
|
||
* тип (иначе кнопка, на которой стоит Kid, ведёт себя как «не пол» — напр.
|
||
* climb-оверлей уходил в draw_other_overlay и закрашивал Kid целым тайлом
|
||
* вместо узкой кромки floor_left_overlay). */
|
||
uint8_t pop_tile_code_drawn(int row, int col)
|
||
{
|
||
uint8_t code = pop_tile_code(row, col);
|
||
if (code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||
else if (code == 0x06) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||
return code;
|
||
}
|
||
|
||
uint8_t pop_wall_modifier(int row, int col)
|
||
{
|
||
uint8_t lw = (col > 0) ? (pop_tile_code(row, col - 1) == 20) : 1;
|
||
uint8_t rw = (col < 9) ? (pop_tile_code(row, col + 1) == 20) : 1;
|
||
if (lw && rw) return 3; /* WWW */
|
||
if (lw) return 2; /* WWS */
|
||
if (rw) return 1; /* SWW */
|
||
return 0; /* SWS */
|
||
}
|
||
|
||
/* ---- Зелье: склянка (tiles_10_potion, seg008 draw_tile_fore) --------- *
|
||
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
|
||
* склянка (id 13), остальные — маленькая (id 12). Спрайты из chtab_1 (атлас
|
||
* pop_pot), поэтому НЕ из env: раньше рисовали chtab_6 id 12 (res212),
|
||
* которого в VDUNGEON нет — оттуда и «мусорный объект». Позиция оригинала:
|
||
* xh = fore_x + draw_xh, xl = 6, ybottom = fore_y + draw_main_y.
|
||
*
|
||
* Здесь, а не в банке: склянку рисует и полная отрисовка тайла (холодная
|
||
* половина), и fore-проход поверх персонажа (горячая). */
|
||
void pop_potion_flask(int row, int col, uint8_t modif)
|
||
{
|
||
uint8_t type = (uint8_t)((modif & 0xF8) >> 3);
|
||
uint8_t id = 12;
|
||
if (type == 0) return;
|
||
if (type >= 2 && type < 5) id = 13; /* большая склянка */
|
||
if (pop_palace) id += 2; /* seg008:747: во дворце склянки другие */
|
||
pop_pot_b(id, (POT_FORE_X + POP_COL_XH[col]) * 8 + 6,
|
||
POT_FORE_Y + 63 * row + 62);
|
||
}
|
||
|
||
/* Окно fore-клипа: ставит тот, кто нарисовал персонажа (pop_cdraw, банк 4),
|
||
* читает blit_b. Резидент — чтобы вызов из банка шёл без трамплина. */
|
||
void pop_fore_set_clip(int x, int y, int w, int h)
|
||
{
|
||
pop_t_fclip_x0 = x; pop_t_fclip_y0 = y;
|
||
pop_t_fclip_x1 = x + w; pop_t_fclip_y1 = y + h;
|
||
if (pop_upside) { /* вернуть окно в неперевёрнутый вид */
|
||
pop_t_fclip_ly0 = 2 * POP_YOFF + POP_PLAYFIELD_H - (y + h);
|
||
pop_t_fclip_ly1 = 2 * POP_YOFF + POP_PLAYFIELD_H - y;
|
||
} else {
|
||
pop_t_fclip_ly0 = y; pop_t_fclip_ly1 = y + h;
|
||
}
|
||
}
|
||
|
||
/* Ряд 0 комнаты СНИЗУ (fg-коды) — нужен draw_tile_topright/floorright, когда
|
||
* тайл нижнего ряда стал видимым (empty после падения loose): «стена вниз» =
|
||
* топрайт тайла room_B(0, col-1). 0 — нет комнаты снизу (кромка = стена). */
|
||
void pop_room_set_below(const uint8_t *below_row0_fg) { pop_t_below = below_row0_fg; }
|
||
|
||
/* Ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки у
|
||
* потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
|
||
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
|
||
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod)
|
||
{ pop_t_above = above_row2_fg; pop_t_abovemod = above_row2_mod; }
|