Уровень 4: skip по маске тайлов + порт loose_land, ворота 0xFF, пламя в фон
ОПТИМИЗАЦИЯ. Метка «фон трогали» была одним union-прямоугольником на страницу, и три факела комнаты склеивались в полосу x 40..280 на всю комнату — Кид, стоящий между крайними факелами, терял пропуск перерисовки и каждый кадр платил полным fore-проходом. Теперь это маска тайлов (uint16_t pop_cd_dmask[2][3]: бит = колонка, слово = ряд, набор = страница), проверка — три AND через резидентный pop_cd_hit. Гранулярность тайла — это гранулярность оригинала (redraw_frames_anim[tilepos], set_wipe; подтайловое уточнение там только по высоте, wipe_heights). Замер на (1,7): циан 233 515 -> 24 781, работа за кадр 517 609 -> 306 553, период 4 -> 3 растровых кадра. BUG-LOOSE-BUTTON-1. Упавшая плита не нажимала кнопку. Три слоя: порт loose_land не звал trigger_button вовсе; pop_room_col_landing считала площадкой только чистый пол, а у оригинала их семь (пол, пика, обе кнопки, зелье, оба факела); сигнал приходил в момент ОТРЫВА плиты, из-за чего ворота начинали открываться, пока она ещё в воздухе. Нажатие в оригинале ОДНО, но с button_type = tiles_14_debris — это «открыть НАСОВСЕМ» (modifier 0xFF), и кнопка съедается. Посадка в комнате снизу переехала на новый сигнал pop_loose_exit (взводит mob_tick_one, когда кусок ушёл ниже поля). BUG-GATE-FF-1. 0xFF был перегружен: сторожевое «тайла нет» в gate_modif и живое «открыто навсегда» из trigger_gate. gate_passable заворачивал Кида в воротах, нарисованных открытыми. Мёртвая ветка убрана. BUG-TORCH-CHOMP-1. Чомпер (0,7) комнаты 23 healит x 224..255 / y 30..93 и стирал пламя факела (0,6), которое рисуется в ячейке правого соседа. Фикс — запекать пламя (GFX_BANK_NORMAL): у факела все девять кадров на общем канвасе 16x18 и непрозрачны, протухнуть в ОЗУ-копии нечему, а heal чомпера сам возвращает огонь и кладёт челюсти поверх — z-порядок как в оригинале. Пузырёк зелья так нельзя (ползёт вверх, нужен heal) — остаётся в SPRITE. Заведено открытым: died_on_button (seg007:776) не портирован — нужен тайл tiles_5_stuck в атласе. Проверено пользователем в MAME (уровень 4: плита 16(1,1) -> кнопка 17(0,1) -> ворота 23(0,9); комната 23 с чомпером и факелом); make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -158,40 +158,104 @@ uint8_t pop_loose_frame(uint8_t m)
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ---- «Фон трогали ВОТ ЗДЕСЬ» (DRAW-COST; контракт — pop_cdraw.h) ----- */
|
||||
uint8_t pop_cd_dirty;
|
||||
int pop_cd_dx0[2], pop_cd_dy0[2], pop_cd_dx1[2], pop_cd_dy1[2];
|
||||
/* ---- «Фон трогали ВОТ ЗДЕСЬ» (DRAW-COST; контракт — pop_cdraw.h) ----- *
|
||||
*
|
||||
* Метка — БИТОВАЯ МАСКА ТАЙЛОВ, по слову на ряд и по набору на страницу.
|
||||
* Раньше здесь лежал ОДИН union-прямоугольник на страницу, и это убивало
|
||||
* пропуск перерисовки персонажа: три факела комнаты трогают по пятнышку
|
||||
* 16x18 в колонках 1, 6 и 8, а их объединение — полоса x 40..280 на всю
|
||||
* комнату. Кид, стоящий где угодно между крайними факелами, попадал в неё
|
||||
* и перерисовывался каждый кадр со всем fore-проходом (замер: циан 231 829
|
||||
* тактов против 20 071, период 4 растровых кадра против 3).
|
||||
*
|
||||
* Гранулярность тайла (32 x 63) вместо точного прямоугольника — осознанное
|
||||
* огрубление: факел помечает всю свою колонку по высоте ряда. Персонаж и
|
||||
* так занимает бОльшую часть высоты ряда, зато касания перестают
|
||||
* склеиваться. См. docs/impl_diff.md. */
|
||||
uint8_t pop_cd_dirty;
|
||||
uint16_t pop_cd_dmask[2][3];
|
||||
|
||||
/* Цикл по страницам развёрнут, а x+w / y+h посчитаны один раз. Не
|
||||
* вкусовщина: индекс-переменная заставляла SDCC считать адрес каждой из
|
||||
* восьми ячеек сложением, а `x + w` в условии и в присваивании считались
|
||||
* дважды — замер в MAME дал 6 846 тактов на вызов, а зовётся он с КАЖДОГО
|
||||
* блита фона (два факела в комнате — уже 13 700 за кадр). */
|
||||
/* 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]);
|
||||
}
|
||||
|
||||
/* Обе страницы помечаются сразу (персонаж чинится на каждой в свой кадр),
|
||||
* поэтому цикл развёрнут: индекс-переменная заставляла SDCC считать адрес
|
||||
* каждой ячейки сложением. Зовётся с КАЖДОГО блита фона. */
|
||||
void pop_cd_touch(int x, int y, int w, int h)
|
||||
{
|
||||
int x1 = x + w, y1 = y + h;
|
||||
uint8_t d = pop_cd_dirty;
|
||||
if (!(d & 1)) {
|
||||
pop_cd_dx0[0] = x; pop_cd_dy0[0] = y;
|
||||
pop_cd_dx1[0] = x1; pop_cd_dy1[0] = y1;
|
||||
} else {
|
||||
if (x < pop_cd_dx0[0]) pop_cd_dx0[0] = x;
|
||||
if (y < pop_cd_dy0[0]) pop_cd_dy0[0] = y;
|
||||
if (x1 > pop_cd_dx1[0]) pop_cd_dx1[0] = x1;
|
||||
if (y1 > pop_cd_dy1[0]) pop_cd_dy1[0] = y1;
|
||||
}
|
||||
if (!(d & 2)) {
|
||||
pop_cd_dx0[1] = x; pop_cd_dy0[1] = y;
|
||||
pop_cd_dx1[1] = x1; pop_cd_dy1[1] = y1;
|
||||
} else {
|
||||
if (x < pop_cd_dx0[1]) pop_cd_dx0[1] = x;
|
||||
if (y < pop_cd_dy0[1]) pop_cd_dy0[1] = y;
|
||||
if (x1 > pop_cd_dx1[1]) pop_cd_dx1[1] = x1;
|
||||
if (y1 > pop_cd_dy1[1]) pop_cd_dy1[1] = y1;
|
||||
uint16_t cols = cd_cols_of(x, x + w - 1);
|
||||
uint8_t r0, r1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
pop_cd_dirty = 3;
|
||||
}
|
||||
|
||||
/* Задел ли прямоугольник (ЭКРАННЫЕ координаты, x1/y1 ВКЛЮЧИТЕЛЬНО) то, что
|
||||
* трогали на странице p. Резидент: зовёт pop_cdraw.c из банка 4 прямым
|
||||
* вызовом, без трамплина. */
|
||||
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1)
|
||||
{
|
||||
uint16_t cols;
|
||||
uint8_t r0, r1;
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Страница приведена в порядок — снять с неё метку целиком. */
|
||||
void pop_cd_clear(uint8_t p)
|
||||
{
|
||||
pop_cd_dmask[p][0] = pop_cd_dmask[p][1] = pop_cd_dmask[p][2] = 0;
|
||||
pop_cd_dirty = (uint8_t)(pop_cd_dirty & ~(1 << p));
|
||||
}
|
||||
|
||||
/* ---- Блит куска атласа ---------------------------------------------- */
|
||||
/* Клипованный путь — ОТДЕЛЬНОЙ функцией. У него девять 16-битных локалей,
|
||||
* и SDCC заводит под них кадр IX; пока он жил в общем теле, за этот кадр
|
||||
|
||||
Reference in New Issue
Block a user