Files
Sprinter-SDCC/applications/PoP/roomtest/pop_room.c
T
snark13 dc0bd47368 L3-CHOMP: чомперы — анимация, отрисовка и смерть в челюстях
Порт SDLPoP:
  animate_chomper / start_chompers / start_anim_chomper /
  next_chomper_timing (seg007) -> pop_trob.c;
  draw_tile_anim + draw_tile_fore, ветка tiles_18_chomper (seg008) ->
  pop_room.c (низ/кровь/верх, backtable) и pop_bg.c (передний слой);
  check_chomped_kid / check_chomped_guard / chomped (seg004) -> pop_map.c.

Состояние — в room_modif, как у пик и ворот: младшие 7 бит фаза 1..N,
старший бит «перемололо кого-то» (кровь остаётся на тайле навсегда).
Номер позы chomper_fram1 и передние куски лежат в РЕЗИДЕНТЕ (pop_tile.c):
их читают обе половины слоя фона, а const-таблица банка из чужого банка
не видна.

start_chompers зовётся там же, где в оригинале: SEQ_UP/SEQ_DOWN в play_seq
(pop_kid.c), start_fall и land (pop_map.c), вход в комнату (roomtest.c).
Поэтому чомперы щёлкают только пока персонаж в ИХ ряду — так в оригинале.

check_chomped_guard у оригинала отдельное тело (страж не проходит через
check_collisions).  У нас та же формула уже есть в get_row_collision_data,
поэтому флаги ряда считаются во ВРЕМЕННЫЙ массив: coll_curr/above/below —
это кадр Кида, из них move_coll_to_prev берёт прошлые флаги, затирание
сломало бы ему бамп.

Период смыкания — POP_CHOMPER_SPEED в pop_tune.h (15, как в оригинале).

Заодно: устаревшая заглушка pop_fore_set_clip в tests-host была __banked,
хотя функция давно переехала в резидент — всплыло при пересборке.

Ассеты уже были упакованы (pop_pack_bg.py, 2026-08-07).  Банк 6: 20.0 %,
банк 7: 39.7 %, банк 3: 53.8 %.  tests-host зелёные (5 наборов).
Зубья в MAME рисуются; анимация и смерть — на ручной приёмке.
2026-08-08 19:05:16 +03:00

