P15: метка «фон трогали» стала точной — минус 167 880 тактов на кадре
Две правки, обе про ложные срабатывания пропуска отрисовки персонажа. 1. МЕТКА: вместо «маска колонок по 32 px на ТРИ ряда по 63 px» теперь на каждую колонку хранится диапазон затронутых y (ymin/ymax, 40 байт на обе страницы). Прежняя гранулярность склеивала касания внутри ряда: пламя факела занимает y 33..50, клинок стоящего стража — y 59..65, между ними девять пикселей зазора, а метка считала слот задетым. 2. ПРОВЕРКА: cd_quiet сверяет с меткой спрайт и накладной (клинок, брызги) ДВУМЯ ОТДЕЛЬНЫМИ прямоугольниками, а не объединённым bbox. Объединение включает пустой угол между ними, и он ловил касания, которых нет: спрайт стража лежит в колонке 8, клинок уходит в колонку 7 на y 59..65, пламя метит колонку 7 на y 33..50 — прямоугольник «спрайт + клинок» (x 241..284, y 46..84) цеплял метку углом. Без второй правки первая почти ничего не дала (632 676 против 628 542 до неё): объединённый bbox продолжал ловить ложное пересечение. Замер 11/15: фаза до P15 после синяя 259 050 223 902 (heal тоже перестал платить) зелёная 181 494 181 494 циан 194 262 59 406 работа 632 676 464 796 Проверено в MAME: в статике картинка чистая, в динамике (пробежка, бой, переход в соседнюю комнату) хвостов и просвечивания нет. Хост-тесты зелёные. Заодно найден и исправлен собственный баг первой редакции: обе страницы обновлялись по условию, проверяющему только страницу 0, и после pop_cd_clear(0) метка страницы 1 переставала расти. Плюс pop_cd_init: пустая колонка обозначается ymin = 255, а нули от crt0 читались бы как «затронута строка 0». У ОРИГИНАЛА такой метки нет вовсе: и Apple II (FRAMEADV.S RedBlockFast, шесть буферов по блокам), и SDLPoP (set_redraw_fore) метят целыми тайлами, но им это не мешает — персонаж у них рисуется каждый кадр безусловно. Пропуск неизменившегося персонажа — наша добавка, поэтому и точность метки нужна выше оригинальной. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -378,11 +378,28 @@ static uint8_t cd_quiet(uint8_t who, uint8_t p)
|
||||
return 1;
|
||||
if (!pop_cd[who].valid[p]) return 0;
|
||||
if (pop_cd_dirty & (1 << p)) { /* фон правили — но задели ли нас? */
|
||||
int x0, y0, x1, y1;
|
||||
cd_bbox(who, p, &x0, &y0, &x1, &y1);
|
||||
y0 += POP_YOFF; y1 += POP_YOFF; /* метка фона — в ЭКРАННЫХ */
|
||||
/* cd_bbox отдаёт x1/y1 ИСКЛЮЧИТЕЛЬНО, pop_cd_hit ждёт включительно. */
|
||||
if (pop_cd_hit(p, x0, y0, x1 - 1, y1 - 1)) return 0;
|
||||
/* СПРАЙТ И НАКЛАДНОЙ (клинок, брызги) — ДВУМЯ ОТДЕЛЬНЫМИ проверками,
|
||||
* а не объединённым bbox. Объединение включает пустой угол между
|
||||
* ними, и он ловит касания, которых на самом деле нет: у стоящего
|
||||
* стража спрайт лежит в колонке 8, клинок уходит влево в колонку 7 на
|
||||
* y 59..65, а пламя факела метит колонку 7 на y 33..50. Прямоугольник
|
||||
* «спрайт + клинок» (x 241..284, y 46..84) пересекается с этой меткой
|
||||
* углом — и персонаж перерисовывался целиком за 148 302 такта
|
||||
* (замер 2026-08-19, сцена 11/15).
|
||||
*
|
||||
* Для skip_mask объединение по-прежнему годится: там вопрос «рядом ли
|
||||
* два слота», и загрубление в бОльшую сторону безопасно. */
|
||||
const pop_cdraw_t *q = &pop_cd[who];
|
||||
int y0 = q->y[p] + POP_YOFF;
|
||||
if (pop_cd_hit(p, q->x[p], y0,
|
||||
q->x[p] + (int)q->w[p] - 1, y0 + (int)q->h[p] - 1))
|
||||
return 0;
|
||||
if (q->ovalid[p]) {
|
||||
int oy = q->oy[p] + POP_YOFF;
|
||||
if (pop_cd_hit(p, q->ox[p], oy,
|
||||
q->ox[p] + (int)q->ow[p] - 1, oy + (int)q->oh[p] - 1))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
cd_sig_make(who, &g);
|
||||
a = (const uint8_t *)&g; b = (const uint8_t *)&cd_sig[who][p];
|
||||
|
||||
@@ -121,11 +121,16 @@ void pop_char_set_render_dx(uint8_t who, int dx) __banked;
|
||||
* Резидент (pop_tile.c): зовёт и банковый слой фона, и резидентные листья,
|
||||
* а трамплин на КАЖДЫЙ кусок фона стоил бы дороже самой метки. */
|
||||
extern uint8_t pop_cd_dirty; /* биты страниц: метка взведена */
|
||||
extern uint16_t pop_cd_dmask[2][3]; /* [страница][ряд] = биты колонок 0..9 */
|
||||
/* [страница][колонка] = диапазон затронутых ЭКРАННЫХ y; пусто = ymin 255,
|
||||
* ymax 0. Раньше тут была маска колонок на три ряда по 63 px — она
|
||||
* склеивала касания внутри ряда и заставляла перерисовывать нетронутого
|
||||
* персонажа (разбор в шапке pop_cd_touch, pop_tile.c). */
|
||||
extern uint8_t pop_cd_ymin[2][10], pop_cd_ymax[2][10];
|
||||
/* Задели ли прямоугольник (ЭКРАННЫЕ координаты, 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_init(void); /* пустые диапазоны на обеих страницах */
|
||||
void pop_cd_touch(int x, int y, int w, int h);
|
||||
#define POP_CD_TOUCH_ALL() pop_cd_touch(0, 0, 320, 256)
|
||||
|
||||
|
||||
@@ -195,43 +195,34 @@ uint8_t pop_spike_frame(uint8_t m)
|
||||
* и перерисовывался каждый кадр со всем fore-проходом (замер: циан 231 829
|
||||
* тактов против 20 071, период 4 растровых кадра против 3).
|
||||
*
|
||||
* Гранулярность тайла (32 x 63) вместо точного прямоугольника — осознанное
|
||||
* огрубление: факел помечает всю свою колонку по высоте ряда. Персонаж и
|
||||
* так занимает бОльшую часть высоты ряда, зато касания перестают
|
||||
* склеиваться. См. docs/impl_diff.md. */
|
||||
* ГРАНУЛЯРНОСТЬ: колонка 32 px по горизонтали, ТОЧНЫЙ диапазон y по
|
||||
* вертикали. Раньше по вертикали стоял номер ряда (три полосы по 63 px), и
|
||||
* это склеивало касания, разнесённые внутри ряда. Найдено пользователем на
|
||||
* сцене 11/15 (2026-08-19): пламя факела занимает y 5..22, клинок стоящего
|
||||
* стража — y 31..37, между ними девять пикселей чистого зазора, а метка
|
||||
* считала слот задетым, потому что оба попадают в ряд 0 и колонку 7.
|
||||
* Стоило это 148 302 такта в кадр — 23 % работы — на перерисовку персонажа,
|
||||
* которого никто не трогал.
|
||||
*
|
||||
* Почему диапазон, а не более мелкие полосы: полосы по 16 px эту пару всё
|
||||
* равно склеивают (пламя кончается в полосе 1, клинок в ней же начинается),
|
||||
* а 8-пиксельные потребовали бы 24 маски на страницу. Пара ymin/ymax на
|
||||
* колонку — 40 байт на обе страницы, точнее любых полос и без битовой возни.
|
||||
*
|
||||
* У ОРИГИНАЛА такой метки нет вовсе: и Apple II (FRAMEADV.S RedBlockFast с
|
||||
* шестью буферами wipebuf/redbuf/movebuf/floorbuf/halfbuf/objbuf), и SDLPoP
|
||||
* (set_redraw_fore в redraw_at_char) метят ЦЕЛЫМИ тайлами, но им это не
|
||||
* мешает — персонаж у них рисуется каждый кадр безусловно, а пометки нужны
|
||||
* только фону. Пропуск неизменившегося персонажа — наша добавка, поэтому и
|
||||
* точность метки нужна выше оригинальной. См. docs/impl_diff.md. */
|
||||
uint8_t pop_cd_dirty;
|
||||
uint16_t pop_cd_dmask[2][3];
|
||||
/* [страница][колонка] — диапазон затронутых экранных y. Пусто = ymin > ymax
|
||||
* (заполняется как ymin = 255, ymax = 0). */
|
||||
uint8_t pop_cd_ymin[2][10], pop_cd_ymax[2][10];
|
||||
|
||||
/* CD_LOW[n] = n младших единиц: маска пробега колонок c0..c1 считается как
|
||||
* CD_LOW[c1+1] & ~CD_LOW[c0] — два чтения таблицы вместо цикла сдвигов. */
|
||||
static const uint16_t CD_LOW[11] = {
|
||||
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
|
||||
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF
|
||||
};
|
||||
|
||||
/* Экранный y -> ряд комнаты 0..2 с клампом (полоса кладки у потолка и низ
|
||||
* стены ложатся на крайние ряды). Цепочкой сравнений, а не делением на 63:
|
||||
* у SDCC z80 одно деление ~5 400 тактов (memory sdcc_z80_division_hoisting). */
|
||||
static uint8_t cd_row_of(int y)
|
||||
{
|
||||
y -= POP_YOFF;
|
||||
if (y < 63) return 0;
|
||||
if (y < 126) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Колонки прямоугольника [x..x1] (включительно) -> битовая маска. Тайл ровно
|
||||
* 32 px и начинается с x=0, поэтому колонка — просто сдвиг. */
|
||||
static uint16_t cd_cols_of(int x, int x1)
|
||||
{
|
||||
uint8_t c0, c1;
|
||||
if (x < 0) x = 0;
|
||||
if (x1 > 319) x1 = 319;
|
||||
if (x1 < x) return 0; /* весь прямоугольник вне экрана */
|
||||
c0 = (uint8_t)(x >> 5);
|
||||
c1 = (uint8_t)(x1 >> 5);
|
||||
return (uint16_t)(CD_LOW[c1 + 1] & ~CD_LOW[c0]);
|
||||
}
|
||||
/* CD_LOW, cd_row_of и cd_cols_of СНЯТЫ вместе с переходом на диапазон y:
|
||||
* колонка теперь считается прямым сдвигом (x >> 5), а вертикаль сравнением
|
||||
* отрезков — битовые маски больше не нужны. */
|
||||
|
||||
/* Обе страницы помечаются сразу (персонаж чинится на каждой в свой кадр),
|
||||
* поэтому цикл развёрнут: индекс-переменная заставляла SDCC считать адрес
|
||||
@@ -274,8 +265,6 @@ void pop_cd_unmute(void) { pop_cd_batch = 0; }
|
||||
|
||||
void pop_cd_touch(int x, int y, int w, int h)
|
||||
{
|
||||
uint16_t cols;
|
||||
uint8_t r0, r1;
|
||||
if (pop_cd_batch) { /* копим, не разбирая на колонки/ряды */
|
||||
if (pop_cd_batch == CD_BATCH_MUTE) return; /* область помечена вызывающим */
|
||||
int x1 = x + w - 1, y1 = y + h - 1;
|
||||
@@ -285,18 +274,30 @@ void pop_cd_touch(int x, int y, int w, int h)
|
||||
if (y1 > cdb_y1) cdb_y1 = y1;
|
||||
return;
|
||||
}
|
||||
cols = cd_cols_of(x, x + w - 1);
|
||||
if (!cols) return;
|
||||
r0 = cd_row_of(y);
|
||||
r1 = cd_row_of(y + h - 1);
|
||||
pop_cd_dmask[0][r0] |= cols;
|
||||
pop_cd_dmask[1][r0] |= cols;
|
||||
if (r1 != r0) {
|
||||
pop_cd_dmask[0][r1] |= cols;
|
||||
pop_cd_dmask[1][r1] |= cols;
|
||||
if (r1 - r0 > 1) { /* прямоугольник накрыл все три ряда */
|
||||
pop_cd_dmask[0][1] |= cols;
|
||||
pop_cd_dmask[1][1] |= cols;
|
||||
{
|
||||
int8_t c0, c1, c;
|
||||
uint8_t y0, y1;
|
||||
int xr = x + w - 1;
|
||||
if (xr < 0 || x > 319) return; /* весь прямоугольник вне поля */
|
||||
if (x < 0) x = 0;
|
||||
if (xr > 319) xr = 319;
|
||||
c0 = (int8_t)(x >> 5);
|
||||
c1 = (int8_t)(xr >> 5);
|
||||
/* Клип по вертикали: экранные y не выходят за байт, а всё, что выше
|
||||
* поля или ниже его, персонажам всё равно не принадлежит. */
|
||||
if (y < 0) y = 0;
|
||||
if (y > 255) return;
|
||||
y0 = (uint8_t)y;
|
||||
y1 = (y + (int)h - 1 > 255) ? 255 : (uint8_t)(y + h - 1);
|
||||
/* Страницы обновляются НЕЗАВИСИМО. Общее условие по странице 0
|
||||
* («если ей стало теснее — записать в обе») ломается сразу после
|
||||
* pop_cd_clear(0): страница 1 хранит свои старые границы, условие по
|
||||
* нулевой уже не выполняется, и её метка перестаёт расти. */
|
||||
for (c = c0; c <= c1; c++) {
|
||||
if (y0 < pop_cd_ymin[0][c]) pop_cd_ymin[0][c] = y0;
|
||||
if (y1 > pop_cd_ymax[0][c]) pop_cd_ymax[0][c] = y1;
|
||||
if (y0 < pop_cd_ymin[1][c]) pop_cd_ymin[1][c] = y0;
|
||||
if (y1 > pop_cd_ymax[1][c]) pop_cd_ymax[1][c] = y1;
|
||||
}
|
||||
}
|
||||
pop_cd_dirty = 3;
|
||||
@@ -307,25 +308,39 @@ void pop_cd_touch(int x, int y, int w, int h)
|
||||
* вызовом, без трамплина. */
|
||||
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1)
|
||||
{
|
||||
uint16_t cols;
|
||||
uint8_t r0, r1;
|
||||
int8_t c0, c1, c;
|
||||
uint8_t ya, yb;
|
||||
if (!(pop_cd_dirty & (1 << p))) return 0;
|
||||
cols = cd_cols_of(x0, x1);
|
||||
if (!cols) return 0;
|
||||
r0 = cd_row_of(y0);
|
||||
r1 = cd_row_of(y1);
|
||||
if (pop_cd_dmask[p][r0] & cols) return 1;
|
||||
if (r1 != r0) {
|
||||
if (pop_cd_dmask[p][r1] & cols) return 1;
|
||||
if (r1 - r0 > 1 && (pop_cd_dmask[p][1] & cols)) return 1;
|
||||
}
|
||||
if (x1 < 0 || x0 > 319) return 0;
|
||||
if (x0 < 0) x0 = 0;
|
||||
if (x1 > 319) x1 = 319;
|
||||
if (y1 < 0 || y0 > 255) return 0;
|
||||
ya = (uint8_t)(y0 < 0 ? 0 : y0);
|
||||
yb = (uint8_t)(y1 > 255 ? 255 : y1);
|
||||
c0 = (int8_t)(x0 >> 5);
|
||||
c1 = (int8_t)(x1 >> 5);
|
||||
/* Пересечение отрезков [ya,yb] и [ymin,ymax] хотя бы в одной колонке.
|
||||
* Пустая колонка держит ymin = 255, ymax = 0 — условие ниже её отсеет
|
||||
* само, отдельной проверки «пусто» не нужно. */
|
||||
for (c = c0; c <= c1; c++)
|
||||
if (ya <= pop_cd_ymax[p][c] && yb >= pop_cd_ymin[p][c]) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Страница приведена в порядок — снять с неё метку целиком. */
|
||||
/* Начальное состояние обеих страниц — «ничего не трогали». ЯВНО, а не
|
||||
* расчётом на обнуление _DATA: пустая колонка обозначается ymin = 255,
|
||||
* ymax = 0, а нули от crt0 читались бы как «затронута строка 0». */
|
||||
void pop_cd_init(void)
|
||||
{
|
||||
pop_cd_clear(0);
|
||||
pop_cd_clear(1);
|
||||
}
|
||||
|
||||
void pop_cd_clear(uint8_t p)
|
||||
{
|
||||
pop_cd_dmask[p][0] = pop_cd_dmask[p][1] = pop_cd_dmask[p][2] = 0;
|
||||
uint8_t c;
|
||||
for (c = 0; c < 10; c++) { pop_cd_ymin[p][c] = 255; pop_cd_ymax[p][c] = 0; }
|
||||
pop_cd_dirty = (uint8_t)(pop_cd_dirty & ~(1 << p));
|
||||
}
|
||||
|
||||
|
||||
@@ -680,6 +680,7 @@ int pop_boot(void) __banked
|
||||
puts("pop_bg_load failed");
|
||||
return -1;
|
||||
}
|
||||
pop_cd_init(); /* метка «фон трогали» — пустые диапазоны */
|
||||
pop_cheats = 1; /* режим разработки: читы включены */
|
||||
pop_guard_reset();
|
||||
if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
|
||||
|
||||
Reference in New Issue
Block a user