Files
Sprinter-SDCC/applications/PoP/roomtest/pop_redraw.c
T
snark13 de68eb5cec P1: чомпер перерисовывался неизменной позой — минус 110 802 такта
Позиция заводилась с НЕПОЛНЫМ диагнозом.  Я приписал 190 260 тактов
пометке от факела (пламя лежит в ячейке правого соседа, то есть поверх
чомпера, и запекается каждый кадр).  Правка по этому диагнозу не дала
ничего: 769 002 против 768 684.

Зонд pop_dbg_kind показал факт: все 312 перерисовок прогона — вид
POP_RD_CHOMP, полная, и ни одной от факела.  Собственная пометка чомпера
просто перебивала пометку соседа.

Настоящая причина нашлась сверкой с animate_chomper (seg007:0448).
Оригинал заканчивает её так:

    if ((curr_modifier & 0x7F) < 6) redraw_at_trob();

то есть перерисовывает чомпер только пока фаза меньше 6 — пять кадров из
пятнадцати.  Это не оптимизация оригинала, а следствие таблицы поз:
chomper_fram1 = {3,2,0,1,4,3,3}, и с фазы 5 до конца круга поза одна и та
же.  Мы метили тайл каждый кадр, пока trob жив, а живёт он всё время, пока
Кид в том же ряду — то есть платили полный draw_tile плюс heal 32x64 за
неизменную картинку в двух третях кадров.

Сделано:

  1. пометка только при фазе < 6; на фазе 5 — обе страницы дабл-буфера
     (она последняя рисуемая, её поза обязана лечь на обе; вторую страницу
     пометка догоняет в кадре фазы 6, где поза та же — CHOMP_FRAM1[6] == 3);
  2. новый вид POP_RD_CHOMP_ANIM -> pop_chomp_anim_draw: три блита графики
     чомпера поверх свежего пламени, без heal и без остальных слоёв — порт
     ветки redraw_frames_anim (seg008:0211), где оригинал делает ровно
     draw_tile_anim_topright / draw_tile_anim_right / draw_tile_anim и
     никакого wipe;
  3. приоритет полной перерисовки над anim в pop_set_redraw: у оригинала
     это два независимых счётчика и full побеждает, а у нас вид один на
     тайл, и без проверки исход решал бы порядок trob'ов в списке.

Обе половины работают — замер даёт 40 % полных перерисовок и 60 % лёгких.
Работа 768 684 -> 657 882 (медиана), зелёная 294 510 -> 183 420.  В 40 %
кадров цена прежняя: там поза реально меняется, это честная работа.

Циан не сдвинулся ни на такт, то есть надежда P3 (Кид перестанет будиться
каждый кадр) пока не оправдалась — метки продолжают его будить.

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