901 lines
55 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_room.c — ХОЛОДНАЯ половина слоя фона PoP: полная отрисовка комнаты
* (порт seg008.c draw_tile / render_room.py), точечные перерисовки тайлов
* при событиях (пики, ворота, кнопки, дверь уровня, дрожащие плиты) и
* падающие куски loose (mob, seg007 move_loose). Всё это работает раз на
* комнату или раз на событие, поэтому живёт в ОТДЕЛЬНОМ банке и платит
* трамплин за вызовы горячей половины — см. _pop_bg.h, там же граница реза.
*
* Только DUNGEON-стиль (Фаза 1).
*/
#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_geom.h"
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */
#include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */
static const char *const ENV_ATL[5] = {
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон,
* KID — персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска
* зарастает десятками .atl. */
"BG\\pop_env0.atl", "BG\\pop_env1.atl", "BG\\pop_env2.atl",
"BG\\pop_env3.atl", "BG\\pop_env4.atl"
};
int pop_bg_load(void) __banked
{
int i;
for (i = 0; i < 5; i++)
if (atlas_load(&pop_env[i], ENV_ATL[i]) != 0) return -1;
if (atlas_load(&pop_wall_at, "BG\\pop_wall.atl") != 0) return -1;
if (atlas_load(&pop_fore_at, "BG\\pop_fore.atl") != 0) return -1;
if (atlas_load(&pop_pot_at, "BG\\pop_pot.atl") != 0) return -1;
return 0;
}
void pop_bg_free(void) __banked
{
int i;
for (i = 0; i < 5; i++)
atlas_free(&pop_env[i]);
atlas_free(&pop_wall_at);
atlas_free(&pop_fore_at);
atlas_free(&pop_pot_at);
}
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); }
/* topright тайла снизу-слева (seg008.c:410). */
static void draw_topright(uint8_t tt, int x, int dby)
{
if (tt == 7 || tt == 12) 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) — одна клетка ------------------------ */
static void draw_tile(int row, int col)
{
uint8_t code = pop_tile_code(row, col);
uint8_t lcode = pop_tile_code(row, col - 1);
uint8_t lmod = pop_tile_mod(row, col - 1);
/* Нажатая кнопка (seg008 get_tile_to_draw): при таймере связи >1 opener
* (0x0F) рисуется как ПОЛ (modif 0), closer (0x06) — как stuck (0x05).
* Модификатор кнопки = индекс LINKLOC. И для самого тайла, и для левого
* соседа (его правая грань). */
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; }
if (lcode == 0x0F) { if ((pop_doorlink2(lmod) & 0x1F) > 1) { lcode = 1; lmod = 0; } }
else if (lcode == 0x06) { if ((pop_doorlink2(lmod) & 0x1F) > 1) lcode = 5; }
int xh = POP_COL_XH[col];
int x = xh * 8;
int dby = 63 * row + 65;
int dmy = dby - 3;
const piece *t = &pop_tile_table[code];
const piece *lt = &pop_tile_table[lcode];
uint8_t rbl_code;
uint8_t wmod;
/* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0
* комнаты снизу (load_rowbelow, seg008:368): row_below_left_[col] =
* room_B(0, col-1); col-1<0 → левая комната снизу = стена уровня. */
if (row + 1 > 2) {
int lc = col - 1;
/* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате
* (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда;
* «стена» там стоит, только если такой комнаты нет — как в
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20;
else rbl_code = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 1;
} else rbl_code = pop_tile_code(row + 1, col - 1);
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
if (code == 0 || code == 9 || code == 12 || code == 26) {
draw_topright(rbl_code, x, dby);
/* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op
* (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет
* floor_right: lt->floor_right. */
}
/* --- draw_tile_right (seg008.c:456) --- */
if (code != 20) {
if (lcode == 20) {
pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */
} else if (lcode == 0) {
if (lmod <= 3 && BLUELINE_FRAM1[lmod])
pop_env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy);
} else if (lcode == 1) {
uint8_t num;
pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */
num = (lmod <= 3) ? lmod : 0;
if (num != 0) /* dungeon: level_type=0 */
pop_env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */
} else {
if (lt->right_id)
pop_env_b(lt->right_id, x, lt->right_y + dmy);
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
if (lcode == 19 || lcode == 30)
pop_env_b(146, x, dby - 28);
/* stripe — только палас, dungeon пропускает */
}
}
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
* верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от
* отрицательного индекса в pop_loose_modif. --- */
if (row >= 0) {
if (lcode == 4) { /* gate: решётка */
pop_gate_back_b(lmod, xh, dby, dmy);
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[row * 10 + (col - 1)])], x, dby - 1);
} else if (lcode == 2) { /* spike: статич. кадр */
uint8_t sf = (lmod & 0x80) ? 5 : lmod;
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
pop_env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
} else if (lcode == 16) { /* дверь уровня */
draw_leveldoor(lmod, xh, dmy);
}
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
* нового. Рисует pop_torch_draw (динамика, pop_process_trobs). */
}
/* --- draw_tile_bottom (seg008.c:570) --- */
if (code == 20) {
wmod = pop_wall_modifier(row, col);
pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby); /* сплошная нижняя грань */
pop_wall_pattern_b(row, col, 0);
} else if (t->bottom_id) {
pop_env_b(t->bottom_id, x, dby);
}
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
* верхней полосе (row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
* её состояние живёт отдельно, в pop_ceil_modif[col] (pop_map). --- */
if (code == 11)
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(row >= 0 ? pop_loose_modif[row * 10 + col]
: pop_ceil_modif[col])], x, dby);
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (row<0) базу НЕ
* рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */
if (row >= 0) {
uint8_t base_id = t->base_id;
if (code == 11) /* loose: левая грань (анимир.) */
base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])];
else if (code == 0x0F && lcode == 0) /* opener, слева пусто */
base_id = 148; /* левая половина без пола */
if (base_id)
pop_env_b(base_id, x, t->base_y + dmy);
}
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
* рисуется только правым соседом со сдвигом → шахта над самой пикой
* пустая. */
if (row >= 0 && code == 2) {
uint8_t sm = pop_tile_mod(row, 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], x, dmy - 2);
}
/* Чомпер (тот же draw_tile_anim, case tiles_18_chomper): нижняя челюсть
* на draw_main_y, поверх неё кровь (если кого-то перемололо), затем
* верхняя челюсть со своим подъёмом. Всё это BACKtable — то есть ПОД
* персонажем; передний слой рисует fore-проход. */
if (row >= 0 && code == 0x12) {
uint8_t cm = pop_tile_mod(row, col);
uint8_t pose = pop_chomp_pose(cm);
pop_env_b(CHOMP_FRAM_BOT[pose], x, dmy);
if (cm & 0x80)
pop_env_b((uint8_t)(pose + 114), x + 8, dmy - 6);
if (CHOMP_FRAM_TOP[pose])
pop_env_b(CHOMP_FRAM_TOP[pose], x, 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 (row >= 0 && code == 22)
pop_pot_b((uint8_t)((pop_tile_mod(row, col) == 1) + 10), x, dmy - 3);
/* --- draw_tile_fore (seg008.c:690) --- */
if (code == 20) {
wmod = pop_wall_modifier(row, col);
pop_wall_b(POP_WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
pop_wall_pattern_b(row, col, 1);
}
if (code == 10) { /* зелье: chtab_1, не env fore_id.
* Пузырёк — динамика (pop_potion_draw). */
pop_potion_flask(row, col, pop_tile_mod(row, col));
} else if (t->fore_id) {
pop_fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
}
}
/* ---- 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(void) __banked
{
pop_cd_touch(0, POP_YOFF + 3, 32, 62); /* bar мимо pop_blit_b (DRAW-COST) */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
/* Полосу кладки У ПОТОЛКА (ряд −1) НЕ трогаем: её низ на room-space y=2,
* а бары ворот начинаются с y=3 (gate_top_y = dby62). Раньше bar шёл от
* POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать ещё и
* draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации решётки. */
bar(0, POP_YOFF + 3, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
gfx_set_bank(GFX_BANK_NORMAL);
}
/* ---- Плита-потолок (loose ряда 2 комнаты СВЕРХУ) ------------------- *
* В полосе кладки у потолка (наш ряд −1) виден только НИЗ плиты
* (draw_tile_aboveroom: bottom+loose, всё выше отрезано клипом POP_YOFF).
* Дрожащий кадр берётся из pop_ceil_modif[col] (см. draw_loose выше).
* shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
void pop_ceil_shake_draw(int col) __banked
{
int x = POP_COL_XH[col] * 8;
pop_heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
gfx_set_bank(GFX_BANK_SPRITE);
pop_t_clip_top = POP_YOFF;
draw_tile(-1, col);
if (col + 1 < 10) draw_tile(-1, col + 1);
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;
pop_cd_touch(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* bar мимо pop_blit_b */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, POP_YOFF, x + 63, POP_YOFF + CEIL_BAND_H); /* стереть плиту в ОЗУ-копии */
gfx_set_bank(GFX_BANK_TRANSPARENT);
pop_t_clip_top = POP_YOFF;
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_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_set_bank(GFX_BANK_SPRITE);
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;
if (type == 0) return;
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(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
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;
pop_cd_touch(x, POP_YOFF + 63 * row + 26, 60, 39); /* bar мимо pop_blit_b */
int yb = POP_YOFF + 63 * row;
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, yb + 26, x + 59, yb + 64);
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);
}
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 (dby62 = 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_cd_touch(x, yt + 3, 32, 62); /* bar мимо pop_blit_b (DRAW-COST) */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, yt + 3, x + 31, yt + 64);
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);
bar(x, yb, x + 39, POP_YOFF + 63 * row + 65); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
if (col + 1 < 10)
bar(xr, yb, xr + 31, POP_YOFF + 63 * row + 65); /* сосед: РОВНО 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 → мерцание), а щебень
* появлялся только под одной плитой. 4 слота: больше одновременно падающих
* плит в одной комнате практически не бывает (в оригинале 14). */
#define MOB_MAX 4
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 mob_t mobs[MOB_MAX];
/* Кусок «приземлился» (порт 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
void pop_loose_mob_spawn(int row, int col) __banked
{
uint8_t i;
mob_t *m = 0;
/* свободный слот = не летит И дочистка завершена (иначе затрём хвост,
* который ещё не стёрт со второй страницы) */
for (i = 0; i < MOB_MAX; i++)
if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; }
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 = pop_t_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;
}
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26)
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46:
* ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
#define MOB_X0(mx) ((mx) - 4)
#define MOB_W 64 /* mob_x-4 .. mob_x+59 (крышка +57 с запасом 2px) */
/* Остановить падающий кусок (при смене комнаты — состояние 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);
}
static void mob_tick_one(mob_t *m, uint8_t pg)
{
uint8_t here;
if (!m->active && !m->clean) return;
here = (uint8_t)(m->room == pop_t_room);
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
* прошлой позиции. */
if (here && m->prev_y[pg] != MOB_Y_NONE) {
pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - 27, MOB_W, 32);
m->prev_y[pg] = MOB_Y_NONE;
}
if (!m->active) { m->clean--; return; }
/* гравитация (move_loose): ускорение до 29 */
if (m->speed < 29) m->speed += 3;
m->y += m->speed;
/* move_loose (seg007:1126): низ куска пересёк границу своего ряда —
* смотрим тайл этого ряда. Пусто/loose — валится дальше (порт
* loose_fall «плита ломает плиту» не нужен: в ряду ниже её нет);
* пол — приземление (loose_land: debris + do_knock). */
if (m->row < 3 && m->y < 226 && MOB_Y_BOUND[m->row + 1] <= m->y) {
uint8_t t = mob_tile_at(m);
if (t == 0 || t == 11) {
m->row++; /* mob_down_a_row */
} else if (!here) {
/* Приземление в ПОКИНУТОЙ комнате: рисовать нечего, сигнальная
* пара pop_loose_landed/pop_debris_at обслуживает только текущую
* комнату — щебень кладём прямо в данные уровня (loose_land пишет
* в curr_room_tiles комнаты curmob.room, а не отрисованной). */
pop_level_set_tile(m->room, (uint8_t)(m->row * 10 + m->col), 0x0E);
m->active = 0; m->clean = 0;
return;
} else if (pop_loose_landed) {
/* сигнал прошлого приземления ещё не разобран (один за кадр) —
* подержим кусок на границе и приземлим следующим кадром */
m->y = MOB_Y_BOUND[m->row + 1];
} else {
m->y = MOB_Y_BOUND[m->row + 1];
pop_loose_landed = (uint8_t)(m->row * 10 + m->col + 1);
m->active = 0; m->clean = 2; /* кусок исчез — на его месте debris */
return;
}
}
if (m->y - 27 >= POP_PLAYFIELD_H) { /* весь ниже поля (улетел из комнаты) */
m->active = 0; m->clean = (uint8_t)(here ? 2 : 0);
return;
}
if (!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 = (int8_t)(((m->y + 60) / 63) % 4 - 1); /* y_to_row_mod4 */
uint8_t over;
if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */
else if (m->col != pop_bg_obj_col) over = (m->col > pop_bg_obj_col);
else over = (m->y > pop_cd[POP_CH_KID].fpy);
if (over) { m->defer = 1; return; }
}
mob_render(m, pg);
}
void pop_loose_mob_tick(void) __banked
{
uint8_t pg = gfx_get_draw_page() & 1, i;
for (i = 0; i < MOB_MAX; i++) {
/* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом,
* мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя
* (DRAW-COST). Взводим ДО тика: кусок может исчезнуть прямо в нём, а
* стереть себя ему всё равно надо будет. */
if (mobs[i].active || mobs[i].clean)
pop_cd_touch(MOB_X0(mobs[i].x), mobs[i].y - 27 + POP_YOFF, MOB_W, 64);
mob_tick_one(&mobs[i], pg);
}
}
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);
pop_env_b(43, m->x, m->y);
pop_env_b(41, m->x, m->y - 3);
pop_env_b(42, m->x + 32, m->y - 1);
m->prev_y[pg] = m->y;
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
* её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
* до heal'а следующего кадра — виден как хвост во время падения). */
if (m->col + 1 < 10) {
/* Ряд соседа берём ИЗ КООРДИНАТЫ куска (y_to_row_mod4(ypos)), а НЕ из
* m->row: последний — логический счётчик «сквозь какой ряд летим», и
* mob_down_a_row уводит его на ряд ВПЕРЁД (для плиты нижнего ряда —
* сразу в 3, которого нет). Тогда пол соседа не перерисовывался
* вообще, и правый кусок 42 оставался ПОВЕРХ него: грань плиты видна
* через пол + перекрыта передняя грань пола.
* Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на
* границе рядов. Порт draw_mob (seg007:13E5). */
int8_t r = (int8_t)(((m->y + 60) / 63) % 4 - 1);
int8_t rt = (int8_t)(((m->y - 18 + 60) / 63) % 4 - 1);
if (r < 0) pop_t_clip_top = POP_YOFF; /* полоса у потолка — только срез */
draw_tile(r, m->col + 1);
pop_t_clip_top = 0;
if (rt != r) {
if (rt < 0) pop_t_clip_top = POP_YOFF;
draw_tile(rt, m->col + 1);
pop_t_clip_top = 0;
}
}
gfx_set_bank(GFX_BANK_NORMAL);
/* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */
pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30);
}
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
* после отрисовки персонажей, до fore-слоя. */
void pop_loose_mob_draw_over(void) __banked
{
uint8_t pg = gfx_get_draw_page() & 1, i;
for (i = 0; i < MOB_MAX; i++)
if (mobs[i].defer) { mobs[i].defer = 0; mob_render(&mobs[i], pg); }
}
/* Позиция падающего куска для 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);
}