DRAW-COST шаг 1: пропуск неизменившегося персонажа — 210% -> 116% кадра

Персонаж, у которого с прошлой отрисовки ЭТОЙ страницы дабл-буфера не
изменился ни один вход отрисовки, а фон в его прямоугольнике не трогали,
уже нарисован правильно: 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.
This commit is contained in:
2026-08-08 18:39:00 +03:00
parent fd54bc78c0
commit a25ce58869
8 changed files with 283 additions and 60 deletions
+41
View File
@@ -90,4 +90,45 @@ 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