40f0d46542
Замер на сцене каскада (ур.13 к.23) ПОСЛЕ багфиксов показал регресс, причём
в том числе в СИНЕЙ фазе, которую фиксы не трогали:
до фиксов после фиксов после этой правки
работа 783 798 1 038 528 873 930
синяя 142 830 275 538 142 830
зелёная 414 456 415 284 417 630
циан 269 538 479 628 379 482
период (растр.) 4 5 4 (один кадр 5)
Разбор синей зондом L (граница «ввод+heal» / «логика») сразу указал место:
логика 88 020 не изменилась, а «ввод+heal» вырос 55 000 -> 187 500. Это
pop_fore_heal чистит ОБЪЕДИНЁННЫЙ ov_mark: кромка потолка (полоса y 0..8) и
оверлеи соседей (64x64 на тайл) от шести кусков сливались в прямоугольник на
полкомнаты.
Правки, каждая с замером:
1. Оверлей соседа рисуется в ОКНЕ = прямоугольник самого куска. Смысл
оверлея — перекрыть кусок, а что лежит вне него, и так нарисовано
правильным фоном. Даёт три вещи разом: пикселей в разы меньше; куски
тайла, не задевающие кусок, отсеиваются предфильтром pop_blit_b даром (это
и есть вертикальный гейт, отдельного не надо); ov_mark становится НЕ НУЖЕН
— всё нарисованное лежит внутри heal-коридора куска, а он чистится каждый
кадр. Для этого заведён ov_suppress.
2. Кромка потолка — тоже без ov_mark: её куски рисуются от dby = 2, то есть
занимают строки 0..2, а помечает её только кусок и только пока достаёт
(mob_y <= 18) — значит она внутри его же коридора.
3. Предусловия перенесены в РЕЗИДЕНТ, до вызова в банк 2: pop_mob_overlay_tile
и разбор пометок живут в других банках, и каждый вызов платит межбанковый
трамплин (~8 900), а условий всего два дешёвых чтения тайла
(pop_tile_code — резидент, ~1 100). Гейты: тайл соседа не пуст и его
габарит пересекается с габаритом куска; для полосы потолка — тайл полосы
не пуст (а он там чаще всего пуст: кусок существует ровно потому, что
плита из своей клетки выпала, и пустой тайл в переднем слое не рисует
ничего).
ОТРИЦАТЕЛЬНЫЙ РЕЗУЛЬТАТ, откачен: сдавать пометки ряда −1 одной МАСКОЙ
вместо до двенадцати отдельных вызовов (один трамплин вместо двенадцати) —
873 930 -> 879 276, то есть хуже. Пометки ставятся редко, а цена копилки и
разбора маски платится всегда.
Итог: все три секции в бюджете 400 000 (синяя 158 880, циан 379 482), зелёная
417 630 — она была такой и ДО фиксов, это не регресс. Период вернулся к 4
растровым кадрам; ровно один логический кадр из 54 идёт за 5 (работа 873 930
при пороге 860 000).
Цена четырёх багфиксов по итогу: +90 000 работы за кадр (+11 %), из них весь
рост в циане.
Тестовый образ теперь всегда собирается LEVEL=1 (решение пользователя:
переход на нужный уровень он делает сам).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1752 lines
117 KiB
C
1752 lines
117 KiB
C
/*
|
||
* pop_room.c — ХОЛОДНАЯ половина слоя фона PoP: полная отрисовка комнаты
|
||
* (порт seg008.c draw_tile / render_room.py), точечные перерисовки тайлов
|
||
* при событиях (пики, ворота, кнопки, дверь уровня, дрожащие плиты) и
|
||
* падающие куски loose (mob, seg007 move_loose). Всё это работает раз на
|
||
* комнату или раз на событие, поэтому живёт в ОТДЕЛЬНОМ банке и платит
|
||
* трамплин за вызовы горячей половины — см. _pop_bg.h, там же граница реза.
|
||
*
|
||
* Только DUNGEON-стиль (Фаза 1).
|
||
*/
|
||
/* Имена файлов атласов обоих тайлсетов нужны только здесь; макрос обязан
|
||
* стоять ДО первого включения pop_bg_atlas.h (его тянет pop_tile.h). */
|
||
#define POP_BG_ATLAS_NAMES
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprite.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include "pop_bg.h"
|
||
#include "_pop_bg.h" /* контракт с горячей половиной (pop_bg.c, банк 2) */
|
||
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||
#include "pop_state.h"
|
||
#include "pop_redraw.h" /* пометки тайлов: сосед падающего куска (draw_mob) */
|
||
#include "pop_geom.h"
|
||
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
|
||
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */
|
||
#include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */
|
||
#include "pop_map.h" /* pop_upside — переворот (зелье инверсии) */
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
|
||
/* Фазы loose-плит: массив ТЕКУЩЕЙ комнаты (pop_map) и персистентный per-room
|
||
* (pop_trob) плюс постановка trob. Объявлены здесь, а не включением обоих
|
||
* заголовков: нужна ровно одна ячейка каждому — падающий кусок срывает
|
||
* плиту, в которую врезался (loose_fall, см. mob_break_loose). */
|
||
extern uint8_t pop_loose_modif[30];
|
||
uint8_t *pop_trob_modif(uint8_t room) __banked;
|
||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type) __banked;
|
||
/* remove_loose для ОДНОГО тайла — снять плиту с карты немедленно (честный
|
||
* loose_fall, см. mob_break_loose). Живёт в pop_map: там рабочая копия
|
||
* тайлов комнаты и фазы плит. */
|
||
void pop_loose_remove(uint8_t room, uint8_t tilepos) __banked;
|
||
|
||
/* ---- Тайлсет: подземелье / дворец ----------------------------------- *
|
||
* Порт tbl_envir_ki[tbl_level_type[level]] (seg000:1108): оригинал под один
|
||
* и тот же набор id грузит РАЗНЫЙ .DAT — VDUNGEON или VPALACE. У нас так
|
||
* же: раскладка id -> (страница, idx) общая (упаковщик печёт оба набора по
|
||
* одному объединению id), меняются только файлы атласов и 32 записи
|
||
* палитры — env 0x50..0x5F и wall 0x60..0x6F. Кид, страж, меч и зелья
|
||
* живут в других слотах, поэтому общая kid.pal не трогается. */
|
||
static uint8_t bg_set = 0xFF; /* какой набор сейчас загружен */
|
||
|
||
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон, KID —
|
||
* персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска зарастает
|
||
* десятками .atl. Путь склеиваем на месте: держать по строке на каждый из
|
||
* двадцати атласов — лишние полкилобайта в банке. */
|
||
static char bg_path_buf[20];
|
||
|
||
static const char *bg_path(const char *name)
|
||
{
|
||
char *d = bg_path_buf;
|
||
*d++ = 'B'; *d++ = 'G'; *d++ = '\\';
|
||
while (*name) *d++ = *name++;
|
||
*d = 0;
|
||
return bg_path_buf;
|
||
}
|
||
|
||
/* 32 записи (env, затем wall) в ОБЕ палитры: у страниц дабл-буфера они
|
||
* раздельные. Буфер — на стеке: gfx_pal_load отдаёт указатель BIOS ($A4),
|
||
* а тот читает только #4000-#BFFF, то есть банковую rodata бы не увидел
|
||
* (те же грабли, что в pop_guard_set_palette). */
|
||
static int bg_load_tile_pal(const char *name)
|
||
{
|
||
uint8_t buf[64];
|
||
int fd = open(bg_path(name), O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
if (read(fd, buf, 64) != 64) { close(fd); return -1; }
|
||
gfx_pal_load(0, POP_PAL_ENV, 16, buf);
|
||
gfx_pal_load(1, POP_PAL_ENV, 16, buf);
|
||
if (read(fd, buf, 64) != 64) { close(fd); return -1; }
|
||
gfx_pal_load(0, POP_PAL_WALL, 16, buf);
|
||
gfx_pal_load(1, POP_PAL_WALL, 16, buf);
|
||
close(fd);
|
||
return 0;
|
||
}
|
||
|
||
/* ---- Композитный спрайт ПАДАЮЩЕГО КУСКА ------------------------------ *
|
||
* Три части (env 70 / 74 / 72, см. блок MOB_X0 ниже) складываются в ОДИН
|
||
* getimage-блоб в обычной памяти, и кусок рисуется одним блитом вместо трёх.
|
||
*
|
||
* Зачем: у блита ~8 800 такта постоянных накладных против ~5 000 на пиксели
|
||
* (memory blit_cost_model). Шесть кусков в воздухе давали 18 вызовов и
|
||
* 258 708 такта — БОЛЬШЕ ПОЛОВИНЫ цианового блока (замер 2026-08-17,
|
||
* docs/perf_cyan_phase.md). Плюс блоб в обычной памяти не требует
|
||
* atlas_image и gfx_w0_map/unmap — ещё ~1 350 такта на вызов.
|
||
*
|
||
* Прозрачность соблюдена: части ПЕРЕКРЫВАЮТСЯ (74 и 70 обе идут от mob_x),
|
||
* поэтому копируются попиксельно с пропуском 0xFF — ровно как три блита с
|
||
* прозрачностью друг поверх друга.
|
||
*
|
||
* Размер буфера — по худшему из тайлсетов (дворец даёт 63 в ширину,
|
||
* подземелье 19 в высоту), 1 201 Б статики W2. Куча roomtest не
|
||
* используется (malloc'ов в приложении нет), так что это её и не задевает.
|
||
*
|
||
* Собирается на КАЖДУЮ смену тайлсета: размеры частей у наборов разные. */
|
||
/* Коридор heal падающего куска — по фактической высоте СОБРАННОГО композита
|
||
* (у дворца он 16 строк, у подземелья 19). Держать константные 24 «на всякий
|
||
* случай» значило платить лишними строками на самой дорогой операции кадра,
|
||
* помноженными на число падающих плит. Ставит mob_spr_build; стартовые
|
||
* значения — безопасный максимум (см. MOB_HEAL_UP/MOB_HEAL_H ниже). */
|
||
static uint8_t mob_heal_up = 20;
|
||
static uint8_t mob_heal_h = 24;
|
||
|
||
/* Размер буфера — верхняя граница по обоим тайлсетам с запасом; фактические
|
||
* w/h композита считаются при сборке и кладутся в его шапку. */
|
||
#define MOBS_MAXW 64
|
||
#define MOBS_MAXH 20
|
||
static uint8_t mob_spr[4 + MOBS_MAXW * MOBS_MAXH];
|
||
static uint8_t mob_spr_ok; /* 0 — не собрался, рисуем тремя блитами */
|
||
|
||
/* Части композита: id, смещение по x, смещение НИЗА части относительно mob_y.
|
||
* Порядок — как в mob_render (низ, левая часть, правая), он важен: части
|
||
* перекрываются. */
|
||
static const uint8_t MOBS_ID[3] = {74, 70, 72};
|
||
static const uint8_t MOBS_XO[3] = {0, 0, 32};
|
||
static const int8_t MOBS_YBO[3] = {0, -3, -1};
|
||
|
||
static void mob_spr_build(void)
|
||
{
|
||
uint8_t w[3], h[3], k, r, c, v, cw, ch;
|
||
const uint8_t *src;
|
||
int top = 0, row0;
|
||
|
||
mob_spr_ok = 0;
|
||
/* Проход 1 — габариты частей. Считать их обязательно, а не брать
|
||
* константами: у тайлсетов они РАЗНЫЕ (напр. правая часть 72 — 26 px в
|
||
* подземелье и 25 во дворце). */
|
||
for (k = 0; k < 3; k++) {
|
||
atlas_t *a = &pop_env[MOBS_ID[k] >> POP_ENV_SHIFT];
|
||
uint8_t idx = (uint8_t)(MOBS_ID[k] & POP_ENV_MASK);
|
||
if (idx >= a->count) return;
|
||
src = (const uint8_t *)atlas_image(a, idx);
|
||
gfx_w0_map(a->page);
|
||
w[k] = src[0]; h[k] = src[2];
|
||
v = (uint8_t)(src[1] | src[3]);
|
||
gfx_w0_unmap();
|
||
if (v || !w[k] || !h[k]) return; /* кадр >255 или пустой */
|
||
row0 = (int)MOBS_YBO[k] - (int)h[k] + 1;
|
||
if (row0 < top) top = row0;
|
||
}
|
||
/* Габарит композита: низ = mob_y, верх = самая высокая часть. */
|
||
ch = (uint8_t)(1 - top);
|
||
cw = 0;
|
||
for (k = 0; k < 3; k++)
|
||
if ((uint8_t)(MOBS_XO[k] + w[k]) > cw) cw = (uint8_t)(MOBS_XO[k] + w[k]);
|
||
if (cw > MOBS_MAXW || ch > MOBS_MAXH) return;
|
||
mob_spr[0] = cw; mob_spr[1] = 0;
|
||
mob_spr[2] = ch; mob_spr[3] = 0;
|
||
/* Пустое место — ПРОЗРАЧНОЕ (0xFF), иначе композит закрасил бы фон вокруг
|
||
* частей. Страйд блоба = его ширина, поэтому чистим ровно cw*ch. */
|
||
for (r = 0; r < ch; r++)
|
||
for (c = 0; c < cw; c++) mob_spr[4 + (uint16_t)r * cw + c] = 0xFF;
|
||
/* Проход 2 — копирование ПОПИКСЕЛЬНО с пропуском 0xFF: части
|
||
* ПЕРЕКРЫВАЮТСЯ (74 и 70 обе идут от mob_x), и прозрачные пиксели верхней
|
||
* не должны стирать нижнюю — ровно как три прозрачных блита друг поверх
|
||
* друга. */
|
||
for (k = 0; k < 3; k++) {
|
||
atlas_t *a = &pop_env[MOBS_ID[k] >> POP_ENV_SHIFT];
|
||
src = (const uint8_t *)atlas_image(a, (uint8_t)(MOBS_ID[k] & POP_ENV_MASK));
|
||
gfx_w0_map(a->page);
|
||
row0 = (int)MOBS_YBO[k] - (int)h[k] + 1 - top;
|
||
for (r = 0; r < h[k]; r++) {
|
||
const uint8_t *sp = src + 4 + (uint16_t)r * w[k];
|
||
uint8_t *dp = mob_spr + 4 + (uint16_t)(row0 + r) * cw + MOBS_XO[k];
|
||
for (c = 0; c < w[k]; c++) { v = sp[c]; if (v != 0xFF) dp[c] = v; }
|
||
}
|
||
gfx_w0_unmap();
|
||
}
|
||
/* Коридор heal — по композиту: две строки запаса сверху, одна снизу. */
|
||
mob_heal_up = (uint8_t)(ch + 1);
|
||
mob_heal_h = (uint8_t)(ch + 3);
|
||
mob_spr_ok = 1;
|
||
}
|
||
|
||
/* Загрузить набор set (0 подземелье, 1 дворец). Повторный вызов с тем же
|
||
* набором ничего не делает; со сменой — сначала выгружает старый, иначе
|
||
* EMM-страницы утекут (их по 12 на набор). */
|
||
int pop_bg_load(uint8_t set) __banked
|
||
{
|
||
int i;
|
||
if (set >= POP_TILESETS) return -1;
|
||
if (set == bg_set) return 0;
|
||
if (bg_set != 0xFF) { /* смена набора — освободить старый */
|
||
for (i = 0; i < POP_ENV_PAGES; i++) atlas_free(&pop_env[i]);
|
||
atlas_free(&pop_wall_at);
|
||
atlas_free(&pop_fore_at);
|
||
}
|
||
for (i = 0; i < POP_ENV_PAGES; i++)
|
||
if (atlas_load(&pop_env[i], bg_path(pop_env_atl[set][i])) != 0) return -1;
|
||
if (atlas_load(&pop_wall_at, bg_path(pop_wall_atl[set])) != 0) return -1;
|
||
if (atlas_load(&pop_fore_at, bg_path(pop_fore_atl[set])) != 0) return -1;
|
||
if (bg_set == 0xFF && /* chtab_1 от набора не зависит */
|
||
atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1;
|
||
bg_set = set;
|
||
pop_palace = (uint8_t)(set == 1);
|
||
mob_spr_build(); /* композит падающего куска — свой на набор */
|
||
/* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от
|
||
* прежнего набора и картинка хотя бы не разъезжается. */
|
||
return bg_load_tile_pal(pop_tile_pal[set]);
|
||
}
|
||
|
||
/* Пере-применить палитру ТЕКУЩЕГО набора. Нужна отдельно от pop_bg_load
|
||
* из-за порядка старта: атласы грузятся ДО initgraph (тот снимает
|
||
* DSS-страницу W0), а общая игровая палитра kid.pal читается ПОСЛЕ него и
|
||
* затирает слоты 0x50..0x6F — она собрана из VDUNGEON
|
||
* (pop_pack_kid.py build_palette). Звать сразу за gfx_pal_fload. */
|
||
int pop_bg_pal_apply(void) __banked
|
||
{
|
||
if (bg_set == 0xFF) return -1;
|
||
return bg_load_tile_pal(pop_tile_pal[bg_set]);
|
||
}
|
||
|
||
void pop_bg_free(void) __banked
|
||
{
|
||
int i;
|
||
if (bg_set == 0xFF) return;
|
||
for (i = 0; i < POP_ENV_PAGES; i++)
|
||
atlas_free(&pop_env[i]);
|
||
atlas_free(&pop_wall_at);
|
||
atlas_free(&pop_fore_at);
|
||
atlas_free(&pop_pot_at);
|
||
bg_set = 0xFF;
|
||
}
|
||
|
||
static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0};
|
||
|
||
/* ---- Чомпер (seg008.c:0B2B draw_tile_anim, ветка tiles_18_chomper) ---- *
|
||
* Кадр берётся НЕ прямо из modif: chomper_fram1[min(modif & 0x7F, 6)] даёт
|
||
* номер позы 0..4, и уже он индексирует три набора кусков — нижнюю челюсть
|
||
* (backtable), верхнюю (backtable, со своим подъёмом) и передний слой
|
||
* (foretable, поверх персонажа). Кровь — тот же номер позы + 114 (back) /
|
||
* +119 (fore), рисуется mono-силуэтом цветом 12; в атласе она уже такой
|
||
* (pop_pack_bg.py, 2026-08-07).
|
||
* Старший бит modif (0x80) = «в этом чомпере кого-то перемололо».
|
||
*
|
||
* Номер позы (pop_chomp_pose) и передние куски живут в РЕЗИДЕНТЕ
|
||
* (pop_tile.c): их читает ещё и fore-проход из горячей половины, а
|
||
* const-таблицы банка из чужого банка не видны. */
|
||
static const uint8_t CHOMP_FRAM_BOT[5] = {101, 102, 103, 104, 105};
|
||
static const uint8_t CHOMP_FRAM_TOP[5] = {0, 0, 111, 112, 113};
|
||
static const uint8_t CHOMP_FRAM_Y[5] = {0, 0, 0x25, 0x2F, 0x32};
|
||
|
||
static const uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126};
|
||
static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0};
|
||
static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45};
|
||
|
||
/* ---- Зелье (tiles_10_potion, seg008 draw_tile_anim/draw_tile_fore) ----
|
||
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
|
||
* склянка (id 13) и pot_size=1, остальные — маленькая (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; пузырёк/маска xh = draw_xh + 3, xl = 1,
|
||
* ybottom = draw_main_y − (pot_size << 2) − 14. */
|
||
#define POT_MASK_ID 23
|
||
static const uint8_t POTION_FRAM_BUBB[8] = {0, 16, 17, 18, 19, 20, 21, 22};
|
||
|
||
static uint8_t potion_type(uint8_t modif) { return (uint8_t)((modif & 0xF8) >> 3); }
|
||
|
||
|
||
|
||
/* Дворец? — по ЗАГРУЖЕННОМУ набору (pop_palace ставит pop_bg_load), а не по
|
||
* номеру уровня: так ветки отрисовки и картинки заведомо согласованы даже в
|
||
* момент смены. */
|
||
#define PALACE (pop_palace)
|
||
|
||
/* Декоративная панель над воротами — ТОЛЬКО в паласе (в подземелье над
|
||
* воротами не рисуется ничего). Индекс — модификатор тайла doortop.
|
||
* seg008:407 doortop_fram_top / seg008:453 doortop_fram_bot. */
|
||
static const uint8_t DOORTOP_FRAM_TOP[4] = {0, 81, 83, 0};
|
||
static const uint8_t DOORTOP_FRAM_BOT[4] = {78, 80, 82, 0};
|
||
|
||
/* Верх ворот: его рисует тайл НАД воротами (draw_tile_anim_topright,
|
||
* seg008:0568). Кадр — по открытости соседа снизу-слева; 68 — маска,
|
||
* mono-блит чёрным «продавливает» фон под створкой.
|
||
* Без этого над воротами оставался чёрный клин — виден у шва во дворце,
|
||
* где сверху идёт декоративная панель doortop. */
|
||
static const uint8_t DOOR_FRAM_TOP[8] = {60, 61, 62, 63, 64, 65, 66, 67};
|
||
#define GATE_TOP_MASK 68
|
||
|
||
/* topright тайла снизу-слева (seg008.c:410). */
|
||
static void draw_topright(uint8_t tt, uint8_t tmod, int x, int dby)
|
||
{
|
||
if (tt == 7 || tt == 12) { /* верх ворот */
|
||
if (!PALACE) return; /* подземелье: ничего (seg008:413) */
|
||
if (DOORTOP_FRAM_TOP[tmod & 3])
|
||
pop_env_b(DOORTOP_FRAM_TOP[tmod & 3], x, dby);
|
||
return;
|
||
}
|
||
if (tt == 20) { pop_wall_b(2, x, dby); return; }
|
||
if (pop_tile_table[tt].topright_id)
|
||
pop_env_b(pop_tile_table[tt].topright_id, x, dby);
|
||
}
|
||
|
||
|
||
/* ---- Loose floors: кадры дрожащей плиты (seg008 loose_fram_*) ------- *
|
||
* Состояние pop_loose_modif[] живёт в pop_map. draw_tile рисует грани
|
||
* loose АНИМИРОВАННО по modif (как SDLPoP: сам тайл — лево/низ, ПРАВЫЙ
|
||
* СОСЕД — правую грань через draw_tile_anim_right). При modif==0 (покой/
|
||
* статич. запекание) get_loose_frame=0 → базовые кадры 41/42/43. */
|
||
static const uint8_t LOOSE_FRAM_RIGHT[12] = {42,71,42,72,72,42,42,42,72,72,72,0};
|
||
|
||
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
|
||
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры
|
||
* ЧУЖИХ плит рисовать НЕЛЬЗЯ: транзиентный кадр законсервируется в копии, а
|
||
* так как запечка идёт двумя кадрами (по одному на страницу дабл-буфера),
|
||
* на страницах застынут РАЗНЫЕ кадры — вечное мерцание. Пример: упала плита
|
||
* (0,6); её запечка перерисовывает и соседа, а правый угол СОСЕДНЕЙ плиты
|
||
* (0,5), который рисуется в ячейке (0,6), был в этот момент в тряске.
|
||
* Фон обязан быть состоянием ПОКОЯ — анимация каждый кадр кладётся поверх
|
||
* (банк SPRITE, только видео-ОЗУ). */
|
||
/* draw_leveldoor (seg008:1D29) — дверь уровня. Рисуется при обработке
|
||
* ПРАВОЙ половины (tile_left = 0x10), все куски со сдвигом +8px (xh+1):
|
||
* 99 низ лестницы (всегда),
|
||
* 144 марш лестницы за створкой (когда створка приподнята, modif != 0),
|
||
* 33 слайс створки — повторяется вниз с шагом 4px, пока не дойдёт до
|
||
* y = ybottom − modif: modif 0 = закрыто (створка на всю высоту),
|
||
* 43 = открыто (остаётся только верхняя кромка),
|
||
* 34 верх коробки двери.
|
||
* Куски непрозрачные (blitters_0_no_transp) — как в оригинале. */
|
||
static void draw_leveldoor(uint8_t modif, int xh, int dmy)
|
||
{
|
||
int x = (xh + 1) * 8;
|
||
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;
|
||
pop_env_b(99, x, ybottom);
|
||
if (modif)
|
||
pop_env_b(144, x, ybottom - 4); /* лестница (комната ≠ стартовой) */
|
||
for (;;) {
|
||
pop_env_b(33, x, ylo);
|
||
if (y > ylo) ylo += 4;
|
||
else break;
|
||
}
|
||
/* ИМЕННО после цикла: в оригинале clip_char читает уже доведённое
|
||
* значение (низ поднятой створки), а не стартовое. */
|
||
pop_leveldoor_ybottom = ylo;
|
||
pop_env_b(34, x, dmy - 64);
|
||
}
|
||
|
||
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
|
||
/* ---- КОНТЕКСТ ТАЙЛА (порт load_curr_and_left_tile, seg008:0339) -------
|
||
* Не локали draw_tile, а file-scope переменные — ровно как curr_tile /
|
||
* curr_modifier / draw_xh / draw_main_y / draw_bottom_y у оригинала.
|
||
*
|
||
* Зачем: в draw_tile 57 вызовов, и КАЖДОЕ значение, живое через вызов, SDCC
|
||
* спиливал в стековый кадр — получались 26 байт кадра и 513 обращений
|
||
* `-N(ix)` (замер 2026-08-17: 23 700 тактов накладных на вызов при одном
|
||
* блите на тайл, docs/perf_green_phase.md §1.6). У file-scope переменных
|
||
* абсолютная адресация (`ld a,(nn)` — 13 тактов против 19) и спиливать
|
||
* нечего. Функция от этого не становится реентерабельной, но и не была:
|
||
* draw_tile ниоткуда себя не зовёт. */
|
||
static int dt_row, dt_col;
|
||
static uint8_t dt_code, dt_lcode, dt_lmod;
|
||
static int dt_xh, dt_x, dt_dby, dt_dmy;
|
||
static const piece *dt_t, *dt_lt;
|
||
static uint8_t dt_rbl, dt_rblm, dt_wmod, dt_base_id;
|
||
static int dt_base_yb;
|
||
|
||
static void draw_tile(int row, int col)
|
||
{
|
||
dt_row = row; dt_col = col;
|
||
dt_code = pop_tile_code(dt_row, dt_col);
|
||
dt_lcode = pop_tile_code(dt_row, dt_col - 1);
|
||
dt_lmod = pop_tile_mod(dt_row, dt_col - 1);
|
||
/* Нажатая кнопка (seg008 get_tile_to_draw): при таймере связи >1 opener
|
||
* (0x0F) рисуется как ПОЛ (modif 0), closer (0x06) — как stuck (0x05).
|
||
* Модификатор кнопки = индекс LINKLOC. И для самого тайла, и для левого
|
||
* соседа (его правая грань). */
|
||
if (dt_code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(dt_row, dt_col)) & 0x1F) > 1) dt_code = 1; }
|
||
else if (dt_code == 0x06) { if ((pop_doorlink2(pop_tile_mod(dt_row, dt_col)) & 0x1F) > 1) dt_code = 5; }
|
||
if (dt_lcode == 0x0F) { if ((pop_doorlink2(dt_lmod) & 0x1F) > 1) { dt_lcode = 1; dt_lmod = 0; } }
|
||
else if (dt_lcode == 0x06) { if ((pop_doorlink2(dt_lmod) & 0x1F) > 1) dt_lcode = 5; }
|
||
dt_xh = POP_COL_XH[dt_col];
|
||
dt_x = dt_xh * 8;
|
||
dt_dby = 63 * dt_row + 65;
|
||
dt_dmy = dt_dby - 3;
|
||
dt_t = &pop_tile_table[dt_code];
|
||
dt_lt = &pop_tile_table[dt_lcode];
|
||
|
||
/* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0
|
||
* комнаты снизу (load_rowbelow, seg008:368): row_below_left_[dt_col] =
|
||
* room_B(0, dt_col-1); dt_col-1<0 → левая комната снизу = стена уровня. */
|
||
if (dt_row + 1 > 2) {
|
||
int lc = dt_col - 1;
|
||
/* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате
|
||
* (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда;
|
||
* «стена» там стоит, только если такой комнаты нет — как в
|
||
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
|
||
if (lc < 0) dt_rbl = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20;
|
||
else dt_rbl = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 0;
|
||
/* Модификатора ряда снизу у нас нет (pop_t_below — только fg), а он
|
||
* нужен паласной панели над воротами. Ставим 0 = «панели нет»:
|
||
* случай узкий (ворота в комнате СНИЗУ, по диагонали от пустого
|
||
* тайла нижнего ряда), но расхождение с оригиналом честное. */
|
||
dt_rblm = 0;
|
||
} else {
|
||
dt_rbl = pop_tile_code(dt_row + 1, dt_col - 1);
|
||
dt_rblm = pop_tile_mod(dt_row + 1, dt_col - 1);
|
||
}
|
||
|
||
/* Пометка «фон трогали» — ОДНА на весь тайл, а не на каждый его кусок
|
||
* (pop_cd_batch_begin/end). Досрочных выходов у draw_tile нет, так что
|
||
* скобка гарантированно закрывается. */
|
||
pop_cd_batch_begin();
|
||
|
||
/* --- draw_tile_anim_topright (seg008:0568): верх ВОРОТ снизу-слева.
|
||
* Условие дословно: свой тайл пуст / верх большой колонны / верх двери,
|
||
* а снизу-слева — ворота. --- */
|
||
if ((dt_code == 0 || dt_code == 9 || dt_code == 12) && dt_rbl == 4) {
|
||
uint8_t om = (dt_rblm > 188) ? 188 : dt_rblm;
|
||
pop_env_b(GATE_TOP_MASK, dt_x, dt_dby); /* маска (mono, чёрным) */
|
||
pop_env_b(DOOR_FRAM_TOP[(om >> 2) & 7], dt_x, dt_dby);
|
||
}
|
||
|
||
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
|
||
if (dt_code == 0 || dt_code == 9 || dt_code == 12 || dt_code == 26) {
|
||
draw_topright(dt_rbl, dt_rblm, dt_x, dt_dby);
|
||
/* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op
|
||
* (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет
|
||
* floor_right: dt_lt->floor_right. */
|
||
}
|
||
|
||
/* --- draw_tile_right (seg008.c:456) --- */
|
||
if (dt_code != 20) {
|
||
if (dt_lcode == 20) {
|
||
/* Полоска на стене (seg008:510) — только палас и только если у
|
||
* стены не взведён старший бит модификатора. */
|
||
if (PALACE && (dt_lmod & 0x80) == 0)
|
||
pop_env_b(84, dt_x + 3 * 8, dt_dmy - 27);
|
||
pop_wall_b(1, dt_x, pop_tile_table[20].right_y + dt_dmy); /* грань стены */
|
||
} else if (dt_lcode == 0) {
|
||
if (dt_lmod <= 3 && BLUELINE_FRAM1[dt_lmod])
|
||
pop_env_b(BLUELINE_FRAM1[dt_lmod], dt_x, BLUELINE_FRAM_Y[dt_lmod] + dt_dmy);
|
||
} else if (dt_lcode == 1) {
|
||
uint8_t num;
|
||
pop_env_b(42, dt_x, pop_tile_table[1].right_y + dt_dmy); /* правый треуг. пола */
|
||
num = (dt_lmod <= 3) ? dt_lmod : 0;
|
||
/* seg008:501 — `if (num == !!level_type) return;`: в подземелье
|
||
* пропускается num==0, в паласе num==1. */
|
||
if (num != (PALACE ? 1 : 0))
|
||
pop_env_b(BLUELINE_FRAM3[num], dt_x, dt_dmy - 20); /* силуэт кладки */
|
||
} else if (dt_lcode == 7 || dt_lcode == 12) {
|
||
/* Низ декоративной панели над воротами (seg008:506) — только
|
||
* палас; в подземелье у doortop right_id нулевой и ветка пустая. */
|
||
if (PALACE && DOORTOP_FRAM_BOT[dt_lmod & 3])
|
||
pop_env_b(DOORTOP_FRAM_BOT[dt_lmod & 3], dt_x, dt_lt->right_y + dt_dmy);
|
||
} else {
|
||
if (dt_lt->right_id)
|
||
pop_env_b(dt_lt->right_id, dt_x, dt_lt->right_y + dt_dmy);
|
||
/* Орнаментная лента соседа (seg008:486) — только палас. Она и
|
||
* даёт непрерывную полосу под окнами: stripe_id = 145 у пола,
|
||
* кнопок, зелья, loose, чомпера и меча. */
|
||
if (PALACE && dt_lt->stripe_id)
|
||
pop_env_b(dt_lt->stripe_id, dt_x, dt_dmy - 27);
|
||
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
|
||
if (dt_lcode == 19 || dt_lcode == 30)
|
||
pop_env_b(146, dt_x, dt_dby - 28);
|
||
}
|
||
}
|
||
|
||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
|
||
* верхней полосы (dt_row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
||
* отрицательного индекса в pop_loose_modif. --- */
|
||
if (dt_row >= 0) {
|
||
if (dt_lcode == 4) { /* gate: решётка */
|
||
pop_gate_back_b(dt_lmod, dt_xh, dt_dby, dt_dmy);
|
||
} else if (dt_lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[dt_row * 10 + (dt_col - 1)])], dt_x, dt_dby - 1);
|
||
} else if (dt_lcode == 2) { /* spike: статич. кадр */
|
||
uint8_t sf = (dt_lmod & 0x80) ? 5 : dt_lmod;
|
||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||
pop_env_b(SPIKES_FRAM_RIGHT[sf], dt_x, dt_dmy - 7);
|
||
} else if (dt_lcode == 16) { /* дверь уровня */
|
||
draw_leveldoor(dt_lmod, dt_xh, dt_dmy);
|
||
}
|
||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) здесь, в статичной
|
||
* отрисовке тайла, не рисуем — его ведёт pop_torch_draw из
|
||
* pop_process_trobs каждый кадр. Но ЗАПЕКАЕТСЯ оно всё равно (там
|
||
* GFX_BANK_NORMAL): факел — чистый задний план, а самонакрытие кадров
|
||
* делает запекание безопасным. Подробности — в шапке pop_torch_draw. */
|
||
}
|
||
|
||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||
if (dt_code == 20) {
|
||
dt_wmod = pop_wall_modifier(dt_row, dt_col);
|
||
if (!PALACE) /* во дворце тело стены даёт */
|
||
pop_wall_b(POP_WALL_FRAM_BOTTOM[dt_wmod], dt_x, dt_dby); /* wall_pattern */
|
||
/* заливками (seg008:576) */
|
||
pop_wall_pattern_b(dt_row, dt_col, 0);
|
||
} else if (dt_t->bottom_id) {
|
||
pop_env_b(dt_t->bottom_id, dt_x, dt_dby);
|
||
}
|
||
|
||
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
|
||
* верхней полосе (dt_row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
|
||
* её состояние живёт отдельно, в pop_ceil_modif[dt_col] (pop_map). --- */
|
||
if (dt_code == 11)
|
||
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(dt_row >= 0 ? pop_loose_modif[dt_row * 10 + dt_col]
|
||
: pop_ceil_modif[dt_col])], dt_x, dt_dby);
|
||
|
||
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (dt_row<0) базу НЕ
|
||
* рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */
|
||
if (dt_row >= 0) {
|
||
dt_base_id = dt_t->base_id;
|
||
dt_base_yb = dt_dmy;
|
||
if (dt_lcode == 26 && dt_code == 12) {
|
||
/* «Lattice + door A» (seg008:622): у doortop своей базы нет
|
||
* (dt_base_id 0), и когда слева от него стоит арка (lattice_down),
|
||
* пара рисуется ОДНИМ спрайтом 6, опущенным на 3 px. Без этой
|
||
* ветки на месте тайла оставалась чёрная дыра — уровень 5,
|
||
* комната 12, тайл (0,9) (BUG-LATTICE-DOORTOP). */
|
||
dt_base_id = 6;
|
||
dt_base_yb += 3;
|
||
}
|
||
else if (dt_code == 11) /* loose: левая грань (анимир.) */
|
||
dt_base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[dt_row * 10 + dt_col])];
|
||
else if (dt_code == 0x0F && dt_lcode == 0 && !PALACE) /* opener, слева пусто */
|
||
dt_base_id = 148; /* левая половина без пола — только */
|
||
/* подземелье (seg008:628) */
|
||
if (dt_base_id)
|
||
pop_env_b(dt_base_id, dt_x, dt_t->base_y + dt_base_yb);
|
||
}
|
||
|
||
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
|
||
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
|
||
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
||
* пустая. */
|
||
if (dt_row >= 0 && dt_code == 2) {
|
||
uint8_t sm = pop_tile_mod(dt_row, dt_col);
|
||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
||
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], dt_x, dt_dmy - 2);
|
||
}
|
||
/* Чомпер (тот же draw_tile_anim, case tiles_18_chomper): нижняя челюсть
|
||
* на draw_main_y, поверх неё кровь (если кого-то перемололо), затем
|
||
* верхняя челюсть со своим подъёмом. Всё это BACKtable — то есть ПОД
|
||
* персонажем; передний слой рисует fore-проход. */
|
||
if (dt_row >= 0 && dt_code == 0x12) {
|
||
uint8_t cm = pop_tile_mod(dt_row, dt_col);
|
||
uint8_t pose = pop_chomp_pose(cm);
|
||
pop_env_b(CHOMP_FRAM_BOT[pose], dt_x, dt_dmy);
|
||
if (cm & 0x80)
|
||
pop_env_b((uint8_t)(pose + 114), dt_x + 8, dt_dmy - 6);
|
||
if (CHOMP_FRAM_TOP[pose])
|
||
pop_env_b(CHOMP_FRAM_TOP[pose], dt_x, dt_dmy - CHOMP_FRAM_Y[pose]);
|
||
}
|
||
/* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword):
|
||
* chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3.
|
||
* В оригинале это midtable (рисуется вместе с объектами) — у нас статикой
|
||
* в фоне: пока меч лежит, он не анимируется, а Kid и так поверх фона. */
|
||
if (dt_row >= 0 && dt_code == 22)
|
||
pop_pot_b((uint8_t)((pop_tile_mod(dt_row, dt_col) == 1) + 10), dt_x, dt_dmy - 3);
|
||
|
||
/* --- draw_tile_fore (seg008.c:690) --- */
|
||
if (dt_code == 20) {
|
||
dt_wmod = pop_wall_modifier(dt_row, dt_col);
|
||
if (!PALACE) /* seg008:711 */
|
||
pop_wall_b(POP_WALL_FRAM_MAIN[dt_wmod], dt_x, dt_dmy); /* основная грань */
|
||
pop_wall_pattern_b(dt_row, dt_col, 1);
|
||
}
|
||
if (dt_code == 0x12) { /* чомпер: передние зубья + кровь.
|
||
* Отдельной веткой, как в оригинале:
|
||
* у записи 0x12 fore_id == 0, и через
|
||
* таблицу этот слой не идёт. */
|
||
uint8_t cm = pop_tile_mod(dt_row, dt_col);
|
||
uint8_t pose = pop_chomp_pose(cm);
|
||
/* Передние куски чомпера и его кровь лежат в pop_fore.atl
|
||
* (FORE_ENV_IDS в pop_pack_bg.py), а НЕ в env-страницах: блит
|
||
* обязан быть pop_fore_b, иначе id уходит в чужую страницу. */
|
||
pop_fore_b(POP_CHOMP_FRAM_FOR[pose], dt_x, dt_dmy);
|
||
if (cm & 0x80)
|
||
pop_fore_b((uint8_t)(pose + 119), dt_x + 8, dt_dmy - 6);
|
||
}
|
||
if (dt_code == 10) { /* зелье: chtab_1, не env fore_id.
|
||
* Пузырёк — динамика (pop_potion_draw). */
|
||
pop_potion_flask(dt_row, dt_col, pop_tile_mod(dt_row, dt_col));
|
||
} else if (dt_t->fore_id) {
|
||
pop_fore_b(dt_t->fore_id, dt_t->fore_x * 8 + dt_x, dt_t->fore_y + dt_dmy);
|
||
}
|
||
pop_cd_batch_end(); /* одна пометка на весь тайл */
|
||
}
|
||
|
||
|
||
/* ---- Loose floors — динамическая перерисовка (seg008 draw_loose) ---- *
|
||
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
|
||
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
|
||
* внутри draw_tile). */
|
||
void pop_loose_shake_draw(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
|
||
* перерисовка обоих тайлов с дрожащим кадром (draw_tile берёт кадр из
|
||
* pop_loose_modif). Порт redraw_tile_height (loose + правый сосед).
|
||
* NB: печёный кадр-0 может слегка просвечивать по кромкам — чистое
|
||
* решение (bake тайла как empty + рисовать loose всегда динамически)
|
||
* отложено; wipe-в-чёрный делал плиту невидимой — не годится. */
|
||
pop_heal_off(x, 63 * row + 46, 64, 24);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
draw_tile(row, col); /* loose: лево+низ (анимир.) */
|
||
if (col + 1 < 10)
|
||
draw_tile(row, col + 1); /* сосед: правая грань loose (анимир.) + его пол */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Перерисовать пики тайла (row,col) на back-странице по ЖИВОМУ modif
|
||
* (pop_trob). Кадр выдвижения рисует ПРАВЫЙ сосед (draw_tile ветка lcode==2
|
||
* читает lmod = pop_t_bg[row*10+col] = живой modif пики). pop_t_bg должен указывать
|
||
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
|
||
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
||
void pop_spike_redraw(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
|
||
* redraw ДВУХ тайлов — своего и правого соседа (остриё рисуется в его
|
||
* ячейке). Именно ПОЛНЫЙ: рисовать только два спрайта остриёв нельзя —
|
||
* порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и
|
||
* fore соседа ложатся ПОВЕРХ) тогда теряется, и остриё лезет на колонну
|
||
* справа. Заодно draw_tile сам восстанавливает вклад соседних пик:
|
||
* в ячейке col — правое остриё пики col-1 (ветка lcode==2).
|
||
* Шипы выдвигаются выше линии пола (dmy-7), поэтому heal с запасом вверх
|
||
* (63*row+34 .. +74); ширина 64 = обе ячейки. */
|
||
pop_heal_off(x, 63 * row + 34, 64, 40);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
draw_tile(row, col);
|
||
if (col + 1 < 10)
|
||
draw_tile(row, col + 1);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Чомпер: перерисовка кадра анимации (порт redraw_20h/draw_trob). Челюсти
|
||
* поднимаются высоко над линией пола (верхний кусок уходит на 0x32 = 50 px
|
||
* вверх от dmy), поэтому heal берём на всю высоту тайла. Правый сосед НЕ
|
||
* нужен: в отличие от пик, все куски чомпера рисуются в СВОЕЙ ячейке. */
|
||
void pop_chomp_redraw(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
pop_heal_off(x, 63 * row + 2, 32, 64);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
draw_tile(row, col);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа —
|
||
* ворота соседней комнаты слева (portcullis room8) видны в col0 текущей
|
||
* комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = pop_t_lbg[0]).
|
||
* pop_t_lbg (lcol_bg) должен указывать на живой modif (roomtest обновляет из
|
||
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
||
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
||
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
||
void pop_room_redraw_seam_left(uint8_t rows) __banked
|
||
{
|
||
int8_t r;
|
||
/* rows — битовая маска рядов 0..2: перерисовываем ТОЛЬКО те, где openness
|
||
* реально изменился. Раньше ряд был жёстко нулевым, потому что на уровне 1
|
||
* решётка соседа стояла в (0,9); на уровне 4 она в (1,9), и открытые ворота
|
||
* оставались нарисованными закрытыми (BUG-GATE-SEAM-ROW1). */
|
||
for (r = 0; r < 3; r++) {
|
||
int yt = POP_YOFF + 63 * r;
|
||
if (!(rows & (1 << r))) continue;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
/* Полосу кладки НАД рядом НЕ трогаем: её низ на room-space 63*r+2, а
|
||
* бары ворот начинаются с 63*r+3 (gate_top_y = dby−62). Раньше bar шёл
|
||
* от POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать
|
||
* ещё и draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации. */
|
||
pop_bar_black(0, yt + 3, 32, 62); /* col0 этого ряда (бары ворот) */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
draw_tile(r, 0); /* фон col0 + бары ворот (живой modif) */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
}
|
||
|
||
/* ---- ЗАПЕЧКА НА ВТОРУЮ СТРАНИЦУ — копией, а не пересчётом ----------- *
|
||
* Точечные запечки ставятся с pages = 2, то есть срабатывают ДВА кадра подряд
|
||
* — по разу на каждую страницу дабл-буфера, — и оба раза считают одно и то же.
|
||
* А после первого раза нужный прямоугольник уже лежит в ОЗУ-копии первой
|
||
* страницы, и его можно просто скопировать: gfx_copy_page берёт источником
|
||
* ОЗУ-копию НЕактивной страницы (то есть ЧИСТЫЙ фон — спрайты рисуются банком
|
||
* без тени и в копию не попадают), а приёмник обновляет и в видео-ОЗУ, и в
|
||
* ОЗУ-копии.
|
||
*
|
||
* Замер 2026-08-17: полная запечка щебня — 179 914 такта, колодца — 138 318;
|
||
* копия прямоугольника 60x39 стоит ~30 000, а 64x9 — ~14 000.
|
||
*
|
||
* Слот на тайл помнит, НА КАКОЙ странице сделана первая запечка (+1; 0 = не
|
||
* было). Копируем только если первая была на ДРУГОЙ странице и дабл-буфер
|
||
* включён; иначе честно пересчитываем. Такая проверка выдерживает и
|
||
* переплетение двух запечек в одном кадре, и отключённый дабл-буфер (чит
|
||
* SPACE), и смену комнаты между кадрами. */
|
||
static uint8_t bake_pg[30], bake_pg_above[10];
|
||
|
||
static uint8_t bake_copy(uint8_t *slot, int x, int ytop, int w, int h)
|
||
{
|
||
uint8_t pg = (uint8_t)(gfx_get_draw_page() & 1);
|
||
gfx_rect_t r;
|
||
if (!*slot || (uint8_t)(*slot - 1) == pg ||
|
||
gfx_get_draw_page() == gfx_get_visible_page()) {
|
||
*slot = (uint8_t)(pg + 1); /* это ПЕРВАЯ запечка */
|
||
return 0;
|
||
}
|
||
*slot = 0;
|
||
if (pop_upside) ytop = POP_FLIP_TOP(ytop, h); /* копируем ЭКРАННЫЙ прямоугольник */
|
||
r.x = (int16_t)x; r.y = (int16_t)ytop;
|
||
r.w = (uint16_t)w; r.h = (uint16_t)h;
|
||
gfx_copy_page(&r, GFX_COPY_DIRECT);
|
||
pop_cd_touch(x, ytop, w, h); /* область изменилась и на этой странице */
|
||
return 1;
|
||
}
|
||
|
||
/* Перепометка тайла: содержимое могло измениться (кнопка с идущим таймером
|
||
* перерисовывается каждый кадр РАЗНОЙ), поэтому копия со второй страницы
|
||
* больше не годится — гасим слот, следующая запечка пойдёт честно.
|
||
* Контракт и мотив — в _pop_bg.h. */
|
||
void pop_bake_slot_reset(uint8_t tilepos)
|
||
{
|
||
if (tilepos < 30) bake_pg[tilepos] = 0;
|
||
}
|
||
|
||
void pop_bake_slot_reset_above(uint8_t col)
|
||
{
|
||
if (col < 10) bake_pg_above[col] = 0;
|
||
}
|
||
|
||
/* Смена комнаты/уровня: запечки прошлой комнаты недоделанными не остаются
|
||
* (pop_trob_room_changed сбрасывает пометки), но слоты обнулить надо — иначе
|
||
* первый же тайл с тем же номером в новой комнате получит копию вместо
|
||
* честной запечки. */
|
||
void pop_bake_forget(void) __banked
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < 30; i++) bake_pg[i] = 0;
|
||
for (i = 0; i < 10; i++) bake_pg_above[i] = 0;
|
||
}
|
||
|
||
/* ---- Плита-потолок (loose ряда 2 комнаты СВЕРХУ) ------------------- *
|
||
* В полосе кладки у потолка (наш ряд −1) виден только НИЗ плиты
|
||
* (draw_tile_aboveroom: bottom+loose, всё выше отрезано клипом POP_YOFF).
|
||
* Дрожащий кадр берётся из pop_ceil_modif[col] (см. draw_loose выше).
|
||
* shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту
|
||
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
|
||
|
||
/* Полоса кладки у потолка: дрожащая плита ряда −1 — порт redraw_needed_above
|
||
* (seg008:0221).
|
||
*
|
||
* Оригинал рисует ТОЛЬКО СВОЮ колонку: кусок, свисающий вправо, кладёт её же
|
||
* draw_tile_right. Здесь дополнительно звался `draw_tile(-1, col + 1)` —
|
||
* целый соседний тайл, — и это удваивало работу. Замер 2026-08-13: пик
|
||
* зелёного блока 887 000 тактов при кадровом интервале 430 000 состоял ровно
|
||
* из шести вызовов этой функции, то есть двенадцати отрисовок тайла.
|
||
*
|
||
* Батчинг смежных колонок (один heal на диапазон) пробовался и убран: плиты
|
||
* стартуют со случайными задержками (check_fall_flo), в кадре дрожат
|
||
* разрозненные колонки, пробег почти всегда длиной в одну — механика не
|
||
* окупалась, но крутилась каждый кадр. */
|
||
void pop_ceil_shake_draw(int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
pop_heal_off(x, 0, 64, CEIL_BAND_H);
|
||
/* Пометка «фон трогали» — ОДНА на восстановленный прямоугольник: пока
|
||
* стоит окно (ниже), pop_blit_b пометок не ставит. Область та же, что у
|
||
* heal'а, — он её и стирает. */
|
||
pop_cd_touch(x, POP_YOFF, 64, CEIL_BAND_H);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
pop_t_clip_top = POP_YOFF;
|
||
pop_t_win_set(x, POP_YOFF, 64, CEIL_BAND_H); /* восстанавливаем ровно полосу */
|
||
draw_tile(-1, col);
|
||
pop_t_win_clear();
|
||
pop_t_clip_top = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
void pop_ceil_bake_empty(int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
/* Вторая страница — копией с первой (см. bake_copy). */
|
||
if (bake_copy(&bake_pg_above[col], x, POP_YOFF, 64, CEIL_BAND_H + 1)) return;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
/* Отдельного pop_cd_touch тут не надо: pop_bar_black метит сам, и ровно
|
||
* этот же прямоугольник (было два вызова на одну область). */
|
||
pop_bar_black(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* стереть плиту в ОЗУ-копии */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_t_clip_top = POP_YOFF;
|
||
/* Окно = ровно стёртая полоса. Главный выигрыш здесь не в отсеве, а в
|
||
* КЛИПЕ: куски ряда 0 высотой 63 px рисовались целиком (клип был только
|
||
* сверху, по pop_t_clip_top), хотя восстановить надо девять строк —
|
||
* отсюда 21 447 такта на блит против 14 384 в pop_floor_bake. */
|
||
pop_t_win_set(x, POP_YOFF, 64, CEIL_BAND_H + 1);
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
||
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
||
* нижней грани и свисают ВПРАВО, поэтому в полосу над тайлом col лезут:
|
||
* • ряд −1, колонки col−1..col+1 — сама полоса кладки у потолка;
|
||
* • ряд 0, колонки col−1..col+1 — их верхушки заходят в те же 8 px.
|
||
* Раньше перерисовывались только (−1,col) и (−1,col+1), и в полосе
|
||
* оставался чёрный прямоугольник ~13×3 px (замер: экранные y 47..52 при
|
||
* полосе 44..52), который heal возвращал каждый кадр. Признак, что дело
|
||
* именно в запечке: после выхода из комнаты и возврата бара нет. */
|
||
{
|
||
int c0 = (col > 0) ? col - 1 : 0;
|
||
int c1 = (col + 1 < 9) ? col + 1 : 9;
|
||
int c;
|
||
for (c = c0; c <= c1; c++) draw_tile(-1, c);
|
||
for (c = c0; c <= c1; c++) draw_tile(0, c);
|
||
}
|
||
pop_t_bake_rest = 0;
|
||
pop_t_win_clear();
|
||
pop_t_clip_top = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* ---- Анимация факела и зелья (динамика, каждый кадр) ---------------- *
|
||
* Оба кадра рисуются ПОВЕРХ статического фона и стираются heal'ом своей
|
||
* области (фон запечён БЕЗ них). Факел: пламя chtab_1 id modifier+1 в
|
||
* ячейке ПРАВОГО соседа (seg008:560: xh = draw_xh + 1, ybottom =
|
||
* draw_main_y − 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
|
||
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y −
|
||
* (pot_size<<2) − 14 (seg008:668). */
|
||
void pop_torch_draw(int row, int col, uint8_t modif) __banked
|
||
{
|
||
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = POP_COL_XH[col+1] + 1
|
||
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
|
||
* 146), которое тоже лежит в ячейке соседа. */
|
||
int x = (col + 1 < 10 ? POP_COL_XH[col + 1] : POP_COL_XH[col] + 4) * 8 + 8;
|
||
int yb = 63 * row + 62 - 40;
|
||
if (modif >= 9) return;
|
||
/* heal НЕ нужен: оригинал рисует пламя blitters_0_no_transp (seg008
|
||
* draw_tile_anim_right), и все 9 кадров лежат на общем канвасе 16x18 —
|
||
* непрозрачный блит сам полностью накрывает предыдущий кадр. Кадры
|
||
* упакованы с opaque=True (pop_pack_bg POT_FLAME_IDS).
|
||
*
|
||
* И ровно поэтому пламя ЗАПЕКАЕТСЯ (GFX_BANK_NORMAL: видео-ОЗУ + ОЗУ-копия),
|
||
* а не рисуется в SPRITE поверх фона. Запечь его безопасно именно из-за
|
||
* самонакрытия — следующий кадр сотрёт предыдущий сам, «протухнуть» в
|
||
* копии нечему. А выигрыш принципиальный: точечные перерисовки соседних
|
||
* тайлов начинаются с heal из ОЗУ-копии, и незапечённое пламя они стирали
|
||
* — чомпер (0,7) комнаты 23 уровня 4 healит x 224..255 / y 30..93 и
|
||
* съедал факел (0,6), который рисуется в ячейке правого соседа. С
|
||
* запеканием heal возвращает огонь сам, а челюсти ложатся ПОВЕРХ него —
|
||
* тот же z-порядок, что у оригинала, где draw_tile_anim_right (пламя
|
||
* левого соседа) идёт до draw_tile_anim (сам чомпер).
|
||
*
|
||
* Пузырёк ЗЕЛЬЯ так нельзя: он ползёт вверх и себя не накрывает, ему
|
||
* нужен heal — pop_potion_draw остаётся в SPRITE. */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_pot_b((uint8_t)(modif + 1), x, yb);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
void pop_potion_draw(int row, int col, uint8_t modif) __banked
|
||
{
|
||
uint8_t type = potion_type(modif);
|
||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
||
int x = (POP_COL_XH[col] + 3) * 8 + 1;
|
||
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
||
uint8_t bubb;
|
||
if (type == 0) return;
|
||
bubb = POTION_FRAM_BUBB[modif & 7];
|
||
/* Цвет пузырька по ТИПУ зелья (draw_tile_anim, seg008:652): 3/4 (перо /
|
||
* переворот) — зелёный, 5/6 (вред / открыть ворота) — синий, остальные
|
||
* (1 лечение, 2 склянка жизни) — красный. У нас цвет запечён в спрайт
|
||
* при упаковке (mono-блиттера с параметром цвета нет), поэтому «цвет» =
|
||
* другой набор кадров: id 30..36 / 40..46 вместо 16..22 (pop_pack_bg.py).
|
||
* Кадр 0 («пузырька нет») общий — его подменять нечем и не нужно. */
|
||
if (bubb) {
|
||
if (type == 3 || type == 4)
|
||
bubb = (uint8_t)(POP_POT_BUBB_GREEN + (bubb - POTION_FRAM_BUBB[1]));
|
||
else if (type == 5 || type == 6)
|
||
bubb = (uint8_t)(POP_POT_BUBB_BLUE + (bubb - POTION_FRAM_BUBB[1]));
|
||
}
|
||
pop_heal_off(x - 1, yb - 10, 8, 12);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
pop_pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */
|
||
pop_pot_b(bubb, x, yb); /* кадр пузырька */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Стереть ЗАПЕЧЁННОЕ пламя факела (row,col) с ТЕКУЩЕЙ draw-страницы и
|
||
* вернуть на его место фон покоя.
|
||
*
|
||
* Нужно ровно одному месту — перевороту экрана (pop_flip_screen). Пламя
|
||
* рисуется банком 0x50, то есть запекается и в ОЗУ-копию (см. шапку
|
||
* pop_torch_draw: heal'а у него нет, каждый следующий кадр непрозрачно
|
||
* накрывает предыдущий). Отражение страницы утаскивает его в зеркальную
|
||
* позицию, где накрывать уже нечем — и языки огня остаются по комнате
|
||
* навсегда. Стерев их ДО отражения, мы отражаем чистый фон и не платим за
|
||
* полную перерисовку комнаты (14.5 кадра против 2.1 у копии, замер MAME).
|
||
*
|
||
* Прямоугольник — канвас пламени 16x18 в ячейке ПРАВОГО соседа (seg008:560,
|
||
* те же координаты, что у pop_torch_draw). */
|
||
void pop_torch_wipe(int row, int col) __banked
|
||
{
|
||
int x = (col + 1 < 10 ? POP_COL_XH[col + 1] : POP_COL_XH[col] + 4) * 8 + 8;
|
||
int yb = POP_YOFF + 63 * row + 62 - 40;
|
||
pop_cd_touch(x, yb - 17, 16, 18); /* bar мимо pop_blit_b */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_bar_black(x, yb - 17, 16, 18);
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_t_bake_rest = 1; /* фон = покой */
|
||
draw_tile(row, col);
|
||
if (col + 1 < 10) draw_tile(row, col + 1);
|
||
pop_t_bake_rest = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
|
||
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
|
||
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
|
||
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
|
||
void pop_floor_bake(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
int yb = POP_YOFF + 63 * row;
|
||
/* Вторая страница — копией с первой (см. bake_copy). */
|
||
if (bake_copy(&bake_pg[row * 10 + col], x, yb + 26, 60, 39)) return;
|
||
/* Пометку ставит сам pop_bar_black — по тому же прямоугольнику; отдельный
|
||
* pop_cd_touch тут был вторым вызовом на одну область (4 502 такта). */
|
||
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
|
||
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
|
||
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_bar_black(x, yb + 26, 60, 39);
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
/* ОКНО КЛИПА ЗДЕСЬ ОБЯЗАТЕЛЬНО — и не ради скорости.
|
||
*
|
||
* Без него draw_tile рисует тайлы ЦЕЛИКОМ, то есть пишет ШИРЕ бара, а
|
||
* bake_copy переносит на вторую страницу ровно бар — и всё, что легло вне
|
||
* него, на второй странице остаётся прежним. Страницы расходятся, и это
|
||
* видно как МЕРЦАНИЕ через кадр (найдено пользователем 2026-08-17:
|
||
* уровень 1, комната 6, Кид на кнопке (0,2) — дрожала нижняя грань
|
||
* переднего торца кнопки). С окном запечка и копия совпадают по
|
||
* построению.
|
||
*
|
||
* По скорости окно САМО ПО СЕБЕ убыточно, и это проверено трижды
|
||
* (179 914 -> 188 417): клипованный путь дороже быстрого на ~1 500 такта
|
||
* на каждом из 7,6 блитов, а режет он только редкие высокие куски. Но
|
||
* оно открывает копию второй страницы, а та экономит ~145 000 — так что
|
||
* пара «запечка + копия» всё равно вдвое дешевле двух запечек.
|
||
*
|
||
* Что бар покрывает ВСЁ изменившееся — это инвариант самого бара (иначе
|
||
* запечка не работала бы и без копии). */
|
||
pop_t_win_set(x, yb + 26, 60, 39);
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
draw_tile(row, col);
|
||
if (col + 1 < 10) draw_tile(row, col + 1);
|
||
pop_t_bake_rest = 0;
|
||
pop_t_win_clear();
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
void pop_button_redraw(int row, int col) __banked { pop_floor_bake(row, col); }
|
||
|
||
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
|
||
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
|
||
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
|
||
* Куски двери непрозрачные — wipe не нужен. */
|
||
void pop_leveldoor_redraw(int row, int col) __banked
|
||
{
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
draw_tile(row, col);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Ворота ВНУТРИ комнаты: перерисовать бары по живому modif (порт draw_trob,
|
||
* seg007:01E6 — им заканчивается animate_door). (row,col) — сами ворота;
|
||
* решётку рисует ячейка ПРАВОГО соседа (draw_tile, ветка lcode==4), поэтому
|
||
* wipe и redraw идут по ней. Логика ровно как у левого шва
|
||
* (pop_room_redraw_seam_left), только колонка произвольная.
|
||
*
|
||
* Bar-в-чёрный обязателен: бары висят в видео-ОЗУ и ОЗУ-копии, и при
|
||
* ПОДЪЁМЕ решётки heal вернул бы запечённые нижние бары. Полоса — ровно
|
||
* зона решётки: от gate_top_y (dby−62 = 63*row+3) до низа (63*row+64);
|
||
* выше начинается ряд −1, ниже — стена ряда ниже.
|
||
*
|
||
* Ворота в col9: их бары рисуются уже в СОСЕДНЕЙ комнате, в отрисованной их
|
||
* не видно — оригинал такой trob тоже пропускает
|
||
* (get_trob_right_pos_in_drawn_room → 30). Для взгляда из комнаты справа
|
||
* работает pop_room_redraw_seam_left. */
|
||
void pop_gate_redraw(int row, int col) __banked
|
||
{
|
||
int x, yt;
|
||
if (col + 1 > 9 || row < 0 || row > 2) return;
|
||
x = POP_COL_XH[col + 1] * 8;
|
||
yt = POP_YOFF + 63 * row;
|
||
pop_bar_black(x, yt + 3, 32, 62); /* зеркалится сама (переворот) */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
draw_tile(row, col + 1);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
||
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
||
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
||
* рисовал loose-правый-край pop_env_b(42); теперь пусто → чистая грань).
|
||
* pop_t_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
|
||
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
|
||
void pop_loose_bake_empty(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
int xr = POP_COL_XH[col + 1] * 8;
|
||
int yb = 63 * row + 46;
|
||
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
||
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
|
||
* heal вернул бы старый пол из ОЗУ-копии. Ширина 40 — кусок пола
|
||
* заходит на x+32..+40. */
|
||
/* НИЗ строго 63*row+65 = низ собственной графики плиты (bottom_id 43/73/74
|
||
* стоит ровно на dby; base 41/69/70 — до dmy=62; правая грань 42/66/67 —
|
||
* до dby-1). Ниже начинается СТЕНА ряда ниже (высокий спрайт), которую
|
||
* здесь не перерисовываем: заливка на 9 строк глубже оставляла ЧЁРНЫЙ БОКС
|
||
* под соседним столбом, причём в ОЗУ-копии — то есть heal возвращал его
|
||
* снова, и на второй странице дабл-буфера его не было → мерцание.
|
||
* Тот же урок уже учтён в pop_floor_bake. */
|
||
yb += POP_YOFF;
|
||
/* bar идёт мимо pop_blit_b — пометить область самим (DRAW-COST). */
|
||
pop_cd_touch(x, yb, 72, POP_YOFF + 63 * row + 66 - yb);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
pop_bar_black(x, yb, 40, POP_YOFF + 63 * row + 65 - yb + 1); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
|
||
if (col + 1 < 10)
|
||
pop_bar_black(xr, yb, 32, POP_YOFF + 63 * row + 65 - yb + 1); /* сосед: РОВНО 32 — не залезть в col+2 */
|
||
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
|
||
* снизу) + правый сосед (его левая грань: был loose-край pop_env_b(42),
|
||
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
||
* ОЗУ-копию → heal сохранит. */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
draw_tile(row, col);
|
||
if (col + 1 < 10)
|
||
draw_tile(row, col + 1);
|
||
pop_t_bake_rest = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* ---- Падающий кусок loose (mob, seg007 move_loose) ------------------ *
|
||
* Упрощ. одиночный кусок (в комнате 1 — один loose). Модель fixed-
|
||
* corridor: коридор падения (колонка тайла) heal'ится каждый кадр (стирает
|
||
* прошлый кусок, возвращает печёный фон — empty+«стена вниз»), поверх —
|
||
* кадр 0 loose (41/42/43) на текущей y. Гравитация: speed 0→+3/кадр
|
||
* (cap 29), y+=speed. Двоебуфер: коридор фиксирован → heal чистит любой
|
||
* прошлый кадр ЭТОЙ страницы. Ниже поля кусок прячет clip_borders. */
|
||
/* Падающие куски — ТАБЛИЦА (порт mobs[14]/mobs_count, seg007:965). Одного
|
||
* куска не хватает: в комнате 7 плиты (0,5) и (0,6) соседние, Kid роняет обе
|
||
* подряд, и второй спавн затирал состояние первого — его пиксели оставались
|
||
* висеть в воздухе (на каждой странице на своей y → мерцание), а щебень
|
||
* появлялся только под одной плитой.
|
||
*
|
||
* Слотов СТОЛЬКО ЖЕ, СКОЛЬКО В ОРИГИНАЛЕ (14). Было 4 с обоснованием
|
||
* «больше одновременно падающих плит в одной комнате практически не
|
||
* бывает» — неверным ровно с уровня 13: check_fall_flo роняет ШЕСТЬ разом
|
||
* (комнаты 23 и 16), а с учётом плит, которые эти куски сбивают по дороге
|
||
* (loose_fall), в воздухе бывает и больше. Лишний кусок молча терялся
|
||
* (`if (!m) return`), то есть тайл исчезал, а щебня на месте посадки не
|
||
* появлялось — найдено прогоном 2026-08-13, комната 16, тайл (1,5).
|
||
* Цена — 150 байт W2. */
|
||
#define MOB_MAX 14
|
||
typedef struct {
|
||
uint8_t active; /* 0 нет; 1 падает */
|
||
uint8_t clean; /* кадров дочистки после конца полёта (2 = обе стр.) */
|
||
uint8_t defer; /* кусок ниже Kid → рисуем ПОСЛЕ него (y-порядок) */
|
||
int x; /* левый край (POP_COL_XH*8) */
|
||
int y; /* = curmob.y оригинала (низ куска, dby), растёт вниз */
|
||
int prev_y[2]; /* последняя нарисованная y на КАЖДОЙ странице (heal) */
|
||
int8_t speed;
|
||
int8_t row, col; /* тайл-позиция куска (для окклюзии соседа) */
|
||
uint8_t room; /* curmob.room: кусок падает В СВОЕЙ комнате, не в текущей */
|
||
} mob_t;
|
||
/* НЕ static намеренно: статики банкового модуля в карту символов не
|
||
* попадают, и разобрать зависший кадр по слотам (y каждого куска, порядок,
|
||
* defer) в отладчике нечем — адреса просто нет. Снято 2026-08-13 для
|
||
* разбора артефактов множественного падения; наружу таблица не объявлена и
|
||
* никем не читается. */
|
||
mob_t mobs[MOB_MAX];
|
||
/* Сколько кусков сейчас летит (active). Считает pop_loose_mob_tick. */
|
||
static uint8_t mobs_live;
|
||
|
||
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
||
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
||
/* pop_loose_landed переехал в pop_state.c: его читает pop_map (W1/W2), а
|
||
* писучие данные банкового модуля недоступны снаружи (см. pop_state.h). */
|
||
|
||
/* y_loose_land / y_something (seg007): [row+1], row = −1..3. Ряд −1 —
|
||
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
|
||
* tilepos, у нас — свой ряд полосы у потолка. */
|
||
static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
|
||
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
||
#define MOB_Y_NONE 0x7FFF
|
||
|
||
/* Габарит падающего куска. Части и их места (порт draw_mob, seg007:13E5;
|
||
* индексы loose_fram_*[10], seg008:518/596/608):
|
||
*
|
||
* env 70 левая часть при (mob_x, mob_y-3)
|
||
* env 74 низ при (mob_x, mob_y)
|
||
* env 72 правая часть при (mob_x+32, mob_y-1)
|
||
*
|
||
* Размеры (сняты из каталогов атласов; РАСКЛАДКА env: page = id>>4,
|
||
* idx = id&15 — POP_ENV_SHIFT равен 4, не 5):
|
||
*
|
||
* оба тайлсета 70 = 32x13 74 = 32x3
|
||
* 72 = 26x16 в подземелье, 25x16 во дворце
|
||
*
|
||
* Отсюда след: по x mob_x .. mob_x+57 (32+26−1), по y mob_y-16 .. mob_y
|
||
* (правая часть выше всех: 1+16−1). Именно эти числа и стоят в шапке
|
||
* mob_render — composite их пересчитывает сам и кладёт в шапку блоба.
|
||
*
|
||
* Коридор: MOB_X0 = mob_x при MOB_W = 64 даёт mob_x .. mob_x+63, то есть
|
||
* шесть пикселей запаса справа. Раньше было mob_x-4 при той же ширине
|
||
* (mob_x-4 .. mob_x+59): четыре пикселя слева, где кусок не рисует ничего,
|
||
* и всего два справа. Площадь та же, запас переехал туда, где он нужен. */
|
||
#define MOB_X0(mx) (mx)
|
||
#define MOB_W 64 /* mob_x .. mob_x+63 */
|
||
/* Коридор heal по высоте: mob_y-20 .. mob_y+3 при следе mob_y-18 .. mob_y.
|
||
* Уже НЕ сужать: 24 строки — это след подземелья (19) плюс две строки поля с
|
||
* каждой стороны. Сужение до 18 «по палаццовому следу» сломало бы
|
||
* подземелье. */
|
||
/* Сами величины коридора — в mob_heal_up / mob_heal_h у начала файла: они
|
||
* считаются из высоты собранного композита, а не константами. */
|
||
|
||
/* Свободный слот = не летит И дочистка завершена (иначе затрём хвост,
|
||
* который ещё не стёрт со второй страницы). Общий для обычного отрыва и
|
||
* для копии из loose_fall — раньше цикл был написан дважды. */
|
||
static mob_t *mob_alloc(void)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < MOB_MAX; i++)
|
||
if (!mobs[i].active && !mobs[i].clean) return &mobs[i];
|
||
return 0;
|
||
}
|
||
|
||
static void mob_spawn(uint8_t room, int row, int col)
|
||
{
|
||
mob_t *m = mob_alloc();
|
||
if (!m) return; /* таблица полна — кусок без спрайта (тайл всё равно пуст) */
|
||
m->x = POP_COL_XH[col] * 8;
|
||
m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
|
||
m->row = (int8_t)row;
|
||
m->col = (int8_t)col;
|
||
m->room = room; /* curmob.room (add_mob, seg007:1010) */
|
||
m->speed = 0;
|
||
m->active = 1;
|
||
m->clean = 0;
|
||
m->defer = 0;
|
||
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||
}
|
||
|
||
/* Отрыв куска в ТЕКУЩЕЙ комнате (обычный случай — pop_loose_tick). */
|
||
void pop_loose_mob_spawn(int row, int col) __banked
|
||
{
|
||
mob_spawn(pop_t_room, row, col);
|
||
}
|
||
|
||
/* Копия куска ОТ ПОЗИЦИИ УДАРА — порт хвоста loose_fall (seg007:1085):
|
||
* curmob.y += 6; mob_down_a_row(); add_mob();
|
||
* то есть новый кусок заводится не из тайла, а из места, где его сбили:
|
||
* на 6 пикселей ниже и рядом ниже, со скоростью УЖЕ ПОДЕЛЁННОЙ пополам.
|
||
* Через границу комнаты — как mob_down_a_row: y −= 192, ряд 0, комната снизу. */
|
||
static void mob_spawn_copy(const mob_t *src)
|
||
{
|
||
mob_t *m;
|
||
int16_t y = (int16_t)(src->y + 6);
|
||
int8_t row = (int8_t)(src->row + 1);
|
||
uint8_t room = src->room;
|
||
|
||
if (row >= 3) {
|
||
y = (int16_t)(y - 192);
|
||
row = 0;
|
||
room = pop_room_link(room, 3 /* вниз */);
|
||
if (!room) return; /* ушло из уровня — куску негде лететь */
|
||
}
|
||
m = mob_alloc();
|
||
if (!m) return;
|
||
m->x = src->x;
|
||
m->y = y;
|
||
m->row = row;
|
||
m->col = src->col;
|
||
m->room = room;
|
||
m->speed = src->speed; /* уже half (loose_fall делит ДО add_mob) */
|
||
m->active = 1;
|
||
m->clean = 0;
|
||
m->defer = 0;
|
||
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||
/* Кусок рождается ВНУТРИ mob_tick_one (loose_fall сбил плиту), и слот ему
|
||
* mob_alloc выдаёт с начала таблицы — то есть, скорее всего, УЖЕ
|
||
* пройденный циклом pop_loose_mob_tick. Своей пометки «фон трогали» этот
|
||
* кусок в текущем кадре не получит, а нарисован будет; помечаем коридор
|
||
* здесь — иначе персонаж под ним стирается и не просит перерисовки
|
||
* (см. pop_cd_mute в mob_render). */
|
||
pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64);
|
||
}
|
||
|
||
/* Отрыв куска в ЧУЖОЙ комнате: плита, которую Кид расшатал и убежал, всё
|
||
* равно проваливается (animate_loose в pop_trob крутит её trob по своей
|
||
* комнате, как do_trobs оригинала). Летит и садится кусок дальше сам —
|
||
* mob_tick_one давно умеет чужую комнату (mob_tile_at / pop_loose_exit). */
|
||
void pop_loose_mob_spawn_at(uint8_t room, int row, int col) __banked
|
||
{
|
||
mob_spawn(room, row, col);
|
||
}
|
||
|
||
|
||
/* Остановить падающий кусок (при смене комнаты — состояние per-tile не
|
||
* должно утечь в новую комнату). */
|
||
static void mob_render(mob_t *m, uint8_t pg);
|
||
|
||
void pop_loose_mob_reset(void) __banked
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < MOB_MAX; i++) {
|
||
mobs[i].active = 0; mobs[i].clean = 0; mobs[i].defer = 0;
|
||
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
|
||
}
|
||
}
|
||
|
||
/* Смена комнаты. Куски НЕ останавливаем: в оригинале mobs[] чистится только
|
||
* в start_level (seg003:88), а do_mobs (seg007:1063) крутит их все независимо
|
||
* от drawn_room — move_loose работает по curmob.room, и плита, за которой Кид
|
||
* не досмотрел, всё равно долетает и кладёт щебень (BUG-LOOSE-2: в комнате 12
|
||
* плита 0,1 не успевала упасть до ухода в комнату 15). Сбросить надо только
|
||
* heal-историю: новая комната перерисована целиком, старых пикселей нет. */
|
||
void pop_loose_mob_room_changed(void) __banked
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < MOB_MAX; i++) {
|
||
mobs[i].defer = 0;
|
||
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
|
||
/* дочистка хвоста относилась к покинутой комнате — она уже не нужна */
|
||
if (!mobs[i].active) mobs[i].clean = 0;
|
||
}
|
||
}
|
||
|
||
/* Тайл под куском. В своей комнате — обычный tile_code (он знает про кромки
|
||
* и соседей), в чужой — прямо из данных уровня. */
|
||
static uint8_t mob_tile_at(mob_t *m)
|
||
{
|
||
if (m->room == pop_t_room) return (uint8_t)(pop_tile_code(m->row, m->col) & 0x1F);
|
||
if (m->row < 0 || m->row > 2 || m->col < 0 || m->col > 9) return 1; /* кромка = пол */
|
||
return (uint8_t)(pop_level_tile(m->room, (uint8_t)(m->row * 10 + m->col)) & 0x1F);
|
||
}
|
||
|
||
/* loose_fall (seg007:1082) — падающий кусок пробил ЕЩЁ ОДНУ расшатанную
|
||
* плиту. Порт дословный, три действия В ОДНОМ КАДРЕ:
|
||
* 1. налетевший кусок тормозится вдвое (`curmob.speed >>= 1`);
|
||
* 2. сбитый тайл снимается с карты немедленно (`remove_loose`);
|
||
* 3. на месте удара заводится ВТОРОЙ кусок — копия налетевшего, сдвинутая
|
||
* на 6 пикселей и ряд вниз (`curmob.y += 6; mob_down_a_row; add_mob`).
|
||
* То есть в столбце дальше летят ДВА куска, и это правильное поведение
|
||
* оригинала, а не дефект.
|
||
*
|
||
* Здесь стоял обходной путь: плите ставилась фаза «падает следующим кадром»,
|
||
* а всю работу делал pop_loose_tick. Цена оценивалась как «один кадр
|
||
* задержки» и оказалась выше: защита «один сигнал pop_loose_fell на кадр»
|
||
* придерживает провал, пока главный цикл не разберёт предыдущий, и на 13-м
|
||
* уровне (плиты сыплются пачкой) вторая плита появлялась через несколько
|
||
* кадров — уже после того, как первая улетела с экрана, — да ещё и с высоты
|
||
* покоя тайла, а не от места удара (найдено прогоном 2026-08-13). */
|
||
static void mob_break_loose(mob_t *m)
|
||
{
|
||
m->speed >>= 1;
|
||
pop_loose_remove(m->room, (uint8_t)(m->row * 10 + m->col));
|
||
mob_spawn_copy(m);
|
||
}
|
||
|
||
static mob_t *mt_m;
|
||
static uint8_t mt_pg, mt_here;
|
||
|
||
static void mob_tick_one(mob_t *m, uint8_t pg)
|
||
{
|
||
/* Рабочие переменные — file-scope, не локали: у функции пять вызовов, и
|
||
* живой через них указатель на слот SDCC спиливал в кадр (16 байт, 99
|
||
* обращений `-N(ix)`). То же лечение, что у draw_tile и blit_b_clip.
|
||
* Реентерабельности нет и не требуется: единственный вызывающий —
|
||
* pop_loose_mob_tick, и слоты он обходит последовательно. */
|
||
mt_m = m; mt_pg = pg;
|
||
if (!mt_m->active && !mt_m->clean) return;
|
||
mt_here = (uint8_t)(mt_m->room == pop_t_room);
|
||
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
|
||
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
|
||
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
|
||
* прошлой позиции. */
|
||
/* Гейта по `mt_here` здесь БЫТЬ НЕ ДОЛЖНО: prev_y[mt_pg] — это след, который
|
||
* кусок оставил ИМЕННО НА ЭТОЙ странице, и стереть его надо независимо
|
||
* от того, где кусок сейчас. Пока чистка была под `mt_here`, кусок,
|
||
* улетевший в комнату снизу (mob_down_a_row), навсегда оставлял себя
|
||
* на одной из двух страниц дабл-буфера — «застрявшая плита», видимая
|
||
* через кадр (BUG-MOB-STALE-PAGE). */
|
||
if (mt_m->prev_y[mt_pg] != MOB_Y_NONE) {
|
||
/* Ширина/высота — по СЛЕДУ, а не по спрайту. Спрайт куска целиком
|
||
* влезает в 64x32 (41: 32x13 с y-3, 42: 26x15 с x+32,y-1, 43: 32x3),
|
||
* но mob_render рисует ещё и ЦЕЛЫЙ соседний тайл поверх правой части
|
||
* (окклюзия грани пола), причём в GFX_BANK_SPRITE — то есть мимо
|
||
* теневой копии фона. Всё, что тот тайл нарисовал вне коридора
|
||
* heal'а, не стиралось никогда: кусок улетал, а в соседнем столбце
|
||
* оставался обрезок пола (найдено прогоном 2026-08-13). Коридор
|
||
* расширен до площади, которую реально может тронуть эта
|
||
* перерисовка: столбец соседа по ширине и ряд по высоте.
|
||
*
|
||
* ЗАДЕЛ: правильнее не перерисовывать соседа вовсе, а КЛИПОВАТЬ
|
||
* правую часть куска — оригинал так и делает (add_mob_to_objtable,
|
||
* seg007:1161: clip.right = 40). Тогда коридор вернётся к 64x32. */
|
||
pop_heal_off(MOB_X0(mt_m->x), mt_m->prev_y[mt_pg] - mob_heal_up, MOB_W, mob_heal_h);
|
||
mt_m->prev_y[mt_pg] = MOB_Y_NONE;
|
||
}
|
||
if (!mt_m->active) { mt_m->clean--; return; }
|
||
/* гравитация (move_loose): ускорение до 29 */
|
||
if (mt_m->speed < 29) mt_m->speed += 3;
|
||
mt_m->y += mt_m->speed;
|
||
/* move_loose (seg007:1126): низ куска пересёк границу своего ряда —
|
||
* смотрим тайл этого ряда. Пусто/loose — валится дальше, пол —
|
||
* приземление (loose_land: debris + do_knock). Через LOOSE кусок именно
|
||
* ПРОЛЕТАЕТ, как через пустоту, — но по дороге СРЫВАЕТ её (loose_fall,
|
||
* seg007:1082).
|
||
*
|
||
* Здесь раньше стояло «порт loose_fall не нужен: в ряду ниже её нет» —
|
||
* допущение, верное для уровней 1-11 и сломавшееся на 13-м: комната 16,
|
||
* плиты (1,6) и (1,7), на которые сверху сыплется гряда из комнаты 1.
|
||
* Куски пролетали сквозь них правильно, а сами плиты оставались целыми и
|
||
* нарисованными (найдено прогоном 2026-08-13). */
|
||
if (mt_m->row < 3 && mt_m->y < 226 && MOB_Y_BOUND[mt_m->row + 1] <= mt_m->y) {
|
||
uint8_t t = mob_tile_at(mt_m);
|
||
if (t == 11) mob_break_loose(mt_m);
|
||
if (t == 0 || t == 11) {
|
||
/* mob_down_a_row (seg007:1387): ряд ниже, а из НИЖНЕГО ряда —
|
||
* в комнату СНИЗУ (y -= 192, ряд 0). Именно так кусок пролетает
|
||
* несколько комнат подряд: уровень 6, плита (1,7) комнаты 6
|
||
* проваливается сквозь пустую шахту комнаты 7 и приземляется на
|
||
* кнопку (2,7) комнаты 11. Раньше мы кусок на границе комнаты
|
||
* ГАСИЛИ и подменяли одним «переходом вниз» из главного цикла —
|
||
* дальше первой комнаты он не летел (BUG-MOB-MULTIROOM). */
|
||
mt_m->row++;
|
||
if (mt_m->row >= 3) {
|
||
mt_m->y = (int16_t)(mt_m->y - 192);
|
||
mt_m->row = 0;
|
||
mt_m->room = pop_room_link(mt_m->room, 3 /* вниз */);
|
||
if (!mt_m->room) { /* улетел из уровня — просто гаснет */
|
||
mt_m->active = 0; mt_m->clean = 2; /* дочистить ОБЕ страницы */
|
||
return;
|
||
}
|
||
mt_here = (uint8_t)(mt_m->room == pop_t_room);
|
||
}
|
||
} else if (!mt_here && pop_loose_exit) {
|
||
/* Сигнал прошлой посадки в чужой комнате ещё не разобран (он
|
||
* ОДИН на кадр) — держим кусок на границе ряда и садим следующим
|
||
* кадром, как это уже сделано для pop_loose_landed ниже. Без
|
||
* этого второй кусок затирал первый, и из нескольких плит,
|
||
* улетевших в одну дыру, щебень оставляла только одна (найдено
|
||
* прогоном 2026-08-13, уровень 13). */
|
||
mt_m->y = MOB_Y_BOUND[mt_m->row + 1];
|
||
} else if (!mt_here) {
|
||
/* Приземление в ПОКИНУТОЙ комнате: рисовать нечего, но щебень и
|
||
* КНОПКА под куском обязаны сработать — сигналим главному циклу
|
||
* комнатой и тайлом (loose_land, seg007:11E8, работает с
|
||
* curmob.room, а не с отрисованной). */
|
||
pop_loose_exit_room = mt_m->room;
|
||
pop_loose_exit = (uint8_t)(mt_m->row * 10 + mt_m->col + 1);
|
||
/* clean = 2 даже для чужой комнаты: пока кусок летел, он мог
|
||
* рисоваться в ЭТОЙ, и след остался на обеих страницах. */
|
||
mt_m->active = 0; mt_m->clean = 2;
|
||
return;
|
||
} else if (pop_loose_landed) {
|
||
/* сигнал прошлого приземления ещё не разобран (один за кадр) —
|
||
* подержим кусок на границе и приземлим следующим кадром */
|
||
mt_m->y = MOB_Y_BOUND[mt_m->row + 1];
|
||
} else {
|
||
mt_m->y = MOB_Y_BOUND[mt_m->row + 1];
|
||
pop_loose_landed = (uint8_t)(mt_m->row * 10 + mt_m->col + 1);
|
||
mt_m->active = 0; mt_m->clean = 2; /* кусок исчез — на его месте debris */
|
||
return;
|
||
}
|
||
}
|
||
/* Страховка: кусок ушёл ниже поля, не поймав границу ряда (в норме этого
|
||
* не бывает — mob_down_a_row уводит его в комнату снизу). Просто гаснет,
|
||
* как у оригинала кусок в комнате 0 (move_loose: `speed = -2`). */
|
||
if (mt_m->y - 27 >= POP_PLAYFIELD_H) {
|
||
mt_m->active = 0; mt_m->clean = 2;
|
||
return;
|
||
}
|
||
if (!mt_here) return; /* чужая комната — считаем, но не рисуем */
|
||
/* Порядок «плита vs Kid». В оригинале объекты рисуются при обходе СВОИХ
|
||
* тайлов (redraw_needed_tiles: ряды 2,1,0; колонки 0..9), и только внутри
|
||
* ОДНОГО тайла сортируются по obj_y (sort_curr_objs, seg008:1561: кто
|
||
* ниже — тот позже, т.е. поверх). Тайл падающего куска — draw_mob
|
||
* (seg007:13E5): row = y_to_row_mod4(y), col = xh>>2. Значит кусок
|
||
* поверх Kid, если его тайл идёт в обходе ПОЗЖЕ тайла Kid, а при
|
||
* совпадении тайлов — если он ниже по y. */
|
||
{
|
||
int8_t mrow = pop_y_to_row((int16_t)mt_m->y); /* y_to_row_mod4 */
|
||
uint8_t over;
|
||
if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */
|
||
else if (mt_m->col != pop_bg_obj_col) over = (mt_m->col > pop_bg_obj_col);
|
||
else over = (mt_m->y > pop_cd[POP_CH_KID].fpy);
|
||
/* РИСОВАНИЯ ЗДЕСЬ БОЛЬШЕ НЕТ — только решение, в какой проход кусок
|
||
* пойдёт. Пока heal и вывод шли в одной итерации, heal СЛЕДУЮЩЕГО
|
||
* куска срезал верхнюю кромку уже нарисованного предыдущего: на 13-м
|
||
* уровне куски идут лесенкой в соседних столбцах с разницей ~20 px
|
||
* по y, и перекрытие гарантировано (артефакт «чёрный бар», найден
|
||
* прогоном 2026-08-13, комната 23). Теперь сперва тикают ВСЕ куски
|
||
* (то есть heal'ятся все), и только потом рисуются. */
|
||
mt_m->defer = over;
|
||
}
|
||
}
|
||
|
||
/* Один проход отрисовки кусков. want_defer: 0 — те, что под Kid (идут сразу
|
||
* после фона), 1 — те, что поверх него (после персонажей).
|
||
*
|
||
* Порядок внутри прохода — ПО y, но ПО УБЫВАНИЮ: сперва нижние, последним
|
||
* верхний. Это НЕ описка и не общее правило «ниже = поверх»: сравнение двух
|
||
* объектов в оригинале (compare_curr_objs, seg008:1572) разводит два случая,
|
||
*
|
||
* оба объекта — падающие плиты (тип 0x80): y1 < y2 → по УБЫВАНИЮ
|
||
* любая другая пара: y1 > y2 → по возрастанию
|
||
*
|
||
* то есть между СОБОЙ куски укладываются наоборот, чем всё остальное: верхний
|
||
* ложится поверх нижнего. Видно на паре из loose_fall (сбитая плита летит в
|
||
* 6 пикселях под налетевшей): при общем правиле нижняя перекрывала верхнюю,
|
||
* и это поймал прогон 2026-08-13.
|
||
*
|
||
* Порядок «кусок против Кида» здесь ни при чём — его решает defer выше, и там
|
||
* действует обычное правило. */
|
||
/* Пометки СОСЕДА СПРАВА — порт draw_mob (seg007:1147). Оригинал каждый
|
||
* кадр помечает тайл col+1 в ряду куска сразу двумя пометками:
|
||
*
|
||
* set_redraw2(tilepos, 1); // фон соседа — рисуется ДО куска
|
||
* set_redraw_fore(tilepos, 1); // его передние части — ПОСЛЕ куска
|
||
*
|
||
* и повторяет их для второго ряда, если кусок висит на границе (ypos-18).
|
||
* Так правая часть куска (спрайт 72 лежит в столбце col+1) прячется за
|
||
* передней гранью соседнего пола, а передние части чужого тайла — колонна,
|
||
* например, — ложатся поверх куска.
|
||
*
|
||
* Здесь была подпорка: целый соседний тайл рисовался ПОВЕРХ куска прямо в
|
||
* mob_render. Она тащила на плиту чужой узор и окно и стоила по полной
|
||
* отрисовке тайла на кусок за кадр (MOB-CLIP-RIGHT). */
|
||
static void mob_mark_neighbour(const mob_t *m)
|
||
{
|
||
int8_t r = pop_y_to_row((int16_t)m->y);
|
||
int8_t rt = pop_y_to_row((int16_t)(m->y - 18));
|
||
int8_t c = (int8_t)(m->col + 1);
|
||
/* ПОЛОСА У ПОТОЛКА (ряд −1). Её передние куски (нижняя кромка потолка и
|
||
* кадр плиты-потолка) живут в трёх верхних строках поля — dby = 2 при
|
||
* клипе по POP_YOFF, — а спрайт куска занимает mob_y−16 .. mob_y. Значит
|
||
* кусок закрывает кромку, пока mob_y − 16 <= 2, то есть mob_y <= 18: три
|
||
* кадра после отрыва (y = 2, 5, 11 при ускорении 3). Помечаем ОБА
|
||
* столбца, которые кусок накрывает по x (mob_x .. mob_x+62 — это col и
|
||
* col+1).
|
||
*
|
||
* Без этого кусок оказывался ПОВЕРХ кромки потолка соседней колонки —
|
||
* физически невозможно (найдено пользователем 2026-08-17, ур.1 к.6:
|
||
* плита (−1,5) перекрывала потолок (−1,6)). В оригинале порядок даёт
|
||
* сама структура таблиц: кусок идёт в midtable (draw_mobs), а полоса
|
||
* потолка — в foretable, и та рисуется позже (draw_tables, seg008:1373). */
|
||
if (m->y <= 18) {
|
||
/* Пустой тайл полосы потолка не рисует В ПЕРЕДНЕМ СЛОЕ ничего
|
||
* (bottom_id = 0, FORE_ANY = 0), а пометка стоит потом двух трамплинов
|
||
* — в банк 7 за разбором пометок и в банк 2 за самой отрисовкой,
|
||
* ~8 900 такта каждый. А пустой он тут как раз чаще всего: кусок
|
||
* существует ровно потому, что плита из своей клетки уже выпала.
|
||
* Проверяем pop_tile_code — он резидентный, ~1 100 такта. */
|
||
if (pop_tile_code(-1, m->col))
|
||
pop_set_redraw_fore_above((uint8_t)m->col);
|
||
if (m->col + 1 <= 9 && pop_tile_code(-1, m->col + 1))
|
||
pop_set_redraw_fore_above((uint8_t)(m->col + 1));
|
||
}
|
||
if (c > 9) return;
|
||
if (r >= 0 && r <= 2) pop_set_redraw_fore((uint8_t)(r * 10 + c));
|
||
if (rt != r && rt >= 0 && rt <= 2) pop_set_redraw_fore((uint8_t)(rt * 10 + c));
|
||
}
|
||
|
||
/* Вторая половина draw_mob (seg007:1149): рядом с set_redraw_fore там стоит
|
||
* set_redraw2, то есть draw_other_overlay соседнего тайла — его части уходят в
|
||
* midtable и ложатся ПОВЕРХ куска (порядок разобран в pop_mob_overlay_tile).
|
||
* Звать СРАЗУ после mob_render: списков у нас нет, порядок эмулируется
|
||
* последовательностью вызовов. */
|
||
static void mob_overlay_neighbour(const mob_t *m)
|
||
{
|
||
int8_t r = pop_y_to_row((int16_t)m->y);
|
||
int8_t rt = pop_y_to_row((int16_t)(m->y - 18));
|
||
int8_t c = (int8_t)(m->col + 1);
|
||
int ytop, ybot;
|
||
if (c > 9) return;
|
||
ytop = m->y - (int)mob_spr[2] + 1; /* верх спрайта куска */
|
||
ybot = m->y;
|
||
/* ПРЕДУСЛОВИЯ ПРОВЕРЯЕМ ЗДЕСЬ, до вызова в банк 2. pop_mob_overlay_tile
|
||
* живёт в банке 2, то есть каждый вызов из банка 7 платит межбанковый
|
||
* трамплин (~8 900 такта); а условий всего два — тайл не пуст и габарит
|
||
* тайла пересекается с габаритом куска. Оба дешёвые: pop_tile_code —
|
||
* резидент (~1 100). Замер 2026-08-17: без этого гейта двенадцать
|
||
* вызовов на шесть кусков стоили ~130 000 в циане, причём большинство
|
||
* ничего не рисовало.
|
||
*
|
||
* Габарит тайла берём ПОЛНЫМ (63*row+2 .. +66) — какая именно часть
|
||
* соседа окажется поверх куска, решит уже окно клипа внутри. Сужать по
|
||
* реальной высоте кусков нельзя: у колонн и зеркала база высокая. */
|
||
if (r >= 0 && r <= 2 && pop_tile_code(r, c) &&
|
||
ybot >= 63 * r + 2 && ytop <= 63 * r + 66)
|
||
pop_mob_overlay_tile(r, c, r, (int8_t)m->col, m->x, m->y, mob_spr[2]);
|
||
if (rt != r && rt >= 0 && rt <= 2 && pop_tile_code(rt, c) &&
|
||
ybot >= 63 * rt + 2 && ytop <= 63 * rt + 66)
|
||
pop_mob_overlay_tile(rt, c, r, (int8_t)m->col, m->x, m->y, mob_spr[2]);
|
||
}
|
||
|
||
static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
|
||
{
|
||
uint8_t order[MOB_MAX], n = 0, i, j;
|
||
/* Ранний выход: проход зовётся ДВАЖДЫ за кадр (под Kid и поверх него), и
|
||
* без него мы перебирали бы все MOB_MAX слотов 28 раз в кадре даже
|
||
* когда в воздухе пусто. Счётчик ведёт тик, которому проход по слотам
|
||
* нужен и так. */
|
||
if (!mobs_live) return;
|
||
for (i = 0; i < MOB_MAX; i++) {
|
||
mob_t *m = &mobs[i];
|
||
if (!m->active || m->room != pop_t_room) continue;
|
||
if ((uint8_t)(m->defer != 0) != want_defer) continue;
|
||
for (j = n; j > 0 && mobs[order[j - 1]].y < m->y; j--)
|
||
order[j] = order[j - 1];
|
||
order[j] = i;
|
||
n++;
|
||
}
|
||
for (i = 0; i < n; i++) {
|
||
/* Пометки соседа ставятся ЗДЕСЬ, а не в тике: у оригинала draw_mob
|
||
* (seg007:1147) считает ряд от той же координаты, по которой кусок
|
||
* рисуется. Стояли они в цикле тика — то есть по позиции ДО
|
||
* mob_tick_one, а рисовался кусок уже по новой. Пока он летит
|
||
* внутри ряда, старый и новый тайл совпадают и разницы не видно; но
|
||
* в кадр пересечения границы пометка указывала на прежний тайл, и
|
||
* передняя часть колонны кусок не перекрывала — ровно ОДИН кадр,
|
||
* как и наблюдалось (прогон 2026-08-13, комната 23, колонка 8). */
|
||
mob_mark_neighbour(&mobs[order[i]]);
|
||
mob_render(&mobs[order[i]], pg);
|
||
mob_overlay_neighbour(&mobs[order[i]]);
|
||
}
|
||
}
|
||
|
||
void pop_loose_mob_tick(void) __banked
|
||
{
|
||
uint8_t pg = gfx_get_draw_page() & 1, i, live = 0;
|
||
/* Слоты обходим УКАЗАТЕЛЕМ и пустые отсеиваем ЗДЕСЬ, а не внутри
|
||
* mob_tick_one. Индексная запись mobs[i] заставляла SDCC умножать i на
|
||
* sizeof(mob_t)=15 заново под каждое поле (.active/.clean/.x/.y), и 14
|
||
* пустых слотов стоили 35 790 тактов в кадре, где не падает НИЧЕГО
|
||
* (замер 2026-08-13). Гард в самом mob_tick_one оставлен: функция
|
||
* зовётся и из других мест. */
|
||
mob_t *m = mobs;
|
||
/* Пометки всех кусков — ОДНИМ пакетом: настоящий pop_cd_touch стоит 4 502
|
||
* такта (замер 2026-08-17), а кусков в кадре каскада шесть. В пакете они
|
||
* копят общий прямоугольник четырьмя сравнениями, и настоящая пометка одна.
|
||
*
|
||
* Загрубление осознанное: объединение шести коридоров накрывает и ряды
|
||
* между ними. Безопасно в одну сторону — лишняя пометка стоит лишней
|
||
* перерисовки персонажа, пропущенная стоит ИСЧЕЗНУВШЕГО персонажа.
|
||
* Практически гряда идёт по колонкам 2..7, а Кид от неё убегает — на
|
||
* замерах разницы в числе перерисовок нет. */
|
||
pop_cd_batch_begin();
|
||
for (i = 0; i < MOB_MAX; i++, m++) {
|
||
/* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом,
|
||
* мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя
|
||
* (DRAW-COST). Взводим ДО тика: кусок может исчезнуть прямо в нём, а
|
||
* стереть себя ему всё равно надо будет. */
|
||
if (!m->active && !m->clean) continue; /* пустой слот — ни адреса, ни вызова */
|
||
/* Метка «фон трогали вот здесь» обязана покрывать ВЕСЬ коридор
|
||
* heal'а, а не габарит спрайта. Иначе персонаж, попавший в
|
||
* расширенную часть коридора, стирается heal'ом, но перерисовать
|
||
* себя не просит (pop_char_skip_mask его пропускает) — и просто
|
||
* исчезает с экрана. Ровно это и случилось, когда коридор
|
||
* расширили под транзитную перерисовку соседа (прогон
|
||
* 2026-08-13: в комнате 23 после падения гряды пропадал Кид).
|
||
* Запас сверху — на путь, пройденный за кадр (speed до 29): heal
|
||
* стирает ПРОШЛУЮ позицию, она выше текущей. */
|
||
pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64);
|
||
mob_tick_one(m, pg);
|
||
if (m->active) live++;
|
||
}
|
||
pop_cd_batch_end();
|
||
mobs_live = live;
|
||
}
|
||
|
||
static void mob_render(mob_t *m, uint8_t pg)
|
||
{
|
||
/* Падающий кусок (SDLPoP draw_mobs/add_midtable, case 0x80): ТРИ куска —
|
||
* left(41) на obj_y-3, bottom(43) на obj_y, right(42) на obj_x+4 (=col+1,
|
||
* заходит в тайл СПРАВА) на obj_y-1. */
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
/* КЛИПА ОБЪЕКТА ЗДЕСЬ НЕТ. У оригинала он есть — add_mob_to_objtable
|
||
* (seg007:1161) ставит куску clip.right = 40, — но единицы этого поля я
|
||
* НЕ выяснил: буквальные 40 экранных пикселей срезают правый задний угол
|
||
* плиты (прогон 2026-08-13). У оригинала obj_x живёт в удвоенных
|
||
* единицах (char_x_left = obj_x / 2 + 58), так что 40 может означать и
|
||
* не пиксели. До выяснения рисуем кусок целиком.
|
||
*
|
||
* Подпорки «перерисовать соседний тайл поверх куска» тоже нет: она тащила
|
||
* на плиту чужой узор и окно (MOB-CLIP-RIGHT). Цена отказа — правая
|
||
* грань куска не прячется за передней гранью соседнего пола; артефакт
|
||
* мельче прежнего и уйдёт вместе с сортируемым midtable. */
|
||
/* Спрайты ПАДАЮЩЕГО куска, а не лежащей плиты. У объекта в objtable
|
||
* obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из
|
||
* таблиц по этому индексу (seg008:518/596/608):
|
||
* loose_fram_left[10] = 70 при (x, y-3)
|
||
* loose_fram_bottom[10] = 74 при (x, y)
|
||
* loose_fram_right[10] = 72 при (x+32, y-1)
|
||
* Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и
|
||
* «цельная ровная плита» вместо двух частей со сдвигом правой половины
|
||
* на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом).
|
||
* Габариты частей — в блоке MOB_X0 выше (правая часть у тайлсетов
|
||
* РАЗНАЯ: 26 px в подземелье, 25 во дворце).
|
||
*
|
||
* Рисуем ОДНИМ блитом по композиту (mob_spr_build): три вызова блита
|
||
* стоили 258 708 такта в кадре при шести кусках — больше половины
|
||
* цианового блока. Три отдельных блита остались резервным путём на
|
||
* случай, если композит не собрался. */
|
||
/* Пометки «фон трогали» на время блитов ПОДАВЛЕНЫ: коридор куска
|
||
* (MOB_X0 .. +MOB_W, y−27 .. +37) уже помечен ОДНИМ вызовом — из
|
||
* pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска,
|
||
* рождённого внутри тика. Спрайт целиком внутри коридора: по x он
|
||
* занимает m->x .. m->x+57, по y m->y−16 .. m->y, коридор — m->x ..
|
||
* m->x+63 и m->y−27 .. m->y+37 (запас на ускорение, speed ≤ 31 за кадр).
|
||
* Замер 2026-08-17: три настоящих pop_cd_touch стоили 13 506 тактов на
|
||
* кусок, то есть 81 000 в кадре при шести кусках. */
|
||
pop_cd_mute();
|
||
/* ВЫХОД ЗА ПОЛЕ РЕЖЕМ САМИ, а не чистим борта после. Раньше здесь стоял
|
||
* pop_clip_sprite, то есть кусок рисовался в борт целиком, взводил
|
||
* border_dirty, и pop_room_clip_borders потом стирал ДВЕ полосы во всю
|
||
* ширину экрана — 320x28 и 320x28. Замер 2026-08-17: 150 978 тактов в
|
||
* КАЖДОМ кадре, пока хоть один кусок торчит выше поля, то есть почти весь
|
||
* каскад (гряда на 13-м рождается ровно у потолка, y = 2). Это была
|
||
* четверть цианового блока.
|
||
*
|
||
* Окно ставим только когда кусок реально задевает борт: внутри поля блиты
|
||
* идут быстрым путём (10 422 против 14 088). */
|
||
{
|
||
int ytop = m->y - 16 + POP_YOFF, ybot = m->y + POP_YOFF;
|
||
uint8_t clipped = (uint8_t)(ytop < POP_YOFF || ybot >= POP_CLIP_BOTTOM);
|
||
if (clipped) pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H);
|
||
if (mob_spr_ok) {
|
||
pop_mem_b(mob_spr, m->x, m->y);
|
||
} else {
|
||
pop_env_b(74, m->x, m->y);
|
||
pop_env_b(70, m->x, m->y - 3);
|
||
pop_env_b(72, m->x + 32, m->y - 1);
|
||
}
|
||
if (clipped) pop_t_win_clear();
|
||
}
|
||
pop_cd_unmute();
|
||
m->prev_y[pg] = m->y;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Отрисовка кусков, идущих ПОД Kid. Звать ПОСЛЕ pop_redraw_needed(): фон
|
||
* кадра обязан быть полностью готов до любого спрайта.
|
||
*
|
||
* Раньше куски рисовались внутри pop_loose_mob_tick, то есть ДО разбора
|
||
* пометок перерисовки, и перепечатка тайла ложилась поверх уже нарисованного
|
||
* куска — окно задней стены, передняя грань пола (найдено прогоном
|
||
* 2026-08-13, комнаты 16 и 23). */
|
||
void pop_loose_mob_draw(void) __banked
|
||
{
|
||
mob_draw_pass(gfx_get_draw_page() & 1, 0);
|
||
}
|
||
|
||
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
|
||
* после отрисовки персонажей, до fore-слоя. */
|
||
void pop_loose_mob_draw_over(void) __banked
|
||
{
|
||
mob_draw_pass(gfx_get_draw_page() & 1, 1);
|
||
}
|
||
|
||
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
|
||
* y (низ куска). 0 — куска нет. */
|
||
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
|
||
{
|
||
uint8_t i;
|
||
/* check_loose_fall_on_kid (seg007:1192) начинается с Char.room ==
|
||
* curmob.room: кусок, падающий в покинутой комнате, Киду по голове не
|
||
* бьёт. */
|
||
for (i = 0; i < MOB_MAX; i++)
|
||
if (mobs[i].active && mobs[i].room == pop_t_room) {
|
||
*col = mobs[i].col; *y = mobs[i].y; return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Спрайт вышел за поле? → взвести флаг на 2 кадра (обе страницы дабл-
|
||
* буфера). Узкий per-спрайт heal НЕ используем: accel-выравнивание блока
|
||
* оставляет 1–2px слайверы правой грани → на одной странице мерцает.
|
||
* Вместо этого борта чистит pop_room_clip_borders (полоса, но ТОЛЬКО в
|
||
* кадрах падения — гейт по флагу). */
|
||
static uint8_t border_dirty;
|
||
|
||
void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
|
||
{
|
||
int sy_bot = sy_top + h;
|
||
(void)x; (void)w;
|
||
if (sy_bot > POP_CLIP_BOTTOM || sy_top < POP_YOFF)
|
||
border_dirty = 2; /* 2 кадра = обе страницы */
|
||
}
|
||
|
||
/* Клип бортов (см. pop_bg.h): работает ТОЛЬКО пока border_dirty (кто-то
|
||
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
|
||
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
|
||
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
|
||
void pop_room_clip_borders(void) __banked
|
||
{
|
||
if (!border_dirty) return;
|
||
border_dirty--;
|
||
/* ОСТАЁТСЯ клипающим осознанно (CLIP-1, 2026-08-01): полоса шириной 320
|
||
* не лезет в 8-битный параметр noclip-ядра, а разбивать её на два куска
|
||
* по 160 нет смысла — гейт border_dirty пускает сюда только в кадрах
|
||
* падения/выхода за поле, и выигрыш (~2×5.6 К тактов подготовки) там
|
||
* тонет в цене самих 320×28 пикселей. Не «оптимизировать» повторно. */
|
||
gfx_heal(0, 0, 320, POP_YOFF); /* верхний борт */
|
||
gfx_heal(0, POP_CLIP_BOTTOM, 320, 256 - POP_CLIP_BOTTOM);/* статус-полоса + нижний борт */
|
||
}
|
||
|
||
void pop_room_draw(uint8_t room_num,
|
||
const uint8_t *fg, const uint8_t *bg,
|
||
const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked
|
||
{
|
||
int row, col;
|
||
pop_t_room = room_num;
|
||
pop_wall_pattern_reset_b(); /* кэш кладки привязан к номеру комнаты */
|
||
pop_t_fg = fg; pop_t_bg = bg;
|
||
pop_t_lfg = lcol_fg; pop_t_lbg = lcol_bg;
|
||
|
||
/* прозрачный блит: 0xFF не пишется (чёрный фон/нижние слои проступают) */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
for (row = 2; row >= 0; row--) /* снизу вверх (2,1,0) */
|
||
for (col = 0; col < 10; col++)
|
||
draw_tile(row, col);
|
||
/* Полоса кладки у потолка (seg008 draw_room: доп. ряд из room_A, row2
|
||
* при draw_main_y=-1): нижние/правые грани ряда 2 комнаты сверху. Кадры
|
||
* высокие — верх ушёл бы в борт (за поле); клип по верхней границе поля
|
||
* (POP_YOFF) рисует ТОЛЬКО срез у потолка, без зачернения борта. */
|
||
pop_t_clip_top = POP_YOFF;
|
||
for (col = 0; col < 10; col++)
|
||
draw_tile(-1, col);
|
||
pop_t_clip_top = 0;
|
||
pop_t_bake_rest = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
} |