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
+21 -6
View File
@@ -464,6 +464,7 @@ int main(void)
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
uint8_t skip = 0; /* маска «слот перерисовывать не надо» */
uint16_t dead_frames = 0;
#ifdef ROOMNAV
uint8_t nav_prev = 0;
@@ -645,8 +646,13 @@ int main(void)
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
pop_ctrl_tick(); /* ввод -> control(): смена seq */
pop_char_heal(POP_CH_KID); /* стереть прошлый кадр ЭТОЙ страницы */
pop_char_heal(POP_CH_OPP); /* и прошлый кадр соперника */
/* Кто в этом кадре не изменился с прошлой отрисовки ЭТОЙ страницы
* тот на ней уже нарисован правильно: ни стирать, ни рисовать заново
* не надо (DRAW-COST, контракт в pop_cdraw.h). Решение
* перепроверяется перед самой отрисовкой — тик ещё впереди. */
skip = pop_char_skip_mask();
if (!(skip & 1)) pop_char_heal(POP_CH_KID); /* стереть прошлый кадр ЭТОЙ страницы */
if (!(skip & 2)) pop_char_heal(POP_CH_OPP); /* и прошлый кадр соперника */
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
PROF(2); /* логика (тот же синий) */
kid_tick(); /* play_seq: следующий кадр */
@@ -819,10 +825,14 @@ int main(void)
* персонажа (см. guard_over_kid). Отрисовка одна на всех Char
* (pop_cdraw.c), различает их только слот. */
uint8_t g_after = guard_over_kid();
if (!g_after) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
/* Пересчёт после тика: слот, за который heal не платили, мог
* всё-таки сдвинуться — тогда бит снимется, а прошлый кадр
* сотрёт сам pop_char_draw (страховка cd_heal). */
skip = pop_char_skip_mask();
if (!g_after && !(skip & 2)) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
PROF(6); /* кадр Кида (тот же циан) */
pop_char_draw(POP_CH_KID); /* спрайт + брызги + клинок */
if (g_after) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
if (!(skip & 1)) pop_char_draw(POP_CH_KID); /* спрайт + брызги + клинок */
if (g_after && !(skip & 2)) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
/* Брызги урона рисует сама отрисовка персонажа (порядок objtable:
* сразу после него и до клинка), а флаги снимаем здесь: соперника
* могло не оказаться в отрисованной комнате — тогда брызги просто
@@ -840,7 +850,12 @@ int main(void)
* pop_char_fore(KID) заодно возвращает окно Кида (при «соперник
* поверх» в нём остался бы соперник, и передние грани у Кида
* отсеклись бы целиком — он рисовался бы поверх столбов). */
pop_char_fore(POP_CH_KID);
if (!(skip & 1)) pop_char_fore(POP_CH_KID);
/* Персонажи на этой странице приведены в порядок — след «фон трогали»
* для неё снят. ПОСЛЕ отрисовки, а не до: слой фона этого же кадра
* взводит бит и обязан быть учтён. Всё, что рисуется ниже
* (mob поверх Кида, борта), взводит его заново само. */
pop_cd_dirty = (uint8_t)(pop_cd_dirty & ~(1 << back));
pop_room_clip_borders(); /* почистить борта — только в кадрах падения (гейт) */
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже