mob_tick_one в file-scope + снятие временной оснастки замеров
зелёная пик 423 558 -> 414 456 работа пик 793 266 -> 783 798 ПЕРИОД логического кадра в каскаде: было 5-6 растровых, стало РОВНО 4 mob_tick_one: рабочие переменные в file-scope (было 16 байт кадра и 99 обращений `-N(ix)`, стало 17) — то же лечение, что у draw_tile и blit_b_clip. Снята временная оснастка из ГОРЯЧИХ путей: шесть вызовов pop_dbg_b1..b6 в pop_blit_b (по ~65 такта каждый на КАЖДЫЙ блит), pop_dbg_kind/m16 и подсчёт состава кадра в pop_redraw_needed, pop_dbg_m13..m15 в pop_ceil_shake_draw. Сами пустышки в pop_state.c оставлены — вставить их обратно на один замер дешевле, чем заводить заново; как это делается, записано в docs/perf_l13_room23.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,13 +80,9 @@ void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked
|
||||
void pop_redraw_needed(void) __banked
|
||||
{
|
||||
uint8_t i, row = 0, col = 0, left = 0;
|
||||
uint8_t cnt[10], tot = 0; /* ВРЕМЕННО: состав кадра */
|
||||
if (!rd_pending) return;
|
||||
for (i = 0; i < 10; i++) cnt[i] = 0;
|
||||
for (i = 0; i < NTILES; i++) {
|
||||
if (rd_cnt[i]) {
|
||||
if (rd_kind[i] < 8) { cnt[rd_kind[i]]++; tot++; } /* ВРЕМЕННО */
|
||||
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;
|
||||
@@ -97,7 +93,6 @@ void pop_redraw_needed(void) __banked
|
||||
case POP_RD_CHOMP: pop_chomp_redraw(row, col); break;
|
||||
default: break;
|
||||
}
|
||||
pop_dbg_m16(); /* ВРЕМЕННО */
|
||||
if (--rd_cnt[i]) left++;
|
||||
else rd_kind[i] = 0;
|
||||
}
|
||||
@@ -105,18 +100,11 @@ void pop_redraw_needed(void) __banked
|
||||
}
|
||||
for (i = 0; i < NCOLS; i++) {
|
||||
if (rda_cnt[i]) {
|
||||
cnt[8 + (rda_kind[i] == POP_RDA_CEIL ? 0 : 1)]++; tot++; /* ВРЕМЕННО */
|
||||
pop_dbg_kind((uint8_t)(8 + rda_kind[i])); /* ВРЕМЕННО */
|
||||
if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i);
|
||||
else pop_ceil_bake_empty(i);
|
||||
pop_dbg_m16(); /* ВРЕМЕННО */
|
||||
if (--rda_cnt[i]) left++;
|
||||
else rda_kind[i] = 0;
|
||||
}
|
||||
}
|
||||
if (tot > pop_dbg_rdmax_tot) { /* ВРЕМЕННО: расклад пика */
|
||||
pop_dbg_rdmax_tot = tot;
|
||||
for (i = 0; i < 10; i++) pop_dbg_rdmax[i] = cnt[i];
|
||||
}
|
||||
rd_pending = left;
|
||||
}
|
||||
|
||||
@@ -757,9 +757,7 @@ void pop_bake_forget(void) __banked
|
||||
void pop_ceil_shake_draw(int col) __banked
|
||||
{
|
||||
int x = POP_COL_XH[col] * 8;
|
||||
pop_dbg_m13(); /* ЗАМЕР: вход pop_ceil_shake_draw */
|
||||
pop_heal_off(x, 0, 64, CEIL_BAND_H);
|
||||
pop_dbg_m14(); /* ЗАМЕР: heal сделан */
|
||||
/* Пометка «фон трогали» — ОДНА на восстановленный прямоугольник: пока
|
||||
* стоит окно (ниже), pop_blit_b пометок не ставит. Область та же, что у
|
||||
* heal'а, — он её и стирает. */
|
||||
@@ -771,7 +769,6 @@ void pop_ceil_shake_draw(int col) __banked
|
||||
pop_t_win_clear();
|
||||
pop_t_clip_top = 0;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
pop_dbg_m15(); /* ЗАМЕР: draw_tile сделан */
|
||||
}
|
||||
|
||||
void pop_ceil_bake_empty(int col) __banked
|
||||
@@ -1257,22 +1254,30 @@ static void mob_break_loose(mob_t *m)
|
||||
mob_spawn_copy(m);
|
||||
}
|
||||
|
||||
static mob_t *mt_m;
|
||||
static uint8_t mt_pg, mt_here;
|
||||
|
||||
static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
{
|
||||
uint8_t here;
|
||||
if (!m->active && !m->clean) return;
|
||||
here = (uint8_t)(m->room == pop_t_room);
|
||||
/* Рабочие переменные — file-scope, не локали: у функции пять вызовов, и
|
||||
* живой через них указатель на слот SDCC спиливал в кадр (16 байт, 99
|
||||
* обращений `-N(ix)`). То же лечение, что у draw_tile и blit_b_clip.
|
||||
* Реентерабельности нет и не требуется: единственный вызывающий —
|
||||
* pop_loose_mob_tick, и слоты он обходит последовательно. */
|
||||
mt_m = m; mt_pg = pg;
|
||||
if (!mt_m->active && !mt_m->clean) return;
|
||||
mt_here = (uint8_t)(mt_m->room == pop_t_room);
|
||||
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
|
||||
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
|
||||
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
|
||||
* прошлой позиции. */
|
||||
/* Гейта по `here` здесь БЫТЬ НЕ ДОЛЖНО: prev_y[pg] — это след, который
|
||||
/* Гейта по `mt_here` здесь БЫТЬ НЕ ДОЛЖНО: prev_y[mt_pg] — это след, который
|
||||
* кусок оставил ИМЕННО НА ЭТОЙ странице, и стереть его надо независимо
|
||||
* от того, где кусок сейчас. Пока чистка была под `here`, кусок,
|
||||
* от того, где кусок сейчас. Пока чистка была под `mt_here`, кусок,
|
||||
* улетевший в комнату снизу (mob_down_a_row), навсегда оставлял себя
|
||||
* на одной из двух страниц дабл-буфера — «застрявшая плита», видимая
|
||||
* через кадр (BUG-MOB-STALE-PAGE). */
|
||||
if (m->prev_y[pg] != MOB_Y_NONE) {
|
||||
if (mt_m->prev_y[mt_pg] != MOB_Y_NONE) {
|
||||
/* Ширина/высота — по СЛЕДУ, а не по спрайту. Спрайт куска целиком
|
||||
* влезает в 64x32 (41: 32x13 с y-3, 42: 26x15 с x+32,y-1, 43: 32x3),
|
||||
* но mob_render рисует ещё и ЦЕЛЫЙ соседний тайл поверх правой части
|
||||
@@ -1286,13 +1291,13 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* ЗАДЕЛ: правильнее не перерисовывать соседа вовсе, а КЛИПОВАТЬ
|
||||
* правую часть куска — оригинал так и делает (add_mob_to_objtable,
|
||||
* seg007:1161: clip.right = 40). Тогда коридор вернётся к 64x32. */
|
||||
pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - mob_heal_up, MOB_W, mob_heal_h);
|
||||
m->prev_y[pg] = MOB_Y_NONE;
|
||||
pop_heal_off(MOB_X0(mt_m->x), mt_m->prev_y[mt_pg] - mob_heal_up, MOB_W, mob_heal_h);
|
||||
mt_m->prev_y[mt_pg] = MOB_Y_NONE;
|
||||
}
|
||||
if (!m->active) { m->clean--; return; }
|
||||
if (!mt_m->active) { mt_m->clean--; return; }
|
||||
/* гравитация (move_loose): ускорение до 29 */
|
||||
if (m->speed < 29) m->speed += 3;
|
||||
m->y += m->speed;
|
||||
if (mt_m->speed < 29) mt_m->speed += 3;
|
||||
mt_m->y += mt_m->speed;
|
||||
/* move_loose (seg007:1126): низ куска пересёк границу своего ряда —
|
||||
* смотрим тайл этого ряда. Пусто/loose — валится дальше, пол —
|
||||
* приземление (loose_land: debris + do_knock). Через LOOSE кусок именно
|
||||
@@ -1304,9 +1309,9 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* плиты (1,6) и (1,7), на которые сверху сыплется гряда из комнаты 1.
|
||||
* Куски пролетали сквозь них правильно, а сами плиты оставались целыми и
|
||||
* нарисованными (найдено прогоном 2026-08-13). */
|
||||
if (m->row < 3 && m->y < 226 && MOB_Y_BOUND[m->row + 1] <= m->y) {
|
||||
uint8_t t = mob_tile_at(m);
|
||||
if (t == 11) mob_break_loose(m);
|
||||
if (mt_m->row < 3 && mt_m->y < 226 && MOB_Y_BOUND[mt_m->row + 1] <= mt_m->y) {
|
||||
uint8_t t = mob_tile_at(mt_m);
|
||||
if (t == 11) mob_break_loose(mt_m);
|
||||
if (t == 0 || t == 11) {
|
||||
/* mob_down_a_row (seg007:1387): ряд ниже, а из НИЖНЕГО ряда —
|
||||
* в комнату СНИЗУ (y -= 192, ряд 0). Именно так кусок пролетает
|
||||
@@ -1315,55 +1320,55 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* кнопку (2,7) комнаты 11. Раньше мы кусок на границе комнаты
|
||||
* ГАСИЛИ и подменяли одним «переходом вниз» из главного цикла —
|
||||
* дальше первой комнаты он не летел (BUG-MOB-MULTIROOM). */
|
||||
m->row++;
|
||||
if (m->row >= 3) {
|
||||
m->y = (int16_t)(m->y - 192);
|
||||
m->row = 0;
|
||||
m->room = pop_room_link(m->room, 3 /* вниз */);
|
||||
if (!m->room) { /* улетел из уровня — просто гаснет */
|
||||
m->active = 0; m->clean = 2; /* дочистить ОБЕ страницы */
|
||||
mt_m->row++;
|
||||
if (mt_m->row >= 3) {
|
||||
mt_m->y = (int16_t)(mt_m->y - 192);
|
||||
mt_m->row = 0;
|
||||
mt_m->room = pop_room_link(mt_m->room, 3 /* вниз */);
|
||||
if (!mt_m->room) { /* улетел из уровня — просто гаснет */
|
||||
mt_m->active = 0; mt_m->clean = 2; /* дочистить ОБЕ страницы */
|
||||
return;
|
||||
}
|
||||
here = (uint8_t)(m->room == pop_t_room);
|
||||
mt_here = (uint8_t)(mt_m->room == pop_t_room);
|
||||
}
|
||||
} else if (!here && pop_loose_exit) {
|
||||
} else if (!mt_here && pop_loose_exit) {
|
||||
/* Сигнал прошлой посадки в чужой комнате ещё не разобран (он
|
||||
* ОДИН на кадр) — держим кусок на границе ряда и садим следующим
|
||||
* кадром, как это уже сделано для pop_loose_landed ниже. Без
|
||||
* этого второй кусок затирал первый, и из нескольких плит,
|
||||
* улетевших в одну дыру, щебень оставляла только одна (найдено
|
||||
* прогоном 2026-08-13, уровень 13). */
|
||||
m->y = MOB_Y_BOUND[m->row + 1];
|
||||
} else if (!here) {
|
||||
mt_m->y = MOB_Y_BOUND[mt_m->row + 1];
|
||||
} else if (!mt_here) {
|
||||
/* Приземление в ПОКИНУТОЙ комнате: рисовать нечего, но щебень и
|
||||
* КНОПКА под куском обязаны сработать — сигналим главному циклу
|
||||
* комнатой и тайлом (loose_land, seg007:11E8, работает с
|
||||
* curmob.room, а не с отрисованной). */
|
||||
pop_loose_exit_room = m->room;
|
||||
pop_loose_exit = (uint8_t)(m->row * 10 + m->col + 1);
|
||||
pop_loose_exit_room = mt_m->room;
|
||||
pop_loose_exit = (uint8_t)(mt_m->row * 10 + mt_m->col + 1);
|
||||
/* clean = 2 даже для чужой комнаты: пока кусок летел, он мог
|
||||
* рисоваться в ЭТОЙ, и след остался на обеих страницах. */
|
||||
m->active = 0; m->clean = 2;
|
||||
mt_m->active = 0; mt_m->clean = 2;
|
||||
return;
|
||||
} else if (pop_loose_landed) {
|
||||
/* сигнал прошлого приземления ещё не разобран (один за кадр) —
|
||||
* подержим кусок на границе и приземлим следующим кадром */
|
||||
m->y = MOB_Y_BOUND[m->row + 1];
|
||||
mt_m->y = MOB_Y_BOUND[mt_m->row + 1];
|
||||
} else {
|
||||
m->y = MOB_Y_BOUND[m->row + 1];
|
||||
pop_loose_landed = (uint8_t)(m->row * 10 + m->col + 1);
|
||||
m->active = 0; m->clean = 2; /* кусок исчез — на его месте debris */
|
||||
mt_m->y = MOB_Y_BOUND[mt_m->row + 1];
|
||||
pop_loose_landed = (uint8_t)(mt_m->row * 10 + mt_m->col + 1);
|
||||
mt_m->active = 0; mt_m->clean = 2; /* кусок исчез — на его месте debris */
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Страховка: кусок ушёл ниже поля, не поймав границу ряда (в норме этого
|
||||
* не бывает — mob_down_a_row уводит его в комнату снизу). Просто гаснет,
|
||||
* как у оригинала кусок в комнате 0 (move_loose: `speed = -2`). */
|
||||
if (m->y - 27 >= POP_PLAYFIELD_H) {
|
||||
m->active = 0; m->clean = 2;
|
||||
if (mt_m->y - 27 >= POP_PLAYFIELD_H) {
|
||||
mt_m->active = 0; mt_m->clean = 2;
|
||||
return;
|
||||
}
|
||||
if (!here) return; /* чужая комната — считаем, но не рисуем */
|
||||
if (!mt_here) return; /* чужая комната — считаем, но не рисуем */
|
||||
/* Порядок «плита vs Kid». В оригинале объекты рисуются при обходе СВОИХ
|
||||
* тайлов (redraw_needed_tiles: ряды 2,1,0; колонки 0..9), и только внутри
|
||||
* ОДНОГО тайла сортируются по obj_y (sort_curr_objs, seg008:1561: кто
|
||||
@@ -1372,11 +1377,11 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* поверх Kid, если его тайл идёт в обходе ПОЗЖЕ тайла Kid, а при
|
||||
* совпадении тайлов — если он ниже по y. */
|
||||
{
|
||||
int8_t mrow = pop_y_to_row((int16_t)m->y); /* y_to_row_mod4 */
|
||||
int8_t mrow = pop_y_to_row((int16_t)mt_m->y); /* y_to_row_mod4 */
|
||||
uint8_t over;
|
||||
if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */
|
||||
else if (m->col != pop_bg_obj_col) over = (m->col > pop_bg_obj_col);
|
||||
else over = (m->y > pop_cd[POP_CH_KID].fpy);
|
||||
else if (mt_m->col != pop_bg_obj_col) over = (mt_m->col > pop_bg_obj_col);
|
||||
else over = (mt_m->y > pop_cd[POP_CH_KID].fpy);
|
||||
/* РИСОВАНИЯ ЗДЕСЬ БОЛЬШЕ НЕТ — только решение, в какой проход кусок
|
||||
* пойдёт. Пока heal и вывод шли в одной итерации, heal СЛЕДУЮЩЕГО
|
||||
* куска срезал верхнюю кромку уже нарисованного предыдущего: на 13-м
|
||||
@@ -1384,7 +1389,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* по y, и перекрытие гарантировано (артефакт «чёрный бар», найден
|
||||
* прогоном 2026-08-13, комната 23). Теперь сперва тикают ВСЕ куски
|
||||
* (то есть heal'ятся все), и только потом рисуются. */
|
||||
m->defer = over;
|
||||
mt_m->defer = over;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -503,10 +503,8 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
pb_yb + POP_YOFF + 1 <= pop_t_fclip_ly0 ||
|
||||
pb_yb + POP_YOFF - 64 >= pop_t_fclip_ly1))
|
||||
return;
|
||||
pop_dbg_b1(); /* ВРЕМЕННО */
|
||||
pb_img = (const uint8_t *)atlas_image(pb_a, pb_idx);
|
||||
gfx_w0_map(pb_a->page);
|
||||
pop_dbg_b2(); /* ВРЕМЕННО */
|
||||
/* Габарит читаем БАЙТАМИ. С `uint16_t pb_w = pb_img[0] | (pb_img[1] << 8)` SDCC
|
||||
* разворачивал каждую такую строку в два десятка IX-относительных
|
||||
* пересылок: 16-битные pb_w/pb_h не влезали в регистры, и функция целиком
|
||||
@@ -547,23 +545,18 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
/* pb_w<256 && pb_h<256 больше не проверяем — гарантировано типом. */
|
||||
if (pb_x >= 0 && pb_top >= 0 &&
|
||||
pb_x + (int)pb_w <= 320 && pb_top + (int)pb_h <= 256) {
|
||||
pop_dbg_b6(); /* ВРЕМЕННО: пометка «шли в noclip» */
|
||||
if (pop_upside) gfx_blit_noclip_vflip(pb_x, pb_top, pb_img);
|
||||
else gfx_blit_noclip(pb_x, pb_top, pb_img);
|
||||
} else {
|
||||
if (pop_upside) gfx_blit_part_vflip(pb_x, pb_top, pb_img, 0, 0, (int)pb_w, (int)pb_h);
|
||||
else gfx_blit_part(pb_x, pb_top, pb_img, 0, 0, (int)pb_w, (int)pb_h);
|
||||
}
|
||||
pop_dbg_b3(); /* ВРЕМЕННО */
|
||||
pop_cd_touch(pb_x, pb_top, (int)pb_w, (int)pb_h);
|
||||
pop_dbg_b4(); /* ВРЕМЕННО */
|
||||
} else {
|
||||
blit_b_clip(pb_img, pb_x, pb_top, pb_w, pb_h);
|
||||
pop_dbg_b3(); pop_dbg_b4(); /* ВРЕМЕННО: медленный путь целиком в b3 */
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
pop_dbg_b5(); /* ВРЕМЕННО */
|
||||
}
|
||||
|
||||
/* Блит getimage-блоба из ОБЫЧНОЙ памяти (W2), а не из EMM-страницы атласа:
|
||||
|
||||
Reference in New Issue
Block a user