a25ce58869
Персонаж, у которого с прошлой отрисовки ЭТОЙ страницы дабл-буфера не изменился ни один вход отрисовки, а фон в его прямоугольнике не трогали, уже нарисован правильно: heal, блит и fore-проход пропускаются целиком. Не спецкейс «мёртвый страж», а общее правило — покрывает и труп, и стоящего Кида, и ждущего стража. Механизм: снимок входов по страницам (pop_cdraw.c, cd_sig/cd_quiet) + позиционная метка «фон трогали вот здесь» (pop_cd_touch в резидентном pop_tile.c, зовёт сам pop_blit_b). Решение перепроверяется перед отрисовкой, а pop_char_draw страхуется собственным heal — если тик всё-таки сдвинул персонажа, прошлый кадр стирается там. Слоты рядом (32 px) — перерисовываем оба, иначе heal соседа выест кусок из «тихого». Метка обязана быть ПОЗИЦИОННОЙ: с флагом «фон трогали хоть где-то» выигрыш был ровно нулевым — факелы анимируются каждый кадр и гасили пропуск для всех сразу (597 684 такта, как без оптимизации). Замеры (MAME, брейкпоинты по totalcycles, бюджет кадра 430 000): комн. 1.3, труп стража, Кид стоит: 210 % -> 116 % (500 772 такта), ноль вызовов pop_heal_fast за кадр, весь фон — 2 блита (44 136); комн. 1.1, Кид стоит вдали от факелов: 404 112 (94 %), цикл 4 -> 3 кадра. Узкое место сместилось на ЛОГИКУ: 60 % кадра уходит на тик персонажей, которые СТОЯТ, ещё 28 % — на loose_tick + process_trobs в комнате без единой ловушки. Разбивка и план — TASKS_OPEN.md#draw-cost.
135 lines
9.3 KiB
C
135 lines
9.3 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 int pop_cd_dx0[2], pop_cd_dy0[2], pop_cd_dx1[2], pop_cd_dy1[2];
|
||
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;
|
||
|
||
#endif
|