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>
1150 lines
71 KiB
C
1150 lines
71 KiB
C
/*
|
||
* pop_bg.c — ГОРЯЧАЯ половина слоя фона PoP: то, что работает КАЖДЫЙ кадр —
|
||
* передний слой поверх персонажей (порт seg003 redraw_at_char + seg008
|
||
* set_char_collision), оверлеи кромки, кладка стены и клип. Полная
|
||
* отрисовка комнаты и точечные перерисовки уехали в pop_room.c (банк 7);
|
||
* граница реза и её цена расписаны в _pop_bg.h.
|
||
*
|
||
* Подробности алгоритма кладки — в toolchain/render_room.py и
|
||
* memory/pop_background_strategy. Только 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_room.c, банк 7) */
|
||
#include "pop_tile.h"
|
||
#include "pop_map.h" /* pop_upside — переворот (зелье инверсии) */ /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
||
#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-порядок плиты) */
|
||
#include "pop_guard.h" /* SWORD_2_DRAWN — расширение футпринта под клинок */
|
||
|
||
|
||
/* Область, куда в этом кадре рисовался ОВЕРЛЕЙ (не футпринт fore, а
|
||
* draw_other_overlay/floor_overlay/полоса потолка). Оверлей включается
|
||
* только в позах виса/полёта/подъёма и рисует НЕ по прямоугольнику Kid,
|
||
* поэтому heal персонажа его следы не убирает: на второй странице дабл-буфера
|
||
* остаётся кадр «с оверлеем», и он мерцает через кадр. Копим bbox и
|
||
* восстанавливаем фон на ОБЕИХ страницах (pop_fore_heal). */
|
||
static int ov_x0, ov_y0, ov_x1, ov_y1;
|
||
static uint8_t ov_pending; /* биты страниц, где надо восстановить фон */
|
||
/* Подавить ov_mark на время серии — для вызывающего, который САМ чистит эту
|
||
* область каждый кадр. Так работает падающий кусок: его heal-коридор
|
||
* прочёсывается всегда, а оверлей соседа и кромка потолка рисуются ВНУТРИ
|
||
* коридора (окно клипа = прямоугольник куска). Без этого объединение ov_mark
|
||
* по шести кускам растягивалось на полкомнаты, и pop_fore_heal стоил 187 500
|
||
* такта в фазе «ввод+heal» против 55 000 (замер 2026-08-17). */
|
||
static uint8_t ov_suppress;
|
||
|
||
static void ov_mark(int x, int y, int w, int h)
|
||
{
|
||
if (ov_suppress) return;
|
||
if (!ov_pending) { ov_x0 = x; ov_y0 = y; ov_x1 = x + w; ov_y1 = y + h; }
|
||
else {
|
||
if (x < ov_x0) ov_x0 = x;
|
||
if (y < ov_y0) ov_y0 = y;
|
||
if (x + w > ov_x1) ov_x1 = x + w;
|
||
if (y + h > ov_y1) ov_y1 = y + h;
|
||
}
|
||
ov_pending = 3; /* обе страницы */
|
||
}
|
||
|
||
void pop_fore_heal(void) __banked
|
||
{
|
||
uint8_t pg = gfx_get_draw_page() & 1;
|
||
if (!(ov_pending & (1 << pg))) return;
|
||
pop_heal_off(ov_x0, ov_y0, ov_x1 - ov_x0, ov_y1 - ov_y0);
|
||
ov_pending = (uint8_t)(ov_pending & ~(1 << pg));
|
||
/* Восстановленный прямоугольник шире персонажа и запросто накрывает
|
||
* СОСЕДА — пометить область (DRAW-COST): heal идёт мимо pop_blit_b. */
|
||
pop_cd_touch(ov_x0, ov_y0 + POP_YOFF, ov_x1 - ov_x0, ov_y1 - ov_y0);
|
||
}
|
||
|
||
|
||
/* ---- Константные таблицы (render_room.py) -------------------------- */
|
||
static const uint8_t TBL_LINE[3] = {0, 10, 20};
|
||
static const uint8_t DOOR_FRAM_SLICE[9] = {67, 59, 58, 57, 56, 55, 54, 53, 52};
|
||
static const uint8_t SPIKES_FRAM_FORE[10] = {0, 139, 140, 141, 142, 143, 142, 140, 139, 0};
|
||
static const uint8_t LPOS[5] = {58, 41, 37, 20, 16};
|
||
static const uint8_t RPOS[4] = {52, 42, 31, 21};
|
||
|
||
/* Wall (chtab_7) id: делители/блок/марки. */
|
||
#define RES_WALL_RNDBLOCK 13
|
||
#define RES_WALL_DIVIDER1 11
|
||
#define RES_WALL_MARK_TL 14
|
||
#define RES_WALL_MARK_BL 15
|
||
#define RES_WALL_MARK_TR 16
|
||
#define RES_WALL_MARK_BR 17
|
||
|
||
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- *
|
||
* Сид (pop_t_seed) живёт в резиденте: его ставит полная отрисовка комнаты, а
|
||
* читает кэш раскладки кладки. */
|
||
#define prandom(maxv) pop_prandom(&pop_t_seed, (maxv))
|
||
|
||
/* Отсев кусков кладки по окну fore-клипа — тела ниже, у wpp_fill. */
|
||
static uint8_t wp_vis(int x, int w, int ytop, int ybot);
|
||
static void wp_blit(uint8_t id, int x, int ybot, uint8_t w, uint8_t h);
|
||
|
||
/* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */
|
||
static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col)
|
||
{
|
||
uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BL : RES_WALL_MARK_TL;
|
||
int lv2 = 0;
|
||
int xh = POP_COL_XH[col];
|
||
int dby = 63 * row + 65;
|
||
if (dv > 3) lv2 = arg1 + 6;
|
||
else if (dv > 1) lv2 = arg2 + 6;
|
||
wp_blit(image_id, (xh + ((dv == 2 || dv == 3) ? 1 : 0)) * 8 + lv2,
|
||
dby - LPOS[dv], 7, 10); /* pop_wall.atl: 16/17 = 7x10 */
|
||
}
|
||
|
||
static void draw_right_mark(uint16_t dv, int arg1, int row, int col)
|
||
{
|
||
uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BR : RES_WALL_MARK_TR;
|
||
int a = (dv < 2) ? 24 : arg1 - 3;
|
||
int xh = POP_COL_XH[col];
|
||
int dby = 63 * row + 65;
|
||
wp_blit(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a,
|
||
dby - RPOS[dv], 14, 5); /* pop_wall.atl: 14/15 = 14x5 */
|
||
}
|
||
|
||
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
|
||
/* КЭШ РАСКЛАДКИ КЛАДКИ. Серия prandom для тайла зависит только от
|
||
* (комната, ряд, колонка) — пока мы в комнате, это КОНСТАНТА. А
|
||
* wall_pattern зовётся из fore-слоя КАЖДЫЙ кадр, и на Z80 это самая
|
||
* дорогая вещь в кадре: замер в MAME 2026-07-29 (комната 3) — 18 вызовов
|
||
* pop_prandom за один fore-проход, ~10 700 тактов каждый (32-битный LCG:
|
||
* __mullong ~8 000 + __moduint), то есть 193 К из 341 К всего прохода при
|
||
* бюджете кадра ~430 К. Кэшируем не сид, а ГОТОВЫЕ РЕШЕНИЯ (что рисовать
|
||
* и с каким смещением) — тогда в кадре не остаётся ни одного prandom.
|
||
*
|
||
* Порядок вызовов prandom воспроизведён один в один, включая то, что
|
||
* значение метки берётся ТОЛЬКО когда сработало её условие: иначе
|
||
* последовательность разъедется и кладка станет другой. which_part=0
|
||
* использует префикс той же серии, поэтому кэш общий.
|
||
*
|
||
* Упаковка: wp_a = mid_div | mid_off<<1 | bot_div<<4 | bot_off<<5;
|
||
* wp_b = rndblk | right<<1 | right_val<<2 | left<<4 | left_val<<5.
|
||
* Индекс — (row+1)*10+col, ряды −1..2. */
|
||
#define WP_N 40
|
||
static uint8_t wp_a[WP_N], wp_b[WP_N], wp_ok[WP_N];
|
||
|
||
/* ---- Цвета дворцовой кладки (gen_palace_wall_colors, seg000:1942) ---- *
|
||
* Тело стены во дворце — не кирпичи-спрайты, а СПЛОШНЫЕ ЗАЛИВКИ; их цвета
|
||
* разыгрываются на КОМНАТУ: сид = номер комнаты, 3 ряда × 4 подряда × 11
|
||
* колонок. Подряды 1 и 3 берут 0x61..0x64, подряды 0 и 2 — 0x66..0x69
|
||
* (это слоты wall-палитры, во дворце они песочные); соседние по горизонтали
|
||
* не повторяются. Одиннадцать колонок, а не десять: заливки 3 и 5 берут
|
||
* цвет СЛЕДУЮЩЕЙ колонки.
|
||
*
|
||
* Не static: таблицу читает и холодная половина слоя (pop_room.c, банк 7).
|
||
* Writable-данные банка живут в _DATA/W2 и видны отовсюду — в отличие от
|
||
* const-таблиц, которые из чужого банка не прочитать. */
|
||
uint8_t pop_pal_wall_col[3 * 44];
|
||
|
||
static void gen_palace_wall_colors(uint8_t room)
|
||
{
|
||
pop_rnd_t saved = pop_t_seed;
|
||
uint8_t row, sub, col;
|
||
uint8_t *p = pop_pal_wall_col;
|
||
pop_prandom_set(pop_t_seed, room);
|
||
prandom(1); /* сброс, как в оригинале */
|
||
for (row = 0; row < 3; row++)
|
||
for (sub = 0; sub < 4; sub++) {
|
||
uint8_t base = (uint8_t)((sub & 1) ? 0x61 : 0x66);
|
||
uint8_t prev = 0; /* 0 — «цвета ещё не было» */
|
||
for (col = 0; col < 11; col++) {
|
||
uint8_t c;
|
||
do { c = (uint8_t)(base + prandom(3)); } while (c == prev);
|
||
*p++ = c;
|
||
prev = c;
|
||
}
|
||
}
|
||
pop_t_seed = saved;
|
||
}
|
||
|
||
static void wall_pattern_reset(void) /* смена комнаты — сид другой */
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < WP_N; i++) wp_ok[i] = 0;
|
||
if (pop_palace) gen_palace_wall_colors(pop_t_room);
|
||
}
|
||
|
||
/* Выходы wall_rnd — в файловых статиках: возвращать девять значений
|
||
* через указатели на Z80 дороже самой функции. */
|
||
static uint8_t wr_mid_div, wr_mid_off, wr_bot_div, wr_bot_off;
|
||
static uint8_t wr_rndblk, wr_right, wr_rightv, wr_left, wr_leftv;
|
||
|
||
/* Правила серии prandom по модификатору стены (bg): что вообще есть у этого
|
||
* вида кладки и с какими порогами. Столбцы:
|
||
* [0] есть «случайный блок» (тогда prandom(4)==0)
|
||
* [1] порог условия метки СПРАВА (0 = метки справа нет, prandom не зовётся)
|
||
* [2] порог значения метки справа, [3] прибавка к нему
|
||
* [4] порог условия метки СЛЕВА (есть у всех четырёх), [5] порог значения
|
||
* Порядок строк — по bg: 0 SWS, 1 SWW, 2 WWS, 3 WWW (seg008:1928). */
|
||
static const uint8_t WR_RULE[4][6] = {
|
||
{0, 0, 0, 0, 6, 1}, /* SWS: только метка слева */
|
||
{1, 4, 3, 0, 4, 3}, /* SWW */
|
||
{0, 4, 1, 2, 4, 4}, /* WWS: значение справа со сдвигом 2 */
|
||
{1, 4, 3, 0, 4, 4}, /* WWW: полный набор */
|
||
};
|
||
|
||
static void wall_rnd(int row, int col)
|
||
{
|
||
/* Кэшируем только штатную сетку: колонка вне 0..9 дала бы индекс
|
||
* соседнего РЯДА и чужую раскладку (тихий баг вместо промаха). */
|
||
int idx = (col >= 0 && col < 10) ? (row + 1) * 10 + col : -1;
|
||
uint8_t a, b, bg;
|
||
pop_rnd_t saved;
|
||
if (idx >= 0 && idx < WP_N && wp_ok[idx]) {
|
||
a = wp_a[idx]; b = wp_b[idx];
|
||
wr_mid_div = (uint8_t)(a & 1); wr_mid_off = (uint8_t)((a >> 1) & 7);
|
||
wr_bot_div = (uint8_t)((a >> 4) & 1); wr_bot_off = (uint8_t)((a >> 5) & 7);
|
||
wr_rndblk = (uint8_t)(b & 1);
|
||
wr_right = (uint8_t)((b >> 1) & 1); wr_rightv = (uint8_t)((b >> 2) & 3);
|
||
wr_left = (uint8_t)((b >> 4) & 1); wr_leftv = (uint8_t)((b >> 5) & 7);
|
||
return;
|
||
}
|
||
saved = pop_t_seed;
|
||
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
|
||
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
|
||
pop_prandom_set(pop_t_seed, pop_t_room + TBL_LINE[row < 0 ? 2 : row] + col);
|
||
prandom(1); /* сброс */
|
||
wr_mid_div = (uint8_t)prandom(1);
|
||
wr_mid_off = (uint8_t)prandom(4);
|
||
wr_bot_div = (uint8_t)prandom(1);
|
||
wr_bot_off = (uint8_t)prandom(4);
|
||
wr_rndblk = wr_right = wr_left = 0;
|
||
wr_rightv = wr_leftv = 0;
|
||
bg = pop_wall_modifier(row, col);
|
||
{ /* Четыре ветки bg отличались ТОЛЬКО числами в одной и той же серии
|
||
* prandom, поэтому они сведены к правилу (WR_RULE). ПОРЯДОК вызовов
|
||
* prandom обязан совпадать с оригиналом дословно, включая то, что
|
||
* значение метки берётся ТОЛЬКО когда сработало её условие: иначе
|
||
* последовательность разъедется и кладка станет другой. «Нет метки
|
||
* справа» = нулевой порог условия — тогда prandom не зовётся вовсе,
|
||
* ровно как в ветке SWS. */
|
||
const uint8_t *r = WR_RULE[bg];
|
||
if (r[0]) wr_rndblk = (uint8_t)(prandom(4) == 0);
|
||
if (r[1] && prandom(r[1]) == 0) {
|
||
wr_right = 1; wr_rightv = (uint8_t)(prandom(r[2]) + r[3]);
|
||
}
|
||
if (prandom(r[4]) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(r[5]); }
|
||
}
|
||
pop_t_seed = saved;
|
||
if (idx >= 0 && idx < WP_N) {
|
||
wp_a[idx] = (uint8_t)(wr_mid_div | (wr_mid_off << 1) |
|
||
(wr_bot_div << 4) | (wr_bot_off << 5));
|
||
wp_b[idx] = (uint8_t)(wr_rndblk | (wr_right << 1) | (wr_rightv << 2) |
|
||
(wr_left << 4) | (wr_leftv << 5));
|
||
wp_ok[idx] = 1;
|
||
}
|
||
}
|
||
|
||
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- *
|
||
* Куски кладки одни и те же для всех четырёх видов стены, различается ровно
|
||
* НАБОР присутствующих (WP_PARTS) — раньше это были четыре ветки с
|
||
* повторяющимся телом. Порядок кусков внутри вызова сохранён: случайный
|
||
* блок, средний делитель, нижний делитель, метка справа, метка слева.
|
||
*
|
||
* which_part == 0 — только нижний делитель (слой draw_tile_bottom), 1 — всё
|
||
* остальное (draw_tile_fore). У SWW нижнего делителя нет вовсе, у SWS —
|
||
* ничего, кроме метки слева. */
|
||
#define WP_RND 1
|
||
#define WP_MID 2
|
||
#define WP_BOT 4
|
||
#define WP_RIGHT 8
|
||
#define WP_LEFT 16
|
||
|
||
static const uint8_t WP_PARTS[4] = {
|
||
WP_LEFT, /* 0 SWS */
|
||
WP_RND | WP_MID | WP_RIGHT | WP_LEFT, /* 1 SWW */
|
||
WP_MID | WP_BOT | WP_RIGHT | WP_LEFT, /* 2 WWS */
|
||
WP_RND | WP_MID | WP_BOT | WP_RIGHT | WP_LEFT, /* 3 WWW */
|
||
};
|
||
|
||
/* ---- Кладка ДВОРЦА (seg008:1946) ------------------------------------ *
|
||
* Тело стены — не кирпичи-спрайты, а шесть СПЛОШНЫХ ЗАЛИВОК цветами из
|
||
* pop_pal_wall_col, поверх — пять разделителей chtab_7 (mono, цвет 6).
|
||
* Геометрия дословно из add_wipetable(layer, left, bottom, height, width):
|
||
* прямоугольник = x..x+width-1, (bottom-height+1)..bottom.
|
||
*
|
||
* Порядок prandom важен так же, как в подземелье: при which_part == 0
|
||
* разыгрывается ОДНО значение, и нижний разделитель берёт ПЕРВОЕ из серии,
|
||
* а не пятое. */
|
||
static uint8_t wpp_d[5]; /* пять prandom(2), 0..2 */
|
||
|
||
static void wall_rnd_palace(int row, int col)
|
||
{
|
||
int idx = (col >= 0 && col < 10) ? (row + 1) * 10 + col : -1;
|
||
pop_rnd_t saved;
|
||
uint8_t i;
|
||
if (idx >= 0 && idx < WP_N && wp_ok[idx]) {
|
||
uint8_t a = wp_a[idx];
|
||
wpp_d[0] = (uint8_t)(a & 3); wpp_d[1] = (uint8_t)((a >> 2) & 3);
|
||
wpp_d[2] = (uint8_t)((a >> 4) & 3); wpp_d[3] = (uint8_t)((a >> 6) & 3);
|
||
wpp_d[4] = wp_b[idx];
|
||
return;
|
||
}
|
||
saved = pop_t_seed;
|
||
pop_prandom_set(pop_t_seed, pop_t_room + TBL_LINE[row < 0 ? 2 : row] + col);
|
||
prandom(1); /* сброс */
|
||
for (i = 0; i < 5; i++) wpp_d[i] = (uint8_t)prandom(2);
|
||
pop_t_seed = saved;
|
||
if (idx >= 0 && idx < WP_N) {
|
||
wp_a[idx] = (uint8_t)(wpp_d[0] | (wpp_d[1] << 2) |
|
||
(wpp_d[2] << 4) | (wpp_d[3] << 6));
|
||
wp_b[idx] = wpp_d[4];
|
||
wp_ok[idx] = 1;
|
||
}
|
||
}
|
||
|
||
/* Прямоугольник [x, x+w) x [ytop, ybot] задевает окно fore-клипа? Клипа
|
||
* нет — задевает всегда. Координаты сюда приходят посчитанными ИЗ РЯДА
|
||
* (63*row + …), то есть логические — значит и рамка нужна логическая
|
||
* (pop_tile.h, пара fclip_l*): переворот применяется позже, при блите. */
|
||
static uint8_t wp_vis(int x, int w, int ytop, int ybot)
|
||
{
|
||
if (!pop_t_fclip_on) return 1;
|
||
return (uint8_t)(x < pop_t_fclip_x1 && x + w > pop_t_fclip_x0 &&
|
||
ytop < pop_t_fclip_ly1 && ybot >= pop_t_fclip_ly0);
|
||
}
|
||
|
||
/* Декаль кладки: ybot — БЕЗ POP_YOFF (как ждёт pop_wall_b), w/h — реальный
|
||
* габарит куска. Размеры взяты из каталогов атласов (pal_wall.atl /
|
||
* pop_wall.atl) и заданы верхней границей по группе, поэтому промахнуться в
|
||
* меньшую сторону нельзя: хуже отсева не будет, лишнего не сотрём.
|
||
*
|
||
* Зачем: pop_blit_b тоже отсеивает до маппинга страницы, но по ЗАВЕДОМО
|
||
* большему габариту 64x64 — куски кладки высотой 3..12 px он пропускает и
|
||
* платит полный atlas_image + gfx_w0_map + чтение w/h (~9 760 тактов на
|
||
* блит), чтобы там обнаружить, что рисовать нечего. Пять таких блитов на
|
||
* тайл — 48 500 тактов в никуда (docs/perf_backlog.md, пункт 0 шаг 2). */
|
||
static void wp_blit(uint8_t id, int x, int ybot, uint8_t w, uint8_t h)
|
||
{
|
||
int yb = ybot + POP_YOFF;
|
||
if (!wp_vis(x, (int)w, yb - (int)h + 1, yb)) return;
|
||
pop_wall_b(id, x, ybot);
|
||
}
|
||
|
||
/* Заливка куска стены. x/ytop — уже ЭКРАННЫЕ (с POP_YOFF). В fore-проходе
|
||
* режем по окну клипа: без этого заливка легла бы поверх областей, которые
|
||
* в этом кадре никто не восстанавливает. */
|
||
static void wpp_fill(int x, int ytop, int w, int h, uint8_t colour)
|
||
{
|
||
int x2 = x + w - 1, y2 = ytop + h - 1;
|
||
if (pop_t_fclip_on) {
|
||
if (x < pop_t_fclip_x0) x = pop_t_fclip_x0;
|
||
if (ytop < pop_t_fclip_ly0) ytop = pop_t_fclip_ly0;
|
||
if (x2 >= pop_t_fclip_x1) x2 = pop_t_fclip_x1 - 1;
|
||
if (y2 >= pop_t_fclip_ly1) y2 = pop_t_fclip_ly1 - 1; /* клип ДО переворота */
|
||
}
|
||
if (pop_t_clip_top && ytop < pop_t_clip_top) ytop = pop_t_clip_top;
|
||
if (x < 0) x = 0;
|
||
if (ytop < 0) ytop = 0;
|
||
if (x2 > 319) x2 = 319;
|
||
if (y2 > 255) y2 = 255;
|
||
if (x2 < x || y2 < ytop) return;
|
||
/* ПЕРЕВОРОТ: заливка — такой же кусок фона, как блит тайла, и обязана
|
||
* отражаться той же формулой (иначе полоса ложится в неотражённое
|
||
* место — так ломались решётка и чомперы). */
|
||
if (pop_upside) {
|
||
int h = y2 - ytop + 1;
|
||
ytop = POP_FLIP_TOP(ytop, h);
|
||
y2 = ytop + h - 1;
|
||
}
|
||
setfillstyle(SOLID_FILL, colour);
|
||
bar(x, ytop, x2, y2);
|
||
if (!pop_t_fclip_on) pop_cd_touch(x, ytop, x2 - x + 1, y2 - ytop + 1);
|
||
}
|
||
|
||
static void wall_pattern_palace(int row, int col, int which_part)
|
||
{
|
||
int xh = POP_COL_XH[col];
|
||
int dbys = 63 * row + 65; /* база БЕЗ POP_YOFF — для блитов */
|
||
int dmys = dbys - 3;
|
||
int dby = dbys + POP_YOFF, dmy = dmys + POP_YOFF; /* экранные — для заливок */
|
||
/* РАННИЙ ВЫХОД по окну fore-клипа. Весь узор лежит в прямоугольнике
|
||
* x [xh*8, xh*8+32), y [dmy-59, dby]: сверху — первая заливка, снизу —
|
||
* нижний декаль, чей низ ровно на dby.
|
||
*
|
||
* Без этой проверки тайл стены под ногами Кида стоил ~60 000 тактов НА
|
||
* НОЛЬ ПИКСЕЛЕЙ: предфильтр fore_tile грубый (габарит 40x78), wpp_fill
|
||
* честно режет по окну, но платит ~3 100 тактов за арифметику клипа, а
|
||
* pop_wall_b об окне не знает вовсе и идёт в atlas_image + gfx_w0_map,
|
||
* чтобы там обнаружить, что рисовать нечего. Два таких тайла (ряд 2 под
|
||
* Кидом попадает в fore-окно всегда) = 139 863 такта за кадр, 32 %
|
||
* растрового кадра. См. docs/perf_backlog.md, пункт 0. */
|
||
if (pop_t_fclip_on) {
|
||
int x0 = xh * 8;
|
||
if (x0 >= pop_t_fclip_x1 || x0 + 32 <= pop_t_fclip_x0 ||
|
||
dmy - 59 >= pop_t_fclip_ly1 || dby + 1 <= pop_t_fclip_ly0)
|
||
return;
|
||
}
|
||
/* Цвета лежат по рядам 0..2; полоса у потолка (row −1) — это ряд 2
|
||
* комнаты сверху, оригинал и сид для неё берёт по ряду 2. */
|
||
const uint8_t *cl = pop_pal_wall_col + 44 * (row < 0 ? 2 : row) + col;
|
||
wall_rnd_palace(row, col);
|
||
if (which_part) {
|
||
wpp_fill(xh * 8, dmy - 59, 32, 20, cl[0]);
|
||
wpp_fill(xh * 8, dmy - 39, 16, 21, cl[11]);
|
||
wpp_fill((xh + 2) * 8, dmy - 39, 16, 21, cl[12]);
|
||
wpp_fill(xh * 8, dmy - 18, 8, 19, cl[22]);
|
||
wpp_fill((xh + 1) * 8, dmy - 18, 24, 19, cl[23]);
|
||
/* Габариты групп из pal_wall.atl: 3..5 = 8x7, 6..8 = 32x12,
|
||
* 9..11 = 32x12 (11-й 28x11), 12..14 = 30x5 (14-й 30x4),
|
||
* 15..17 = 32x3. Берём максимум по группе. */
|
||
wp_blit((uint8_t)(wpp_d[0] + 3), (xh + 3) * 8, dmys - 53, 8, 7);
|
||
wp_blit((uint8_t)(wpp_d[1] + 6), xh * 8, dmys - 34, 32, 12);
|
||
wp_blit((uint8_t)(wpp_d[2] + 9), xh * 8, dmys - 13, 32, 12);
|
||
wp_blit((uint8_t)(wpp_d[3] + 12), xh * 8, dmys, 30, 5);
|
||
}
|
||
wpp_fill(xh * 8, dby - 2, 32, 3, cl[33]);
|
||
wp_blit((uint8_t)((which_part ? wpp_d[4] : wpp_d[0]) + 15), xh * 8, dbys, 32, 3);
|
||
}
|
||
|
||
static void wall_pattern(int row, int col, int which_part)
|
||
{
|
||
int xh = POP_COL_XH[col];
|
||
int dby = 63 * row + 65;
|
||
uint8_t parts;
|
||
if (pop_palace) { wall_pattern_palace(row, col, which_part); return; }
|
||
/* Ранний выход по окну fore-клипа — то же, что у паласной ветки, но свой
|
||
* габарит. Самый верхний кусок здесь — ЛЕВАЯ марка: LPOS[4] = 58 плюс её
|
||
* высота 10, то есть dby+POP_YOFF−67; самый нижний — нижний разделитель,
|
||
* низ ровно на dby+POP_YOFF. По x правая марка уходит до xh*8+46
|
||
* (a = 24 при dv<2, ширина 14), берём 48. */
|
||
{
|
||
int x0 = xh * 8, dbs = dby + POP_YOFF;
|
||
if (!wp_vis(x0, 48, dbs - 67, dbs)) return;
|
||
}
|
||
parts = WP_PARTS[pop_wall_modifier(row, col)];
|
||
|
||
wall_rnd(row, col);
|
||
if (which_part) {
|
||
/* Габариты из pop_wall.atl: 13 = 32x21, 11/12 = 9x21. */
|
||
if ((parts & WP_RND) && wr_rndblk)
|
||
wp_blit(RES_WALL_RNDBLOCK, xh * 8, dby - 42, 32, 21);
|
||
if (parts & WP_MID)
|
||
wp_blit((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div),
|
||
(xh + 1) * 8 + wr_mid_off, dby - 21, 9, 21);
|
||
}
|
||
if (parts & WP_BOT)
|
||
wp_blit((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div),
|
||
xh * 8 + wr_bot_off, dby, 9, 21);
|
||
if (which_part) {
|
||
if ((parts & WP_RIGHT) && wr_right)
|
||
draw_right_mark(wr_rightv, wr_mid_off, row, col);
|
||
if ((parts & WP_LEFT) && wr_left)
|
||
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div,
|
||
wr_bot_off - wr_bot_div, row, col);
|
||
}
|
||
}
|
||
|
||
/* Решётка ворот (portcullis) из ЛЕВОЙ комнаты (seg008.c:1110). */
|
||
static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy)
|
||
{
|
||
int x = xh * 8;
|
||
int gate_top_y = dby - 62;
|
||
int openness = (((modl > 188) ? 188 : modl) >> 2) + 1;
|
||
int gate_bot_y = dmy - openness;
|
||
int yb, gate_frame;
|
||
|
||
if (gate_bot_y + 12 < dmy)
|
||
pop_env_b(50, x, gate_bot_y);
|
||
else
|
||
pop_env_b(51, x, gate_bot_y - 2);
|
||
yb = gate_bot_y - 12;
|
||
if (yb < 192) {
|
||
while (yb >= 0 && yb > 7 && (yb - 7) > gate_top_y) {
|
||
pop_env_b(52, x, yb);
|
||
yb -= 8;
|
||
}
|
||
}
|
||
gate_frame = yb - gate_top_y + 1;
|
||
if (gate_frame > 0 && gate_frame < 9)
|
||
pop_env_b(DOOR_FRAM_SLICE[gate_frame], x, yb);
|
||
}
|
||
|
||
/* Та же решётка, но в ПЕРЕДНИЙ слой — поверх персонажа, стоящего в проёме
|
||
* (draw_gate_fore, seg008:1153). От back-варианта отличается ровно тем, что
|
||
* есть в оригинале: только ПРОЗРАЧНЫЕ куски (51 низ + 52 восьмипиксельные
|
||
* ломти), без непрозрачного 50 и без хвостового DOOR_FRAM_SLICE — фон под
|
||
* баррами уже нарисован, здесь возвращаются только сами прутья.
|
||
* Клип по прямоугольнику персонажа делает pop_t_fclip (см. pop_tile.h). */
|
||
static void draw_gate_fore(uint8_t modl, int xh, int dby, int dmy)
|
||
{
|
||
int x = xh * 8;
|
||
int gate_top_y = dby - 62;
|
||
int openness = (((modl > 188) ? 188 : modl) >> 2) + 1;
|
||
int gate_bot_y = dmy - openness;
|
||
int yb;
|
||
|
||
pop_env_b(51, x, gate_bot_y - 2);
|
||
yb = gate_bot_y - 12;
|
||
if (yb < 192)
|
||
while (yb >= 0 && yb > 7 && (yb - 7) > gate_top_y) {
|
||
pop_env_b(52, x, yb);
|
||
yb -= 8;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/* ---- Точки входа для ХОЛОДНОЙ половины (pop_room.c, банк 7) ---------- *
|
||
* Тонкие обёртки, а не пометка самих тел `__banked`: трамплин выбирается
|
||
* ОБЪЯВЛЕНИЕМ, поэтому пометка тела заставила бы платить 654 такта и
|
||
* ГОРЯЧИХ вызывающих — fore-проход зовёт wall_pattern до девяти раз за кадр.
|
||
* С обёрткой горячий путь идёт прямым call, а трамплин достаётся только
|
||
* холодному (полная отрисовка комнаты: ~40 вызовов, 26 000 тактов РАЗОВО на
|
||
* вход в комнату). См. _pop_bg.h и memory/sdcc_banked_call_rules. */
|
||
void pop_wall_pattern_b(int row, int col, int which_part) __banked
|
||
{
|
||
wall_pattern(row, col, which_part);
|
||
}
|
||
|
||
void pop_wall_pattern_reset_b(void) __banked
|
||
{
|
||
wall_pattern_reset();
|
||
}
|
||
|
||
void pop_gate_back_b(uint8_t modl, int xh, int dby, int dmy) __banked
|
||
{
|
||
draw_gate_back(modl, xh, dby, dmy);
|
||
}
|
||
|
||
/* ---- fore-over-Kid (порт seg003 redraw_at_char + seg008 set_char_collision) --
|
||
* Передний слой (fore_id = foretable-кусок) тайлов ФУТПРИНТА спрайта Kid
|
||
* — рядов top..bottom × колонок left..right (<= 2×2 = 4 тайла) — рисуется
|
||
* ПОВЕРХ Kid: то, что по изометрии перед ним (передние грани колонн/ворот/
|
||
* большой колонны/щебня). Стены и факелы имеют fore_id=0 → остаются сзади.
|
||
* Рисуем в GFX_BANK_SPRITE (видео-ОЗУ, 0xFF прозрачен); heal персонажа восстановит
|
||
* из теневого фона (fore в нём уже запечён pop_room_draw). obj_x — ЛОГИЧЕСКАЯ
|
||
* X кадра Kid (до ×8/7), obj_y — низ спрайта, w/h — размер, dir — направление. */
|
||
static int8_t col_from_x(int xpos) /* get_tile_div_mod: колонка (58/14) */
|
||
{
|
||
/* Таблицей (резидентная POP_TILE_DIV, см. pop_tile.h) — как в оригинале.
|
||
* Было `/14` и `%14`: два полноценных 16-битных деления SDCC на вызов,
|
||
* а зовётся это дважды на каждый футпринт персонажа. */
|
||
if ((unsigned int)xpos < 256u) return POP_TILE_DIV[xpos];
|
||
{
|
||
int x = xpos - 58;
|
||
int xh = x / 14;
|
||
if (x % 14 < 0) xh--; /* округление вниз для отрицательных */
|
||
return (int8_t)xh;
|
||
}
|
||
}
|
||
|
||
#define y_to_row(y) pop_y_to_row((int16_t)(y)) /* общий (pop_geom) */
|
||
|
||
/* Контекст ТЕКУЩЕГО тайла прохода: код, левый край, база Y. Порт глобалов
|
||
* оригинала (curr_tile / draw_xh / draw_main_y, которые load_curr_and_left_tile
|
||
* ставит ОДИН раз на тайл, seg008 redraw_needed_tiles) — все рисовальщики
|
||
* читают их, а не считают заново. У нас код тайла читался дважды за тайл
|
||
* (в fore_tile и снова тут), а координаты — в каждом листе. */
|
||
static uint8_t ft_code;
|
||
static int ft_x, ft_dmy;
|
||
|
||
/* Есть ли у кода тайла ХОТЬ ЧТО-ТО в переднем слое. Порт ранней проверки
|
||
* оригинала — `id = tile_table[curr_tile].fore_id; if (id == 0) return;`
|
||
* в начале ветки default у draw_tile_fore (seg008:0D15), — расширенной
|
||
* нашими спецслучаями: стена (20), пики (2), чомпер (0x12) и нижняя грань
|
||
* loose (11) рисуются мимо fore_id.
|
||
*
|
||
* Была в самом КОНЦЕ цепочки: до неё успевали отработать чтение кода тайла
|
||
* и все сравнения спецслучаев. Замер в MAME: fore-проход стоил 58 764 такта
|
||
* ради одного блита 10×2, а в позиции без переднего слоя — 112 758 тактов
|
||
* и ноль блитов. */
|
||
static const uint8_t FORE_ANY[32] = {
|
||
/* 00 empty */ 0, /* 01 floor */ 0, /* 02 spike */ 1, /* 03 pillar */ 1,
|
||
/* 04 gate */ 1, /* 05 stuck */ 0, /* 06 closebtn */ 0, /* 07 doortopf */ 1,
|
||
/* 08 bpbot */ 1, /* 09 bptop */ 1, /* 0A potion */ 1, /* 0B loose */ 1,
|
||
/* 0C dtop */ 1, /* 0D mirror */ 1, /* 0E debris */ 1, /* 0F openbtn */ 0,
|
||
/* 10 ldL */ 0, /* 11 ldR */ 0, /* 12 chomper */ 1, /* 13 torch */ 0,
|
||
/* 14 wall */ 1, /* 15 skeleton */ 0, /* 16 sword */ 0, /* 17 balcL */ 0,
|
||
/* 18 balcR */ 0, /* 19 lattpil */ 1, /* 1A lattdown */ 1, /* 1B lattsm */ 1,
|
||
/* 1C lattl */ 1, /* 1D lattr */ 1, /* 1E debristo */ 1, /* 1F — */ 0
|
||
};
|
||
|
||
/* Контекст ft_* обязан быть проставлен вызывающим. */
|
||
static void fore_only_tile(int row, int col)
|
||
{
|
||
/* Нажатая кнопка → пол/stuck (get_tile_to_draw) — это уже учтено в ft_code
|
||
* (его ставит pop_tile_code_drawn), иначе поверх Kid лезет передняя грань
|
||
* КНОПКИ. */
|
||
uint8_t code = ft_code;
|
||
const piece *t;
|
||
int x = ft_x, dmy = ft_dmy;
|
||
if (!FORE_ANY[code]) return; /* ранний выход, как в оригинале */
|
||
t = &pop_tile_table[code];
|
||
if (code == 20) {
|
||
/* Во дворце тело стены даёт wall_pattern заливками, а спрайтовая
|
||
* грань не рисуется вовсе (seg008:711). */
|
||
if (!pop_palace)
|
||
pop_wall_b(POP_WALL_FRAM_MAIN[pop_wall_modifier(row, col)], x, dmy);
|
||
wall_pattern(row, col, 1);
|
||
return;
|
||
}
|
||
if (code == 2) { /* пики: fore-кадр */
|
||
uint8_t sm = pop_tile_mod(row, col);
|
||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
|
||
pop_env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2);
|
||
}
|
||
if (code == 0x12) { /* чомпер: передний слой челюстей */
|
||
uint8_t cm = pop_tile_mod(row, 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], x, dmy);
|
||
if (cm & 0x80) /* кровь поверх переднего слоя */
|
||
pop_fore_b((uint8_t)(pose + 119), x + 8, dmy - 6);
|
||
}
|
||
if (code == 10) { /* зелье: склянка (пузырёк — динамика) */
|
||
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);
|
||
}
|
||
}
|
||
|
||
/* Полоса кладки У ПОТОЛКА поверх Kid — порт redraw_needed_above (seg008:0221).
|
||
* Там draw_tile_bottom(1) и draw_loose(1): аргумент 1 = «добавить ещё и в
|
||
* FOREtable», т.е. нижняя грань потолка и кадр плиты-потолка рисуются ПОВЕРХ
|
||
* персонажа (иначе руки прыгающего Kid лезут на кромку потолка). Всё
|
||
* остальное из этой функции (floorright, anim_topright, right) идёт в
|
||
* backtable — под персонажем. */
|
||
static void ceil_over_kid_tile(int col)
|
||
{
|
||
uint8_t code = pop_tile_code(-1, col);
|
||
const piece *t = &pop_tile_table[code];
|
||
int x = POP_COL_XH[col] * 8;
|
||
int dby = 63 * (-1) + 65; /* = 2 */
|
||
if (code == 20) { /* draw_tile_bottom: стена */
|
||
if (!pop_palace) /* seg008:576 */
|
||
pop_wall_b(POP_WALL_FRAM_BOTTOM[pop_wall_modifier(-1, col)], x, dby);
|
||
/* wall_pattern ЗДЕСЬ НЕТ — и это не упущение. draw_tile_bottom
|
||
* (seg008:588) кладёт в foretable ТОЛЬКО кадр грани `id`, под своим
|
||
* `arg_0`; узор вызывается строкой ниже, вне этой ветки, и уходит в
|
||
* текущую таблицу, то есть в ФОН. Мы же звали его прямо в переднем
|
||
* проходе — узор задней стены штамповался поверх всего, что уже
|
||
* нарисовано, включая падающие плиты (найдено прогоном 2026-08-13).
|
||
* В фон он попадает там, где и положено, — при отрисовке тайла.
|
||
*
|
||
* Не путать с fore_only_tile: там узор в переднем слое ЗАКОННЫЙ,
|
||
* это порт draw_tile_fore (seg008:715), где он стоит явно рядом с
|
||
* главной гранью стены, обязанной перекрывать персонажа. */
|
||
} else if (t->bottom_id) {
|
||
pop_env_b(t->bottom_id, x, dby);
|
||
}
|
||
if (code == 11) /* draw_loose(1): плита-потолок */
|
||
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(pop_ceil_modif[col])], x, dby);
|
||
ft_code = pop_tile_code_drawn(-1, col); /* контекст тайла для листа */
|
||
ft_x = x; ft_dmy = 63 * (-1) + 62;
|
||
fore_only_tile(-1, col); /* draw_tile_fore */
|
||
}
|
||
|
||
/* Пересекается ли габарит тайла с окном fore-слоя. Шаг тайла — 32 px
|
||
* (POP_COL_XH[col]*8 = col*32), куски выходят вправо максимум на +8 (дивайдеры
|
||
* и метки рисуются в ячейке правого соседа) и вверх на 42+высота кадра.
|
||
* Даёт отсев ЦЕЛОГО тайла ДО обращения к атласу: спрайт 27..40 px шириной
|
||
* задевает не все тайлы футпринта, а платили за каждый (atlas_image +
|
||
* gfx_w0_map + разбор заголовка на КАЖДЫЙ кусок). */
|
||
static uint8_t tile_in_fclip(int row, int col)
|
||
{
|
||
int x0, y0;
|
||
if (!pop_t_fclip_on) return 1;
|
||
x0 = POP_COL_XH[col] * 8;
|
||
y0 = POP_YOFF + 63 * row;
|
||
/* По ЛОГИЧЕСКОЙ рамке: y0 тайла считается из его ряда и переворотом не
|
||
* меняется (см. pop_tile.h про пару fclip_l*). */
|
||
return (uint8_t)(x0 < pop_t_fclip_x1 && x0 + 40 > pop_t_fclip_x0 &&
|
||
y0 - 8 < pop_t_fclip_ly1 && y0 + 70 > pop_t_fclip_ly0);
|
||
}
|
||
|
||
static void fore_tile(int row, int col) /* передний слой одного тайла */
|
||
{
|
||
if (!tile_in_fclip(row, col)) return;
|
||
/* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал
|
||
* кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в
|
||
* backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ
|
||
* персонажа ВСЕГДА, а не только у потолка (ceil_over_kid_tile). Верх
|
||
* плиты (draw_tile_base) остаётся в backtable — поэтому голова и руки
|
||
* висящего/подтягивающегося Kid видны НАД плитой, а тело уходит ЗА неё.
|
||
* Без этого Kid лез на плиту «с переднего края» (комната 12: вис на
|
||
* кромке loose над дырой от соседней упавшей плиты). */
|
||
ft_code = pop_tile_code_drawn(row, col); /* код тайла — ОДИН раз на тайл */
|
||
if (!FORE_ANY[ft_code]) return; /* нечего класть в передний слой */
|
||
ft_x = POP_COL_XH[col] * 8;
|
||
ft_dmy = 63 * row + 62;
|
||
if (row >= 0 && ft_code == 11)
|
||
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(pop_loose_modif[row * 10 + col])],
|
||
ft_x, ft_dmy + 3);
|
||
/* draw_tile_fore (seg008:690) — ТОЛЬКО add_foretable-часть. Раньше здесь
|
||
* рисовалась ещё и передняя кромка пола (bottom_id) «чтобы перекрывала
|
||
* Kid», но в оригинале bottom идёт через draw_tile_bottom в backtable
|
||
* (под персонажем), а поверх него попадает лишь при draw_other_overlay.
|
||
* Из-за лишнего bottom у основания колонны проскакивала «тень». */
|
||
fore_only_tile(row, col);
|
||
}
|
||
|
||
/* Определение общих с pop_room.c obj-координат (объявление — _pop_bg.h). */
|
||
int8_t pop_bg_obj_col, pop_bg_obj_row;
|
||
|
||
/* Пол-оверлей при подъёме (draw_floor_overlay, seg008:1457): когда Kid
|
||
* подтягивается на floor-подобный тайл назначения (floor/pillar/stuck/torch),
|
||
* а тайл СЛЕВА пуст (т.е. кромка), передний край пола перекрывает нижнюю
|
||
* часть Kid — иначе видна часть, что должна быть за полом. Кадр подъёма
|
||
* 137..144 индексирует floor_left_overlay. */
|
||
static const uint8_t FLOOR_LEFT_OVERLAY[8] = {32, 151, 151, 150, 150, 151, 32, 32};
|
||
|
||
static void other_overlay_tile(int row, int col); /* fwd (draw_floor_overlay -> else) */
|
||
|
||
static void climb_overlay_tile(int row, int col, uint8_t fidx)
|
||
{
|
||
/* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую
|
||
* Kid спускается/подтягивается, закрашивается целым тайлом */
|
||
uint8_t code;
|
||
if (!tile_in_fclip(row, col)) return;
|
||
code = pop_tile_code_drawn(row, col);
|
||
int x, dmy, dby;
|
||
const piece *t;
|
||
if (pop_tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */
|
||
if (code != 1 && code != 3 && code != 5 && code != 19) {
|
||
/* seg008:1E3A draw_floor_overlay: floor-оверлей рисуется только для
|
||
* floor/pillar/stuck/torch, а для ОСТАЛЬНЫХ тайлов вызывается
|
||
* draw_other_overlay() — иначе кнопка (opener/closer), за кромку
|
||
* которой Kid спускается, не перекрывает его. */
|
||
other_overlay_tile(row, col);
|
||
return;
|
||
}
|
||
x = POP_COL_XH[col] * 8;
|
||
dmy = 63 * row + 62;
|
||
dby = dmy + 3;
|
||
t = &pop_tile_table[code];
|
||
ov_mark(x, 63 * row + 2, 64, 64);
|
||
pop_env_b(FLOOR_LEFT_OVERLAY[fidx], x, dmy + (code == 5 ? 1 : 0));
|
||
if (t->bottom_id) pop_env_b(t->bottom_id, x, dby); /* draw_tile_bottom */
|
||
}
|
||
|
||
|
||
/* draw_tile2 (seg008:1F48) — но ТОЛЬКО та его часть, что реально может лечь
|
||
* ПОВЕРХ персонажа. Ключ (подтверждено трассой оригинала): draw_tile_right,
|
||
* draw_tile_anim_right и draw_loose добавляют спрайты через add_backtable
|
||
* НАПРЯМУЮ, игнорируя ptr_add_table — значит правая грань левого соседа
|
||
* («шахматка» столба 93, blueline, грани пик/loose соседа, кадр loose)
|
||
* ВСЕГДА рисуется ПОД персонажем. Через ptr_add_table (→ midtable при
|
||
* draw_other_overlay) идут только: «floor B» 42 при левом соседе-поле,
|
||
* draw_tile_base, draw_tile_anim (свои пики) и draw_tile_bottom. */
|
||
static void overlay_mid_tile(int row, int col)
|
||
{
|
||
uint8_t code = pop_tile_code_drawn(row, col);
|
||
uint8_t lcode = pop_tile_code_drawn(row, col - 1);
|
||
const piece *t;
|
||
int x = POP_COL_XH[col] * 8;
|
||
int dby = 63 * row + 65;
|
||
int dmy = dby - 3;
|
||
t = &pop_tile_table[code];
|
||
ov_mark(x, 63 * row + 2, 64, 64); /* тайл + правый сосед (грани) */
|
||
if (code != 20 && lcode == 1) /* draw_tile_right: только 42 */
|
||
pop_env_b(42, x, pop_tile_table[1].right_y + dmy);
|
||
{ /* draw_tile_base (seg008:0A8E) — ЦЕЛИКОМ, вместе с подстановками id:
|
||
* у loose верх плиты берётся из loose_fram_left (в pop_tile_table base_id=0),
|
||
* у opener'а без пола слева — 148. Раньше здесь стоял голый base_id, и
|
||
* верх loose-плиты в оверлей не попадал: передняя грань ложилась поверх
|
||
* Kid (foretable), а сама плита — нет, и подтягивающийся Kid рисовался
|
||
* поверх её верхней плоскости (комната 12). */
|
||
uint8_t base_id = t->base_id;
|
||
if (code == 11 && row >= 0)
|
||
base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])];
|
||
else if (code == 0x0F && lcode == 0)
|
||
base_id = 148;
|
||
if (base_id)
|
||
pop_env_b(base_id, x, t->base_y + dmy);
|
||
}
|
||
if (code == 2) { /* draw_tile_anim: свои пики */
|
||
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);
|
||
}
|
||
if (code == 20) { /* draw_tile_bottom */
|
||
uint8_t wmod = pop_wall_modifier(row, col);
|
||
if (!pop_palace) /* seg008:576 */
|
||
pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby);
|
||
wall_pattern(row, col, 0);
|
||
} else if (t->bottom_id) {
|
||
pop_env_b(t->bottom_id, x, dby);
|
||
}
|
||
}
|
||
|
||
/* draw_tile_fore (seg008:690) — ТОЛЬКО передний слой (add_foretable): он
|
||
* рисуется после всего и поверх персонажа. Нужен для полосы кладки у
|
||
* потолка: в redraw_needed_above всё остальное идёт в backtable (под Kid). */
|
||
|
||
/* draw_other_overlay (seg008:1492): для поз виса/прыжка/падения/захвата на
|
||
* КРОМКЕ (сосед слева пуст, либо тайл через один слева пуст) — перерисовать
|
||
* ВЕСЬ тайл поверх Kid. Тело пола перекрывает висящего/прыгающего/падающего
|
||
* ПОД ним. Срабатывает только в этих позах (см. redraw_at_char2 ниже) —
|
||
* поэтому при стоянии/залезании ложной окклюзии нет.
|
||
*
|
||
* ПОРЯДОК (redraw_needed_tiles, seg008:1B06 + redraw_needed, seg008:0211):
|
||
* тайлы обходятся ряды 2,1,0, внутри ряда колонки 0..9; в тайле сначала
|
||
* оверлей (redraw_frames2 -> midtable), потом ОБЪЕКТЫ этого тайла. Kid — тоже
|
||
* запись midtable, вставляемая при обработке СВОЕГО тайла (obj_row/obj_col).
|
||
* Значит оверлей ложится ПОВЕРХ Kid только у тайлов, идущих в обходе ПОЗЖЕ
|
||
* его собственного; всё, что раньше, — под ним. У нас списков нет (рисуем
|
||
* сразу), поэтому порядок эмулируется явной проверкой. */
|
||
static void other_overlay_tile(int row, int col)
|
||
{
|
||
uint8_t code, left;
|
||
if (!tile_in_fclip(row, col)) return;
|
||
code = pop_tile_code(row, col);
|
||
/* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для
|
||
* него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань
|
||
* пустого соседа). В оригинале она уходит в y-сортируемый midtable и
|
||
* ложится ПОЗАДИ Kid; у нас draw_tile кладёт её прямо поверх → сетка на
|
||
* висящем/падающем Kid. Фон запечён один раз в pop_room_draw — над
|
||
* спрайтом его перерисовывать не нужно. */
|
||
if (code == 0) return;
|
||
/* Тайл идёт в обходе РАНЬШЕ тайла Kid → его оверлей лёг бы ПОД спрайт. */
|
||
if (row > pop_bg_obj_row || (row == pop_bg_obj_row && col <= pop_bg_obj_col)) return;
|
||
left = pop_tile_code(row, col - 1);
|
||
if (left == 0) { overlay_mid_tile(row, col); return; } /* ветка 1: сосед слева пуст */
|
||
if (col > 0 && pop_tile_code(row, col - 2) == 0) {
|
||
/* Ветка 2 (тайл ЧЕРЕЗ ОДИН слева пуст): в оригинале она ставит
|
||
* tile_object_redraw[tilepos]=0xFF, т.е. объекты тайлов tilepos−1 и
|
||
* tilepos рисуются СРАЗУ ПОСЛЕ оверлея — Kid возвращается наверх.
|
||
* Без этого правая грань соседнего столба (кадр 93) остаётся поверх
|
||
* прыгающего Kid (баг «тень на дальнем столбе поверх Кида»). */
|
||
if (row == pop_bg_obj_row && (col == pop_bg_obj_col + 1)) return;
|
||
overlay_mid_tile(row, col);
|
||
}
|
||
}
|
||
|
||
/* Оверлей тайла ПОВЕРХ ПАДАЮЩЕГО КУСКА — вторая половина draw_mob
|
||
* (seg007:1149: set_redraw2 рядом с set_redraw_fore). set_redraw2 ведёт в
|
||
* draw_other_overlay, а тот кладёт части соседа в MIDtable
|
||
* (ptr_add_table = &add_midtable, seg008:1507/1512), тогда как кусок сидит в
|
||
* objtable, который вливается в midtable при обходе СВОЕГО тайла. Обход идёт
|
||
* ряды 2,1,0, колонки 0..9 — значит сосед перекрывает кусок, если его тайл
|
||
* идёт в обходе ПОЗЖЕ, и весь порядок уже проверяется внутри
|
||
* other_overlay_tile; ему нужен лишь ориентир «тайл объекта».
|
||
*
|
||
* Именно этой половины и не хватало: плита, отвалившаяся от (0,2), накрывала
|
||
* собой переднюю кромку пола (0,3) — а вернуть её через ПЕРЕДНИЙ слой нельзя,
|
||
* у кнопки fore_id = 0 (сверено с tile_table оригинала, там то же самое).
|
||
* Найдено пользователем 2026-08-17, уровень 1 комната 12.
|
||
*
|
||
* Ориентир сохраняем и возвращаем: pop_bg_obj_* принадлежит текущему
|
||
* персонажу, и его читает ещё и mob_tick_one (решение defer). */
|
||
void pop_mob_overlay_tile(int row, int col, int8_t oref_row, int8_t oref_col,
|
||
int mx, int my, uint8_t mh) __banked
|
||
{
|
||
int8_t saved_row = pop_bg_obj_row, saved_col = pop_bg_obj_col;
|
||
pop_bg_obj_row = oref_row;
|
||
pop_bg_obj_col = oref_col;
|
||
/* ОКНО = прямоугольник самого КУСКА. Смысл оверлея — перекрыть кусок, а
|
||
* то, что лежит вне него, и так нарисовано правильным фоном. Даёт сразу
|
||
* три вещи:
|
||
* - рисуем в разы меньше пикселей (тайл соседа шириной 32 обрезается до
|
||
* перекрытия с куском);
|
||
* - куски тайла, вовсе не задевающие кусок, отсеиваются предфильтром
|
||
* pop_blit_b даром — это и есть вертикальный гейт, отдельного не надо;
|
||
* - ov_mark становится НЕ НУЖЕН: всё нарисованное внутри heal-коридора
|
||
* куска, а он чистится каждый кадр.
|
||
* Замер 2026-08-17: без окна оверлеи шести кусков давали +210 000 в циане
|
||
* и +130 000 в «ввод+heal» (объединение ov_mark на полкомнаты). */
|
||
ov_suppress = 1;
|
||
pop_t_win_set(mx, my - (int)mh + 1 + POP_YOFF, 64, (uint8_t)(mh + 1));
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
other_overlay_tile(row, col);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_t_win_clear();
|
||
ov_suppress = 0;
|
||
pop_bg_obj_row = saved_row;
|
||
pop_bg_obj_col = saved_col;
|
||
}
|
||
|
||
/* Футпринт персонажа в тайлах (порт set_char_collision, seg006:0723).
|
||
* Общий счёт для любого Char (Кид, страж, скелет) — в оригинале это один
|
||
* код на всех. Результат — в файловых статиках, а не через 6 указателей: на Z80 такой
|
||
* возврат стоил бы дороже самой функции. */
|
||
static int8_t fp_cL, fp_cR, fp_cLraw, fp_rT, fp_rB, fp_rTraw;
|
||
|
||
static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||
int8_t dir, uint8_t sword)
|
||
{
|
||
int wh = (int)((w + 1) >> 1); /* char_width_half */
|
||
int xl = obj_x / 2 + 58; /* char_x_left (58-база) */
|
||
int xr;
|
||
int8_t cL, cR, cLraw, rT, rB, rTraw;
|
||
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
||
xr = xl + wh;
|
||
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
|
||
cL = cLraw; if (cL < 0) cL = 0; else if (cL > 9) cL = 9;
|
||
cR = col_from_x(xr); if (cR < 0) cR = 0; else if (cR > 9) cR = 9;
|
||
/* redraw_at_char (seg003:0430): «If char is holding sword, it makes
|
||
* redraw-area bigger» — КЛИНОК торчит на колонку дальше габарита кадра,
|
||
* и без этого расширения передние грани в той колонке не
|
||
* перерисовываются: меч остаётся поверх столба, а его след — на фоне. */
|
||
if (sword >= SWORD_2_DRAWN) {
|
||
if (dir >= 0) { if (++cR > 9) cR = 9; }
|
||
else { if (--cL < 0) cL = 0; }
|
||
}
|
||
/* Порт set_char_collision (seg006:0723): нижний ряд = y_to_row_mod4(obj_y),
|
||
* причём обёртка -1 (Kid НИЖЕ комнаты — climbdown/провал, obj_y в полосе
|
||
* «ряд 3») трактуется как ряд 3, НЕ ряд 0. Верхний ряд считается от
|
||
* char_top_y с ресетом в 0, если весь спрайт ниже комнаты (>=192). */
|
||
{
|
||
int top_y = obj_y - (int)h + 1;
|
||
if (top_y >= 192) top_y = 0;
|
||
rT = y_to_row(top_y);
|
||
rB = y_to_row(obj_y);
|
||
if (rB == -1) rB = 3; /* обёртка → ряд 3 (ниже комнаты) */
|
||
rTraw = rT; /* −1 = верх спрайта в полосе у потолка */
|
||
}
|
||
/* single-room roomtest: ряд 3 (комната снизу) в этом виде не рисуется →
|
||
* окклюдер спереди = ряд 2 (передняя грань пола, за которой нога). */
|
||
if (rB < 0) rB = 0; else if (rB > 2) rB = 2;
|
||
if (rT < 0) rT = 0; else if (rT > 2) rT = 2;
|
||
/* Голова/руки почти всегда торчат в ряд НАД опорным (Kid выше тайла).
|
||
* y_to_row(верх спрайта) из-за +60-сдвига может схлопнуться в опорный
|
||
* ряд (rT==rB) — тогда ряд над ним не попадёт в перебор и его окклюдер
|
||
* (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим
|
||
* включение ряда rB-1. */
|
||
if (rB > 0 && rT >= rB) rT = rB - 1;
|
||
fp_cL = cL; fp_cR = cR; fp_cLraw = cLraw;
|
||
fp_rT = rT; fp_rB = rB; fp_rTraw = rTraw;
|
||
}
|
||
|
||
/* Передние грани тайлов и оверлеи ПОВЕРХ персонажа — ОДИН проход на всех
|
||
* Char. В оригинале add_kid_to_objtable (seg008:22F0) и
|
||
* add_guard_to_objtable (seg008:2324) имеют ИДЕНТИЧНОЕ тело и различаются
|
||
* только окном (loadkid/loadshad), типом объекта и спецкейсом тени у
|
||
* зеркала; redraw_at_char / redraw_at_char2 (seg003:0576/0645) гейтов по
|
||
* charid не имеют вовсе.
|
||
*
|
||
* До DRAW-CHAR тут стояли ДВА прохода — свой у Кида, свой у соперника, — и
|
||
* расхождение уже стоило бага: падающий скелет рисовался поверх верхней
|
||
* грани пола соседней колонки, потому что порт redraw_at_char2 был только у
|
||
* Кида. Тем же путём пошли бы тень (ур. 4/5/6/12), визирь (13) и толстяк
|
||
* (12) — поэтому проход сведён к одному.
|
||
*
|
||
* obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер
|
||
* кадра; всё остальное берётся из самого персонажа (ch). Окно fore-клипа к
|
||
* этому моменту уже стоит: его задаёт тот, кто нарисовал спрайт. */
|
||
/* Передний слой ОДНОГО тайла — вход для прохода по пометкам
|
||
* (pop_fore_needed). Без окна fore-клипа: пометка уже сузила работу до
|
||
* нужных тайлов, а кусок тайла обязан лечь целиком — у оригинала foretable
|
||
* ничем не режется. */
|
||
void pop_fore_tile_b(int row, int col) __banked
|
||
{
|
||
uint8_t saved = pop_t_fclip_on;
|
||
pop_t_fclip_on = 0;
|
||
fore_tile(row, col);
|
||
pop_t_fclip_on = saved;
|
||
}
|
||
|
||
/* Передний слой полосы кладки У ПОТОЛКА (ряд −1) для ОДНОГО столбца — вход для
|
||
* прохода по пометкам (pop_fore_needed, ряд −1 идёт там последним).
|
||
*
|
||
* Тот же redraw_needed_above (seg008:0221), что и над персонажем: обход ряда −1
|
||
* стоит в redraw_needed_tiles ПОСЛЕДНИМ, а его draw_tile_fore кладёт куски в
|
||
* foretable, то есть поверх midtable (там лежат падающие плиты) и поверх
|
||
* объектов. Без этого кусок, пролетающий у потолка, накрывает собой кромку
|
||
* потолка — физически невозможная картинка (найдено пользователем 2026-08-17,
|
||
* уровень 1 комната 6: плита (−1,5) перекрывала потолок (−1,6)).
|
||
*
|
||
* Окно — ровно полоса этого столбца: передние куски потолка живут в трёх
|
||
* верхних строках (dby = 2 при клипе по POP_YOFF), рисовать шире незачем.
|
||
* ov_mark — как у персонажа: полоса рисуется банком без тени, и на второй
|
||
* странице её надо будет восстановить (pop_fore_heal). */
|
||
void pop_ceil_fore_tile_b(int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
/* ov_mark ЗДЕСЬ НЕ НУЖЕН, и это важно для бюджета. Передние куски потолка
|
||
* рисуются от dby = 2, то есть занимают строки 0..2 поля, а пометку ставит
|
||
* ТОЛЬКО падающий кусок, и только пока он до этих строк достаёт
|
||
* (mob_y <= 18) — значит нарисованное целиком лежит в его heal-коридоре
|
||
* (mob_y−18 .. mob_y+1), который чистится каждый кадр на своей странице.
|
||
* Пока ov_mark тут стоял, его объединение с оверлеями соседей
|
||
* растягивалось на полкомнаты и pop_fore_heal стоил 187 500 такта вместо
|
||
* 55 000 (замер 2026-08-17). */
|
||
pop_t_clip_top = POP_YOFF;
|
||
pop_t_win_set(x, POP_YOFF, 64, CEIL_BAND_H);
|
||
ceil_over_kid_tile(col);
|
||
pop_t_win_clear();
|
||
pop_t_clip_top = 0;
|
||
}
|
||
|
||
/* Банк на ВЕСЬ проход пометок, а не на каждый тайл: помеченных тайлов за
|
||
* кадр бывает больше десятка, и две смены банка на каждый — заметная доля
|
||
* прохода, который сам по себе дешёвый. */
|
||
void pop_fore_pass_begin(void) __banked { gfx_set_bank(GFX_BANK_SPRITE); }
|
||
void pop_fore_pass_end(void) __banked { gfx_set_bank(GFX_BANK_NORMAL); }
|
||
|
||
void pop_fore_over_char(const pop_char_t *ch, int obj_x, int obj_y,
|
||
uint16_t w, uint16_t h) __banked
|
||
{
|
||
int8_t cL, cR, cLraw, rT, rB, rTraw, r, c;
|
||
uint8_t frame = ch->frame, action = ch->action;
|
||
pop_t_fclip_on = 1; /* окно = прямоугольник спрайта */
|
||
char_footprint(obj_x, obj_y, w, h, ch->direction, ch->sword);
|
||
/* Футпринт считается по КАДРУ персонажа (char_x_left/right, seg006:1021)
|
||
* и потому УЖЕ спрайта, а рядом с ним рисуются ещё клинок и «брызги»
|
||
* удара — они уходят ВПЕРЁД-ВВЕРХ и запросто попадают в соседнюю
|
||
* колонку или ряд выше. Оригинал этим не страдает: он помечает тайлы
|
||
* на перерисовку и рисует foretable целиком, а у нас проход сужен окном
|
||
* ради скорости (см. pop_fore_layer_cost). Поэтому перебор расширяется
|
||
* САМИМ ОКНОМ fore-клипа — оно и есть объединение «персонаж + клинок +
|
||
* брызги», считать его второй раз не надо. */
|
||
/* Границы считаются АРИФМЕТИКОЙ, а не перебором всех колонок и рядов.
|
||
* Условие монотонно по номеру (x0 растёт), значит принятые номера идут
|
||
* подряд — хватает первого и последнего. Перебор стоил 38 970 тактов на
|
||
* кадр (замер MAME): тринадцать итераций, в каждой пара 16-битных
|
||
* ЗНАКОВЫХ сверок, а SDCC пишет их через `jp PO / xor 0x80 / jp P`. */
|
||
if (pop_t_fclip_x1 > pop_t_fclip_x0 && pop_t_fclip_y1 > pop_t_fclip_y0) {
|
||
int8_t lo, hi;
|
||
/* Колонки: x0 = POP_COL_XH[c]*8 = 32*c (таблица линейна, шаг 4).
|
||
* 32c + 40 > x0 -> c >= floor((x0−40)/32) + 1
|
||
* 32c < x1 -> c <= ceil(x1/32) − 1
|
||
* Сдвиг вправо у SDCC z80 для int — арифметический, то есть floor и
|
||
* для отрицательных; на это здесь и рассчитано. */
|
||
lo = (int8_t)(((pop_t_fclip_x0 - 40) >> 5) + 1);
|
||
hi = (int8_t)((((pop_t_fclip_x1 + 31) >> 5)) - 1);
|
||
if (lo < 0) lo = 0;
|
||
if (hi > 9) hi = 9;
|
||
if (lo <= hi) {
|
||
if (lo < fp_cL) fp_cL = lo;
|
||
if (hi > fp_cR) fp_cR = hi;
|
||
}
|
||
/* Ряды: y0 = POP_YOFF + 63*r, r только 0..2 — деления на 63 не надо,
|
||
* достаточно сравнить с границами полос.
|
||
* 63r > y0 − POP_YOFF − 70 и 63r < y1 − POP_YOFF + 8 */
|
||
{
|
||
/* Ряды — величина ЛОГИЧЕСКАЯ, поэтому по логической рамке. */
|
||
int a = pop_t_fclip_ly0 - POP_YOFF - 70;
|
||
int b = pop_t_fclip_ly1 - POP_YOFF + 8;
|
||
lo = a < 0 ? 0 : (a < 63 ? 1 : 2);
|
||
hi = b > 126 ? 2 : (b > 63 ? 1 : 0);
|
||
if (a < 126 && b > 0 && lo <= hi) {
|
||
if (lo < fp_rT) fp_rT = lo;
|
||
if (hi > fp_rB) fp_rB = hi;
|
||
}
|
||
}
|
||
}
|
||
cL = fp_cL; cR = fp_cR; cLraw = fp_cLraw;
|
||
rT = fp_rT; rB = fp_rB; rTraw = fp_rTraw;
|
||
/* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт
|
||
* попадает в midtable — база порядка окклюзии в other_overlay_tile.
|
||
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
|
||
* подъём — колонка на одну ЛЕВЕЕ curr_col. */
|
||
pop_bg_obj_row = ch->curr_row;
|
||
pop_bg_obj_col = ch->curr_col;
|
||
if (action == 1) { pop_bg_obj_row = rB; pop_bg_obj_col = cL; }
|
||
if ((frame >= 135 && frame < 149) ||
|
||
action == 2 || action == 3 || action == 4 || action == 6)
|
||
pop_bg_obj_col--;
|
||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, heal восстановит */
|
||
/* Шовная окклюзия ворот (порт redraw_at_char для шва): футпринт зашёл за
|
||
* ЛЕВЫЙ шов (cLraw<0) и у соседа слева в этом ряду решётка ворот (код 4) —
|
||
* перерисовать её бары ПОВЕРХ персонажа. Иначе он, стоя за закрытыми
|
||
* воротами (col-1), рисуется поверх решётки. Бары = draw_gate_back по
|
||
* живому modif соседа (pop_t_lbg = lcol_bg, openness). */
|
||
if (cLraw < 0 && pop_t_lfg)
|
||
for (r = rT; r <= rB; r++)
|
||
if ((pop_t_lfg[r] & 0x1F) == 4) {
|
||
int dby = 63 * r + 65;
|
||
draw_gate_back(pop_t_lbg ? pop_t_lbg[r] : 0, POP_COL_XH[0], dby, dby - 3);
|
||
}
|
||
if (frame >= 137 && frame <= 144) { /* подъём: пол назначения поверх него */
|
||
uint8_t fidx = (uint8_t)(frame - 137);
|
||
for (r = rT; r <= rB; r++)
|
||
for (c = cL; c <= cR; c++)
|
||
climb_overlay_tile(r, c, fidx);
|
||
} else {
|
||
/* redraw_at_char2 (seg003:449) — путь set_redraw2/draw_other_overlay:
|
||
* тело пола на кромке перекрывает Kid в позах захвата (78-79), виса
|
||
* (action 2/6), полёта (3/4) и старта падения (bumped 102-106).
|
||
* Верхний ряд — всегда; опорный — кроме action 2 (hang_climb). */
|
||
uint8_t do_ov = 0;
|
||
if (frame >= 78 && frame < 80) do_ov = 1; /* захват */
|
||
else if (action == 2 || action == 3 || action == 4 || action == 6) do_ov = 1;
|
||
else if (action == 5 && frame >= 102 && frame <= 106) do_ov = 1;
|
||
else if (frame == 135 || frame == 136) do_ov = 1; /* начало подъёма (SEQ_UP */
|
||
/* на 141 — до него Kid ещё */
|
||
/* в ряду 1 ПОД полом [0,3]) */
|
||
if (do_ov) {
|
||
/* ВЕСЬ диапазон рядов, а не только два крайних. В оригинале
|
||
* (redraw_at_char2, seg003:0645) стоит ровно два вызова —
|
||
* char_bottom_row и char_top_row, — и этого хватает, потому что
|
||
* там они ВСЕГДА соседние. У нас между ними бывает пропуск:
|
||
* char_footprint форсит rT = rB−1 (голова торчит в ряд выше), а
|
||
* потом окно fore-клипа расширяет rB ещё на ряд вниз — rT=0,
|
||
* rB=2 давали обход рядов 0 и 2 МИМО ряда 1.
|
||
*
|
||
* Ровно так терялась передняя грань пола у Кида, спрыгнувшего с
|
||
* кромки: кадр 103, action 3, ряд по y = 1, а обход шёл по 0 и 2
|
||
* (трасса MAME 2026-08-13: rT=0 rB=2 cL=0 cR=1, ни одного вызова
|
||
* overlay_mid_tile). Глубже в падении y_to_row давал уже 2, ряд
|
||
* 1 попадал в пару крайних — и плита вдруг начинала рисоваться
|
||
* правильно, что и было видно глазом (JUMP-FLOOR-LEGS). */
|
||
for (c = cR; c >= cL; c--) {
|
||
int8_t r2;
|
||
for (r2 = rB; r2 >= rT; r2--) {
|
||
/* seg003:0645: при hang_climb опорный ряд НЕ трогаем. */
|
||
if (r2 == rB && action == 2) continue;
|
||
other_overlay_tile(r2, c);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* draw_tile_fore (seg008:690) — foretable, рисуется ПОСЛЕ midtable, т.е.
|
||
* ПОСЛЕ оверлеев выше: передняя грань колонны перекрывает скос пола
|
||
* (floor_left_overlay), а не наоборот. Раньше fore шёл первым, и тёмный
|
||
* скос ложился поверх колонны — «тень у её основания». */
|
||
for (r = rT; r <= rB; r++)
|
||
for (c = cL; c <= cR; c++)
|
||
fore_tile(r, c);
|
||
/* Первое условие draw_tile_fore (seg008:0D15): персонаж СТОИТ на тайле
|
||
* ворот — их решётка возвращается поверх него. Решение приняла карта
|
||
* (pop_gate_over_char, разбор там же); здесь остаётся отрисовка. Бары
|
||
* рисуются в колонке curr_col+1: графика ворот принадлежит ПРАВОМУ краю
|
||
* своего тайла и в оригинале кладётся в проходе следующей колонки.
|
||
*
|
||
* Один вызов через трамплин на персонажа за кадр — не в тайловом цикле:
|
||
* тайл-кандидат всегда ровно один, а fore-проход и так самый горячий
|
||
* кусок кадра (memory pop_fore_layer_cost). */
|
||
if (pop_gate_over_char(ch)) {
|
||
int8_t gr = ch->curr_row, gc = (int8_t)(ch->curr_col + 1);
|
||
if (tile_in_fclip(gr, gc)) {
|
||
int dby = 63 * gr + 65;
|
||
draw_gate_fore(pop_t_bg ? pop_t_bg[gr * 10 + ch->curr_col] : 0,
|
||
POP_COL_XH[gc], dby, dby - 3);
|
||
}
|
||
}
|
||
/* redraw_needed_above (seg008:0221): полоса кладки У ПОТОЛКА (ряд −1 =
|
||
* ряд 2 комнаты сверху). В оригинале она рисуется в самом конце обхода
|
||
* тайлов — ПОСЛЕ объектов, т.е. поверх персонажа (redraw_frames_above,
|
||
* куда set_redraw* уводит отрицательные tilepos). Без этого руки Kid,
|
||
* прыгающего в ряду 0, лезли поверх потолка. */
|
||
if (rTraw < 0) {
|
||
pop_t_clip_top = POP_YOFF;
|
||
for (c = cL; c <= cR; c++) {
|
||
ov_mark(POP_COL_XH[c] * 8, 0, 64, CEIL_BAND_H);
|
||
ceil_over_kid_tile(c);
|
||
}
|
||
pop_t_clip_top = 0;
|
||
}
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_t_fclip_on = 0;
|
||
}
|