8f0362f3d4
libbgi: новая gfx_blit_cols_part_wx — обрезка СЛЕВА (skipw) в дополнение к верхней (skip/rows) и правой (maxw). По подсказке пользователя сделано примитивом, а не обходным путём «нарисовать и вернуть фон поверх лишнего»: для column-major левая обрезка стоит ровно столько же, сколько правая — колонка это непрерывный кусок ОЗУ, меняется стартовая колонка источника и экранная X. Внутри это уже было (так клипается левый край экрана), наружу не было выведено. Полное тело блита колонками переехало туда, gfx_blit_cols_part_w стала обёрткой (skipw=0). size-check: OK, роста нет (62 программы, -22..-33 Б на пользователей блита). Шаг 5 — клип ТЕНИ (seg008:1699): на уровне зеркала она показывается только СПРАВА от него, obj_clip_left = 137 + (mirror_column-4)*32. Шаг 3 — ОТРАЖЕНИЕ (check_mirror, seg003:0798): пока Кид стоит на тайле зеркала, каждый кадр рисуется его зеркальная копия с клипом left = (curr_col<<5)+9 и top = y_clip[curr_row+1]. Отдельной функцией pop_mirror_draw, а НЕ третьим слотом Char — это структура самого оригинала: отражение идёт сокращённым путём load_frame_to_obj + add_objtable(4), без клинка, брызг, fore-прохода и пропуска кадра; гейтить всё это в общем теле значило бы добавить ветки в самый горячий путь. Свой heal (pop_mirror_heal) рядом с pop_char_heal, в skip-маске не участвует. tests-host: заглушки pop_mirror_draw/pop_mirror_heal (pop_map теперь на них ссылается), все 5 наборов прошли. Цена: _CODE 24501 -> 24629, банк 3 10551, банк 4 8267 -> 9590. НЕ ПРОВЕРЕНО В MAME. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
148 lines
10 KiB
C
148 lines
10 KiB
C
/*
|
||
* pop_cdraw.h — ОТРИСОВКА ПЕРСОНАЖА, одна на всех Char.
|
||
*
|
||
* В оригинале это два входа с ИДЕНТИЧНЫМ телом (seg008:22F0/2324):
|
||
*
|
||
* add_kid_to_objtable add_guard_to_objtable
|
||
* loadkid() loadshad()
|
||
* load_fram_det_col() load_fram_det_col()
|
||
* load_frame_to_obj() load_frame_to_obj()
|
||
* stuck_lower() stuck_lower()
|
||
* set_char_collision() set_char_collision()
|
||
* set_objtile_at_char() set_objtile_at_char()
|
||
* redraw_at_char() redraw_at_char()
|
||
* redraw_at_char2() redraw_at_char2()
|
||
* clip_char() clip_char()
|
||
* add_objtable(0) add_objtable(1 тень / 2 страж)
|
||
*
|
||
* — различаются ТОЛЬКО окном (loadkid/loadshad), набором спрайтов и типом
|
||
* объекта. У нас до DRAW-CHAR это были два независимых куска кода
|
||
* (kid_draw в резиденте и pop_guard_draw в банке), и расхождение уже стоило
|
||
* бага: падающий скелет рисовался поверх верхней грани пола соседней
|
||
* колонки, потому что порт redraw_at_char2 был только у Кида. Тем же путём
|
||
* пошли бы тень (ур. 4/5/6/12), визирь (13) и толстяк (12).
|
||
*
|
||
* Поэтому здесь ОДИН набор функций, а различия сведены к слоту:
|
||
* POP_CH_KID — Кид: окно loadkid, атласы chtab_2 (kidp), кадр kid_frame;
|
||
* POP_CH_OPP — соперник: окно loadshad, атласы chtab_5 (страж/скелет),
|
||
* кадр pop_gframe.
|
||
*
|
||
* Порядок в кадре (задаёт главный цикл, как draw_people):
|
||
* pop_char_heal(who) — стереть прошлый кадр ЭТОЙ страницы дабл-буфера;
|
||
* pop_char_draw(who) — спрайт + брызги урона + клинок;
|
||
* pop_char_fore(who) — передние грани и оверлеи тайлов ПОВЕРХ него.
|
||
*/
|
||
#ifndef POP_CDRAW_H
|
||
#define POP_CDRAW_H
|
||
|
||
#include <stdint.h>
|
||
|
||
#define POP_CH_KID 0
|
||
#define POP_CH_OPP 1
|
||
#define POP_CH_N 2
|
||
|
||
/* Состояние отрисовки одного слота. Публичное, потому что метрики кадра
|
||
* (fp*) читает физика из ДРУГОГО банка (char_width_half в
|
||
* set_char_collision, порядок «кто поверх кого» в pop_bg): данные лежат в
|
||
* _DATA (W2) и видны всем без трамплина, а вызов аксессора из банка стоил
|
||
* бы дороже самого чтения. */
|
||
typedef struct {
|
||
/* прямоугольник спрайта по СТРАНИЦАМ дабл-буфера: у каждой своя
|
||
* видео-ОЗУ и своя ОЗУ-копия, поэтому heal обязан стирать спрайт именно
|
||
* той страницы, в которую сейчас рисуем */
|
||
int x[2], y[2];
|
||
uint16_t w[2], h[2];
|
||
uint8_t valid[2];
|
||
/* НАКЛАДНЫЕ спрайты (клинок + брызги урона) — свой прямоугольник, не
|
||
* объединение с персонажем: объединение сильно больше суммы двух
|
||
* (клинок уходит вперёд-вверх), а heal стоит ровно по площади */
|
||
int ox[2], oy[2];
|
||
uint16_t ow[2], oh[2];
|
||
uint8_t ovalid[2];
|
||
/* габарит кадра для fore-прохода: fpx — ЛОГИЧЕСКАЯ X (до ×8/7),
|
||
* fpy — низ спрайта; fpw == 0 — в этом кадре рисовать было нечего */
|
||
int fpx, fpy;
|
||
uint16_t fpw, fph;
|
||
/* окно fore-клипа слота (объединение «спрайт + накладные»), КОМНАТНЫЙ y */
|
||
int cx, cy;
|
||
uint16_t cw, ch;
|
||
/* straddle: рендерное смещение по ЛОГИЧЕСКОЙ X, когда комната персонажа
|
||
* не совпадает с отрисованной (порт xpos_in_drawn_room) */
|
||
int render_dx;
|
||
} pop_cdraw_t;
|
||
|
||
extern pop_cdraw_t pop_cd[POP_CH_N];
|
||
|
||
/* Стереть прошлый кадр персонажа (спрайт + накладные) на ТЕКУЩЕЙ странице. */
|
||
void pop_char_heal(uint8_t who) __banked;
|
||
|
||
/* Нарисовать текущий кадр: спрайт, брызги урона (по флагу pop_kid_hurt /
|
||
* pop_guard_hurt) и клинок. Загружает окно Char сам — clip_char и футпринт
|
||
* работают с АКТИВНЫМ персонажем, как в оригинале. */
|
||
void pop_char_draw(uint8_t who) __banked;
|
||
|
||
/* Передние грани и оверлеи тайлов ПОВЕРХ персонажа: ставит окно fore-клипа
|
||
* слота и зовёт общий проход pop_fore_over_char. Отдельным вызовом, а не
|
||
* хвостом pop_char_draw, потому что порядок в кадре задаёт главный цикл:
|
||
* между спрайтом и его fore-слоем успевает нарисоваться падающая плита. */
|
||
void pop_char_fore(uint8_t who) __banked;
|
||
|
||
/* Straddle-смещение спрайта (см. render_dx). */
|
||
void pop_char_set_render_dx(uint8_t who, int dx) __banked;
|
||
|
||
/* ---- Пропуск НЕИЗМЕНИВШЕГОСЯ кадра (DRAW-COST) ---------------------- *
|
||
* heal + спрайт + fore-проход стоят ~50 % кадрового периода НА ПЕРСОНАЖА
|
||
* (замер пользователя 2026-08-08: комната 1.3 с телом убитого стража — 210 %
|
||
* кадра, соседняя 1.2 без тела — 140 %). Платили мы это и за то, что не
|
||
* менялось: труп лежит неподвижно до выхода из комнаты, стоящий Кид — пока
|
||
* игрок не нажал клавишу, а перерисовывались они каждый кадр.
|
||
*
|
||
* Оптимизация: у каждой страницы дабл-буфера свой снимок ВХОДОВ отрисовки
|
||
* слота (поза, координаты, направление, меч, брызги, straddle). Если снимок
|
||
* совпал, спрайт этой страницы не стёрт и фона под ним никто не трогал —
|
||
* нужный кадр на странице УЖЕ нарисован, и heal/draw/fore пропускаются
|
||
* целиком.
|
||
*
|
||
* `pop_cd_touch` — «фон трогали ВОТ ЗДЕСЬ»: слой фона копит ПРЯМОУГОЛЬНИК
|
||
* своих правок по страницам дабл-буфера, и слот перерисовывается, только
|
||
* если тот задел его самого. Флага «трогали хоть что-то» не хватило:
|
||
* факелы анимируются КАЖДЫЙ кадр, и один факел в комнате обнулял пропуск
|
||
* для всех персонажей сразу (замер 2026-08-08: комната 1.1, Кид стоит —
|
||
* 597 684 такта, то есть ровно как без оптимизации).
|
||
*
|
||
* Метку ставит сам блит фона (`pop_blit_b`, по признаку «идёт не
|
||
* fore-проход») плюс те немногие места, что стирают фон голым `bar` или
|
||
* своим heal (падающий кусок, оверлей fore-heal). Координаты — ЭКРАННЫЕ
|
||
* (с POP_YOFF). Прямоугольник копится сразу для ОБЕИХ страниц: правку фона
|
||
* почти всегда повторяют на второй, а лишняя перерисовка персонажа дешевле
|
||
* лишнего состояния. Снимает метку главный цикл — после того, как
|
||
* персонажи на этой странице отрисованы.
|
||
*
|
||
* Резидент (pop_tile.c): зовёт и банковый слой фона, и резидентные листья,
|
||
* а трамплин на КАЖДЫЙ кусок фона стоил бы дороже самой метки. */
|
||
extern uint8_t pop_cd_dirty; /* биты страниц: метка взведена */
|
||
extern uint16_t pop_cd_dmask[2][3]; /* [страница][ряд] = биты колонок 0..9 */
|
||
/* Задели ли прямоугольник (ЭКРАННЫЕ координаты, x1/y1 включительно) то,
|
||
* что трогали на странице p. Резидент (pop_tile.c). */
|
||
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1);
|
||
void pop_cd_clear(uint8_t p); /* снять метку страницы целиком */
|
||
void pop_cd_touch(int x, int y, int w, int h);
|
||
#define POP_CD_TOUCH_ALL() pop_cd_touch(0, 0, 320, 256)
|
||
|
||
/* Кого в этом кадре можно НЕ перерисовывать: бит 0 — Кид, бит 1 — соперник.
|
||
* Звать ДВАЖДЫ: перед heal (стирать нечего) и перед отрисовкой (тик мог
|
||
* персонажа всё-таки сдвинуть — тогда бит снимется сам, а pop_char_draw
|
||
* сотрёт прошлый кадр за нас). */
|
||
uint8_t pop_char_skip_mask(void) __banked;
|
||
|
||
/* ОТРАЖЕНИЕ В ЗЕРКАЛЕ (уровень 4, seg003:0798). Char к моменту вызова уже
|
||
* отражён (pop_check_mirror); clip_top — линия y_clip[curr_row+1], выше
|
||
* которой отражение не рисуется. Отдельно от pop_char_draw, потому что и в
|
||
* оригинале это сокращённый путь add_objtable(4): без клинка, брызг,
|
||
* fore-прохода и пропуска кадра. heal — парный вызов, рядом с
|
||
* pop_char_heal. */
|
||
void pop_mirror_draw(int clip_top) __banked;
|
||
void pop_mirror_heal(void) __banked;
|
||
|
||
#endif
|