157 lines
7.9 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_redraw.c — пометки перерисовки тайлов и их разбор. См. pop_redraw.h.
*
* Живёт в W1/W2 (НЕ в резиденте W3): пометки ставит логика, которая должна
* оставаться достижимой из будущих __banked-модулей. Сам разбор зовёт
* pop_bg (резидент W3) — это законно, потому что разбор дёргает только
* главный цикл.
*/
#include <stdint.h>
#include "pop_redraw.h"
#include "pop_bg.h"
#include "_pop_bg.h" /* pop_bake_slot_reset — тот же банк, без трамплина */
#define NTILES 30
#define NCOLS 10
static uint8_t rd_kind[NTILES], rd_cnt[NTILES];
static uint8_t rda_kind[NCOLS], rda_cnt[NCOLS];
/* Сколько всего непустых пометок: без него проход по 40 ячейкам крутился бы
* КАЖДЫЙ кадр впустую (замер 2026-07-27 на прежнем rest-цикле: ~12 % кадра). */
static uint8_t rd_pending;
/* Пометки ПЕРЕДНЕГО слоя — однокадровые, отдельно от rd_*: те живут по
* страницам дабл-буфера (запекают фон), а эти транзиентны. */
static uint8_t rdf[NTILES];
static uint8_t rdf_pending;
/* Передний слой РЯДА −1 (полоса кладки у потолка). Отдельным набором, как и
* сами перерисовки ряда −1 (rda_*): у оригинала это вообще другой проход —
* redraw_needed_tiles сначала обходит ряды 2,1,0, а ПОТОМ ряд 2 комнаты сверху
* (seg008:1B06), и его draw_tile_fore кладёт куски в foretable, то есть поверх
* midtable, где лежат падающие плиты, и поверх объектов. */
static uint8_t rdfa[NCOLS];
static uint8_t rdfa_pending;
void pop_redraw_reset(void) __banked
{
uint8_t i;
for (i = 0; i < NTILES; i++) { rd_kind[i] = 0; rd_cnt[i] = 0; }
for (i = 0; i < NCOLS; i++) { rda_kind[i] = 0; rda_cnt[i] = 0; }
rd_pending = 0;
for (i = 0; i < NTILES; i++) rdf[i] = 0;
for (i = 0; i < NCOLS; i++) rdfa[i] = 0;
pop_fore_pass_end();
rdf_pending = 0;
rdfa_pending = 0;
}
void pop_set_redraw_fore(uint8_t tilepos) __banked
{
if (tilepos >= NTILES) return;
if (!rdf[tilepos]) rdf_pending++;
rdf[tilepos] = 1;
}
/* То же для полосы кладки У ПОТОЛКА (ряд −1). Ставит падающий кусок, когда
* его спрайт достаёт до кромки потолка: иначе кусок накрывает эту кромку
* собой, чего быть не может (найдено пользователем 2026-08-17, ур.1 к.6 —
* плита (−1,5) перекрывала потолок (−1,6)). */
void pop_set_redraw_fore_above(uint8_t col) __banked
{
if (col >= NCOLS) return;
if (!rdfa[col]) rdfa_pending++;
rdfa[col] = 1;
}
/* Разбор пометок переднего слоя: тайлы обходятся в том же порядке, что и
* фон, и рисуются ПОСЛЕ всех объектов кадра — как foretable у оригинала
* (draw_tables, seg008:1373: back -> mid -> fore). */
void pop_fore_needed(void) __banked
{
uint8_t i, row = 0, col = 0;
if (!rdf_pending && !rdfa_pending) return;
pop_fore_pass_begin();
for (i = 0; i < NTILES; i++) {
if (rdf[i]) { rdf[i] = 0; pop_fore_tile_b(row, col); }
if (++col == 10) { col = 0; row++; }
}
rdf_pending = 0;
/* Ряд −1 — ПОСЛЕДНИМ, как в redraw_needed_tiles (seg008:1B06). */
if (rdfa_pending) {
for (i = 0; i < NCOLS; i++)
if (rdfa[i]) { rdfa[i] = 0; pop_ceil_fore_tile_b(i); }
rdfa_pending = 0;
}
}
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) __banked
{
if (tilepos >= NTILES) return;
if (!rd_cnt[tilepos]) rd_pending++;
else pop_bake_slot_reset(tilepos); /* перепометка — копия запечки не годится */
/* ПРИОРИТЕТ ПОЛНОЙ ПЕРЕРИСОВКИ НАД слоем anim. У оригинала это не
* конфликт видов, а два независимых счётчика, и полная побеждает
* (redraw_needed, seg008:0211: `if (redraw_frames_full) draw_tile();
* else if (redraw_frames_anim) {...}`). У нас вид ОДИН на тайл, и без
* этой проверки исход решал бы порядок trob'ов в списке: чомпер, который
* СЕЙЧАС смыкает челюсти, ставит POP_RD_CHOMP (ему нужен heal — поза
* меняется), а факел слева тут же метил бы тот же тайл как
* POP_RD_CHOMP_ANIM, и heal пропал бы — новая поза легла бы поверх
* старой. */
if (!(kind == POP_RD_CHOMP_ANIM && rd_kind[tilepos] == POP_RD_CHOMP &&
rd_cnt[tilepos]))
rd_kind[tilepos] = kind;
if (pages > rd_cnt[tilepos]) rd_cnt[tilepos] = pages;
}
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked
{
if (col >= NCOLS) return;
if (!rda_cnt[col]) rd_pending++;
else pop_bake_slot_reset_above(col); /* перепометка — копия запечки не годится */
rda_kind[col] = kind;
if (pages > rda_cnt[col]) rda_cnt[col] = pages;
}
/* redraw_needed (seg008:0178): пройти пометки и нарисовать. row/col ведём
* бегущими счётчиками, а не через i/10 и i%10 — у Z80 нет деления, и SDCC
* выносит __divuchar/__moduchar в вершину цикла как общее подвыражение
* аргументов (30*2 делений за кадр даже когда рисовать нечего). */
void pop_redraw_needed(void) __banked
{
uint8_t i, row = 0, col = 0, left = 0, cnt = 0; /* cnt — ВРЕМЕННО, замер */
pop_dbg_rdmax_tot = 0;
if (!rd_pending) return;
for (i = 0; i < NTILES; i++) {
if (rd_cnt[i]) {
pop_dbg_kind(rd_kind[i]); /* ВРЕМЕННО: какой вид перерисовки */
switch (rd_kind[i]) {
case POP_RD_SPIKE: pop_spike_redraw(row, col); break;
case POP_RD_LOOSE: pop_loose_shake_draw(row, col); break;
case POP_RD_LOOSE_GONE: pop_loose_bake_empty(row, col); break;
case POP_RD_FLOOR: pop_floor_bake(row, col); break;
case POP_RD_LEVELDOOR: pop_leveldoor_redraw(row, col); break;
case POP_RD_GATE: pop_gate_redraw(row, col); break;
case POP_RD_CHOMP: pop_chomp_redraw(row, col); break;
case POP_RD_CHOMP_ANIM: pop_chomp_anim_draw(row, col); break;
default: break;
}
cnt++; /* ВРЕМЕННО: замер */
if (--rd_cnt[i]) left++;
else rd_kind[i] = 0;
}
if (++col == 10) { col = 0; row++; }
}
for (i = 0; i < NCOLS; i++) {
if (rda_cnt[i]) {
if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i);
else pop_ceil_bake_empty(i);
cnt++; /* ВРЕМЕННО: замер */
if (--rda_cnt[i]) left++;
else rda_kind[i] = 0;
}
}
rd_pending = left;
pop_dbg_rdmax_tot = cnt; /* ВРЕМЕННО: замер */
if (cnt > pop_dbg_rdmax[0]) pop_dbg_rdmax[0] = cnt;
}