de68eb5cec
Позиция заводилась с НЕПОЛНЫМ диагнозом. Я приписал 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>
157 lines
7.9 KiB
C
157 lines
7.9 KiB
C
/*
|
||
* 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;
|
||
}
|