Files
Sprinter-SDCC/applications/PoP/roomtest/pop_cdraw.h
T
snark13 4718bff767 Уровень 4: skip по маске тайлов + порт loose_land, ворота 0xFF, пламя в фон
ОПТИМИЗАЦИЯ.  Метка «фон трогали» была одним union-прямоугольником на
страницу, и три факела комнаты склеивались в полосу x 40..280 на всю
комнату — Кид, стоящий между крайними факелами, терял пропуск перерисовки
и каждый кадр платил полным fore-проходом.  Теперь это маска тайлов
(uint16_t pop_cd_dmask[2][3]: бит = колонка, слово = ряд, набор = страница),
проверка — три AND через резидентный pop_cd_hit.  Гранулярность тайла — это
гранулярность оригинала (redraw_frames_anim[tilepos], set_wipe;
подтайловое уточнение там только по высоте, wipe_heights).  Замер на (1,7):
циан 233 515 -> 24 781, работа за кадр 517 609 -> 306 553, период 4 -> 3
растровых кадра.

BUG-LOOSE-BUTTON-1.  Упавшая плита не нажимала кнопку.  Три слоя: порт
loose_land не звал trigger_button вовсе; pop_room_col_landing считала
площадкой только чистый пол, а у оригинала их семь (пол, пика, обе кнопки,
зелье, оба факела); сигнал приходил в момент ОТРЫВА плиты, из-за чего
ворота начинали открываться, пока она ещё в воздухе.  Нажатие в оригинале
ОДНО, но с button_type = tiles_14_debris — это «открыть НАСОВСЕМ»
(modifier 0xFF), и кнопка съедается.  Посадка в комнате снизу переехала на
новый сигнал pop_loose_exit (взводит mob_tick_one, когда кусок ушёл ниже
поля).

BUG-GATE-FF-1.  0xFF был перегружен: сторожевое «тайла нет» в gate_modif и
живое «открыто навсегда» из trigger_gate.  gate_passable заворачивал Кида в
воротах, нарисованных открытыми.  Мёртвая ветка убрана.

BUG-TORCH-CHOMP-1.  Чомпер (0,7) комнаты 23 healит x 224..255 / y 30..93 и
стирал пламя факела (0,6), которое рисуется в ячейке правого соседа.  Фикс —
запекать пламя (GFX_BANK_NORMAL): у факела все девять кадров на общем
канвасе 16x18 и непрозрачны, протухнуть в ОЗУ-копии нечему, а heal чомпера
сам возвращает огонь и кладёт челюсти поверх — z-порядок как в оригинале.
Пузырёк зелья так нельзя (ползёт вверх, нужен heal) — остаётся в SPRITE.

Заведено открытым: died_on_button (seg007:776) не портирован — нужен тайл
tiles_5_stuck в атласе.

Проверено пользователем в MAME (уровень 4: плита 16(1,1) -> кнопка 17(0,1)
-> ворота 23(0,9); комната 23 с чомпером и факелом); make -C tests-host —
все 5 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 17:52:04 +03:00

139 lines
9.7 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];
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;
#endif