Files
Sprinter-SDCC/applications/PoP/roomtest/pop_cdraw.h
T
snark13 79bcabde94 Габариты спрайтов в байтах: uint16_t -> uint8_t в слоте отрисовки
Замечание пользователя: спрайты наших атласов не крупнее 64x64, а w/h
почти везде были uint16_t.  Это уже записано в памяти проекта
(pop_sprite_size_limits: весь игровой кадр PoP <= 56x63; больше 255 только
восемь полноэкранных подложек титров, а они через слот персонажа не
проходят).

Переведены в uint8_t: w/h, ow/oh, fpw/fph, cw/ch в pop_cdraw_t, параметры
cd_overlay_add и cd_clip_add, локали w/h/vis_w в pop_char_draw и
cd_splash, и чтение габарита из шапки ленты (было двухбайтным сложением
со сдвигом).

Эффект: лёгкая позиция 438 600 -> 438 324 (там персонажи не рисуются,
поэтому почти ничего), тяжёлая — циан 181 404 -> 180 270.  Плюс 24 байта
_DATA на двух слотах.

Скромно, но код от этого не запутаннее, а честнее: тип теперь отражает
реальный диапазон.  Проверено в MAME — бой идёт, хвостов и обрезков нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:00:19 +03:00

178 lines
13 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_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];
uint8_t w[2], h[2]; /* габарит: спрайты игры не крупнее 64x64 */
uint8_t valid[2];
/* НАКЛАДНЫЕ спрайты (клинок + брызги урона) — свой прямоугольник, не
* объединение с персонажем: объединение сильно больше суммы двух
* (клинок уходит вперёд-вверх), а heal стоит ровно по площади */
int ox[2], oy[2];
uint8_t ow[2], oh[2];
uint8_t ovalid[2];
/* габарит кадра для fore-прохода: fpx — ЛОГИЧЕСКАЯ X (до ×8/7),
* fpy — низ спрайта; fpw == 0 — в этом кадре рисовать было нечего */
int fpx, fpy;
uint8_t fpw, fph;
/* окно fore-клипа слота (объединение «спрайт + накладные»), КОМНАТНЫЙ y */
int cx, cy;
uint8_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; /* биты страниц: метка взведена */
/* [страница][колонка] = диапазон затронутых ЭКРАННЫХ y; пусто = ymin 255,
* ymax 0. Раньше тут была маска колонок на три ряда по 63 px — она
* склеивала касания внутри ряда и заставляла перерисовывать нетронутого
* персонажа (разбор в шапке pop_cd_touch, pop_tile.c). */
extern uint8_t pop_cd_ymin[2][10], pop_cd_ymax[2][10];
/* Задели ли прямоугольник (ЭКРАННЫЕ координаты, x1/y1 включительно) то,
* что трогали на странице p. Резидент (pop_tile.c). */
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1);
/* То же для СЛОТА персонажа: спрайт и накладной, координаты изнутри pop_cd
* (см. тело — там про цену пяти аргументов). */
uint8_t pop_cd_hit_slot(uint8_t who, uint8_t p);
void pop_cd_clear(uint8_t p); /* снять метку страницы целиком */
void pop_cd_init(void); /* пустые диапазоны на обеих страницах */
void pop_cd_touch(int x, int y, int w, int h);
#define POP_CD_TOUCH_ALL() pop_cd_touch(0, 0, 320, 256)
/* Пакетная пометка: между begin и end блиты КОПЯТ общий прямоугольник, и
* настоящая пометка ставится одна (на весь тайл), а не на каждый кусок.
* Скобку ставит draw_tile — выигрыш ~15% цены блита фона. Вкладывать
* скобки нельзя: аккумулятор один. */
extern uint8_t pop_cd_batch;
void pop_cd_batch_begin(void);
void pop_cd_batch_end(void);
/* ПОДАВИТЬ пометки на время серии блитов — для того, кто пометил область САМ
* и заведомо шире, чем нарисует (падающий кусок: pop_loose_mob_tick помечает
* весь коридор heal'а одним вызовом, а спрайт куска — три блита внутри него).
* Дешевле пакета: pop_cd_touch выходит второй проверкой, не считая ни колонок,
* ни рядов. Замер 2026-08-17: настоящий вызов pop_cd_touch = 4 502 такта,
* 28% всей цены блита.
*
* ОПАСНО применять без разбора: пропущенная пометка = персонаж, которого
* стёрли, но не попросили перерисоваться, то есть исчезнувший со экрана
* персонаж (ровно этот баг ловили 2026-08-13 в комнате 23). Ставить только
* там, где покрытие доказано. */
void pop_cd_mute(void);
void pop_cd_unmute(void);
/* Кого в этом кадре можно НЕ перерисовывать: бит 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