P5: гейты холостого хода в pop_loose_tick — минус 33 840 тактов на кадре
Замер 11/15 показал, что loose-механика берёт 28 872 такта в комнате, где не анимируется ни одна плита и не летит ни один кусок. Раскладка зондами m9..m12: два цикла по тайлам 9 852, обход 14 слотов mob 12 090, поиск куска над головой Кида 5 868 (там ещё и банковый трамплин). Два гейта: loose_any (статик pop_map.c) — «идёт ли анимация плит». Ставят пять мест записи ненулевой фазы: make_loose_fall, ветка потолка в check_press, do_knock для обоих рядов и восстановление фазы из room_modif при входе в комнату. Снимает его сам цикл, по факту прохода, в котором не осталось ни одной живой фазы. pop_mob_busy (резидент pop_state.c) — «занят ли слот падающего куска» (active или дочистка clean). Ставит mob_alloc, снимает обход по факту пустой таблицы. В резиденте, а не в pop_room.c, потому что читает его pop_map из банка 3, а писучие статики банкового модуля наружу не видны. Гейт отвечает не на «есть ли в комнате плиты», а на «идёт ли анимация»: у лежащей плиты-потолка фаза нулевая, и крутить нечего (вопрос пользователя). Асимметрия намеренная — ложная единица стоит одного холостого прохода, ложный ноль стоит застывшей навсегда плиты, поэтому взвод стоит рядом с КАЖДОЙ записью, а снятие только по факту пустого прохода. Стало: 132 / 996 / 546, вся функция 28 872 -> 2 760. На кадре работа 801 768 -> 767 928. Ожидание по реестру было -28 000. Покрытие: новый phys_loose_gate_survives_room_change на пятое место взвода (фаза восстановлена входом в комнату) — единственное, которое не прогонял ни один тест, и дающее самый тихий отказ. Мутационная проверка: со снятым взводом тест падает (фаза 3 вместо 4). Заодно отладочный старт сразу в целевую комнату: make ROOM=15 POS=2 (дефолт), roomtest стартует в 11/15 с Кидом в (0,2). kid_init ставит x = x_bump[col] + TILE_SIZEX, а это левая граница СЛЕДУЮЩЕЙ колонки — с неё физика относила Кида в тайл чомпера, и он погибал на старте (найдено пользователем). Сдвиг внутрь на 2: колонку определяет весовая точка кадра, поэтому число снято замером, а не выведено геометрией. План работ между сессиями — docs/perf_registry.md §4: очередь позиций со статусами, текущий бюджет сцены, рецепт её воспроизведения и метод замера. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2133,6 +2133,21 @@ static void check_bumped(void)
|
||||
* make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */
|
||||
uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */
|
||||
|
||||
/* Гейт холостого хода pop_loose_tick. В подавляющем большинстве кадров
|
||||
* (и в целых комнатах) НИ ОДНА плита не анимируется, а два цикла по 30 и 10
|
||||
* позициям всё равно отрабатывают: замер 11/15 — 9 852 такта на комнату,
|
||||
* где loose-плит нет вовсе.
|
||||
*
|
||||
* Ставится ПРИ ВЗВОДЕ фазы — все пять мест записи ненулевого значения лежат
|
||||
* в этом файле (make_loose_fall, ветка потолка в check_press, do_knock для
|
||||
* обоих рядов, восстановление из room_modif в pop_loose_reset). Снимает его
|
||||
* САМ цикл, когда прошёл оба массива и не встретил ни одной ненулевой фазы.
|
||||
*
|
||||
* Асимметрия намеренная: ложная единица стоит одного холостого прохода,
|
||||
* ложный ноль — застывшей навсегда плиты. Поэтому взвод обязан стоять
|
||||
* рядом с КАЖДОЙ записью, а снятие — только по факту пустого прохода. */
|
||||
static uint8_t loose_any;
|
||||
|
||||
/* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */
|
||||
|
||||
/* make_loose_fall (seg007:0EF6): взвести отсчёт падения, если ещё не идёт. */
|
||||
@@ -2141,8 +2156,10 @@ static void make_loose_fall(int pos, uint8_t modifier)
|
||||
if (pos < 0 || pos >= 30) return;
|
||||
if ((g_fg[pos] & 0x1F) != TILE_LOOSE) return;
|
||||
if ((g_fg[pos] & 0x20)) return; /* «solid» loose — не от шага */
|
||||
if ((int8_t)pop_loose_modif[pos] <= 0) /* покой/тряска, не отсчёт */
|
||||
if ((int8_t)pop_loose_modif[pos] <= 0) { /* покой/тряска, не отсчёт */
|
||||
pop_loose_modif[pos] = modifier;
|
||||
loose_any = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* died_on_button (seg007:0776): на кнопке КТО-ТО УМЕР — эффект становится
|
||||
@@ -2213,6 +2230,7 @@ static void check_press(void)
|
||||
(g_above[c] & 0x1F) == TILE_LOOSE && !(g_above[c] & 0x20) &&
|
||||
(int8_t)pop_ceil_modif[c] <= 0) {
|
||||
pop_ceil_modif[c] = 1; /* make_loose_fall(1) */
|
||||
loose_any = 1;
|
||||
is_guard_notice = 1; /* seg006:1734 */
|
||||
}
|
||||
} else if (get_tile_above_char() == TILE_LOOSE) {
|
||||
@@ -2266,16 +2284,20 @@ static void do_knock(int tile_row)
|
||||
if (tile_row == -1) { /* ряд 2 комнаты сверху — плиты-потолки */
|
||||
if (!g_above || !g_link_u) return;
|
||||
for (col = 0; col < 10; col++)
|
||||
if ((g_above[col] & 0x1F) == TILE_LOOSE && pop_ceil_modif[col] == 0)
|
||||
if ((g_above[col] & 0x1F) == TILE_LOOSE && pop_ceil_modif[col] == 0) {
|
||||
pop_ceil_modif[col] = 0x80;
|
||||
loose_any = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tile_row < 0 || tile_row > 2) return;
|
||||
for (col = 0; col < 10; col++) {
|
||||
pos = tile_row * 10 + col;
|
||||
if ((g_fg[pos] & 0x1F) != TILE_LOOSE) continue;
|
||||
if (pop_loose_modif[pos] == 0)
|
||||
if (pop_loose_modif[pos] == 0) {
|
||||
pop_loose_modif[pos] = 0x80;
|
||||
loose_any = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2393,11 +2415,13 @@ void pop_loose_reset(void) __banked
|
||||
for (i = 0; i < 30; i++) {
|
||||
pop_loose_modif[i] = (mod && g_fg && (g_fg[i] & 0x1F) == TILE_LOOSE)
|
||||
? mod[i] : 0;
|
||||
if (pop_loose_modif[i]) loose_any = 1; /* взвод гейта — см. loose_any */
|
||||
}
|
||||
mod = (g_above && g_link_u) ? pop_trob_modif(g_link_u) : 0;
|
||||
for (i = 0; i < 10; i++) {
|
||||
pop_ceil_modif[i] = (mod && (g_above[i] & 0x1F) == TILE_LOOSE)
|
||||
? mod[20 + i] : 0;
|
||||
if (pop_ceil_modif[i]) loose_any = 1; /* взвод гейта — см. loose_any */
|
||||
/* ...и СНЯТЬ заочный trob: плиту-потолок отсюда снова ведёт
|
||||
* pop_loose_tick. Без этого её крутили бы ДВОЕ — trob по
|
||||
* room_modif и мы по pop_ceil_modif, — и она проваливалась бы
|
||||
@@ -2525,6 +2549,10 @@ static void check_loose_fall_on_kid(void)
|
||||
{
|
||||
int mcol, my;
|
||||
if (pop_kid_dead) return;
|
||||
/* Ни одного куска в воздухе — не платим за банковый трамплин в
|
||||
* pop_loose_mob_pos с обходом 14 слотов (замер 11/15: 5 868 тактов на
|
||||
* комнату, где не падает ничего). */
|
||||
if (!pop_mob_busy) return;
|
||||
if (!pop_loose_mob_pos(&mcol, &my)) return;
|
||||
/* Своё окно Char, как в оригинале (seg007:1199 — loadkid/savekid внутри
|
||||
* самой функции): зовут нас из pop_loose_tick, а тот идёт в главном
|
||||
@@ -2557,96 +2585,107 @@ void pop_loose_tick(void) __banked
|
||||
* Инкремент+сравнение считают row/col без деления. */
|
||||
uint8_t row = 0, col = 0;
|
||||
pop_dbg_m9(); /* ЗАМЕР: вход pop_loose_tick */
|
||||
for (pos = 0; pos < 30; pos++, lm++) {
|
||||
if (*lm != 0) {
|
||||
uint8_t m = ++*lm;
|
||||
/* Отложенный старт уровня 13 досчитал до нуля. Ноль у нас
|
||||
* означает «не анимируется», и плита застряла бы навсегда —
|
||||
* перескакиваем на 1. Стартовое значение выбрано с учётом
|
||||
* этого перескока (см. pop_check_fall_flo). */
|
||||
if (m == 0) m = *lm = 1;
|
||||
if (m & 0x80) { /* тряска (do_knock) */
|
||||
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:823): фазу со старшим битом НЕ
|
||||
* гасим и не трясём. На нём check_fall_flo раздаёт плитам
|
||||
* ОТРИЦАТЕЛЬНЫЙ старт (0xF0..0xFF) как отложенный таймер, и
|
||||
* обычная ветка «тряска кончилась на 0x84» убила бы его в
|
||||
* первом же кадре — плита не упала бы никогда. Пропущенная
|
||||
* инкрементом фаза сама дойдёт до 0x00, там старший бит
|
||||
* снимется, и дальше пойдёт обычный отсчёт до провала. */
|
||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||
/* ждём молча */
|
||||
} else if (m >= 0x84) { /* конец тряски: сброс + покой */
|
||||
/* Гейт холостого хода: если ни одна фаза не взведена, оба цикла (30 + 10
|
||||
* позиций) пропускаем целиком — замер 11/15 дал 9 852 такта на комнату,
|
||||
* где loose-плит нет вовсе. Флаг ставят места записи фазы, снимаем его
|
||||
* здесь и только по факту прохода, в котором не осталось ни одной живой
|
||||
* фазы (см. объявление loose_any). */
|
||||
if (loose_any) {
|
||||
uint8_t found = 0;
|
||||
for (pos = 0; pos < 30; pos++, lm++) {
|
||||
if (*lm != 0) {
|
||||
uint8_t m = ++*lm;
|
||||
/* Отложенный старт уровня 13 досчитал до нуля. Ноль у нас
|
||||
* означает «не анимируется», и плита застряла бы навсегда —
|
||||
* перескакиваем на 1. Стартовое значение выбрано с учётом
|
||||
* этого перескока (см. pop_check_fall_flo). */
|
||||
if (m == 0) m = *lm = 1;
|
||||
if (m & 0x80) { /* тряска (do_knock) */
|
||||
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:823): фазу со старшим битом НЕ
|
||||
* гасим и не трясём. На нём check_fall_flo раздаёт плитам
|
||||
* ОТРИЦАТЕЛЬНЫЙ старт (0xF0..0xFF) как отложенный таймер, и
|
||||
* обычная ветка «тряска кончилась на 0x84» убила бы его в
|
||||
* первом же кадре — плита не упала бы никогда. Пропущенная
|
||||
* инкрементом фаза сама дойдёт до 0x00, там старший бит
|
||||
* снимется, и дальше пойдёт обычный отсчёт до провала. */
|
||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||
/* ждём молча */
|
||||
} else if (m >= 0x84) { /* конец тряски: сброс + покой */
|
||||
*lm = 0;
|
||||
/* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
|
||||
* правая грань loose живёт в соседе). ОБЕ страницы —
|
||||
* иначе на одной застывает дрожащий кадр → мерцание. */
|
||||
pop_set_redraw(pos, POP_RD_LOOSE, 2);
|
||||
} else {
|
||||
pop_set_redraw(pos, POP_RD_LOOSE, 1); /* дрожащий кадр */
|
||||
}
|
||||
} else if (m >= 11 && pop_loose_fell) {
|
||||
/* Сигнал прошлого провала ещё не разобран главным циклом (он
|
||||
* ОДИН на кадр) — придержим плиту кадр, откатив фазу к 10.
|
||||
* Иначе из двух плит, провалившихся в одном кадре, дыру
|
||||
* запекала бы только вторая, а первая оставалась нарисованной
|
||||
* целой (уровень 13: check_fall_flo роняет гряду разом). */
|
||||
*lm = 10;
|
||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||
* Debris появляется ТОЛЬКО там, где приземлился падающий
|
||||
* кусок (mob), т.е. рядом ниже. Loose в нижнем ряду (2,6)
|
||||
* — кусок улетает из комнаты вниз, на месте остаётся пусто
|
||||
* (стена-вниз от тайла снизу + чёрный фон). */
|
||||
g_fg[pos] = TILE_EMPTY;
|
||||
/* remove_loose возвращает ТИП УРОВНЯ, и вызывающий кладёт
|
||||
* его модификатором пустой клетки (seg007:846/1083): по нему
|
||||
* draw_tile_right выбирает blueline_fram1 у дыры. */
|
||||
pop_trob_modif(g_room)[pos] = pop_palace;
|
||||
*lm = 0;
|
||||
/* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
|
||||
* правая грань loose живёт в соседе). ОБЕ страницы —
|
||||
* иначе на одной застывает дрожащий кадр → мерцание. */
|
||||
pop_set_redraw(pos, POP_RD_LOOSE, 2);
|
||||
} else {
|
||||
pop_set_redraw(pos, POP_RD_LOOSE, 1); /* дрожащий кадр */
|
||||
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
|
||||
/* ...и СОСЕДА СПРАВА: передний торец пола заезжает в его
|
||||
* клетку, и без этой пометки он оставался висеть над пустым
|
||||
* тайлом. То же, что делает pop_skel_wake_tile. */
|
||||
if ((pos % 10) < 9)
|
||||
pop_set_redraw((uint8_t)(pos + 1), POP_RD_FLOOR, 2);
|
||||
pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
|
||||
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению */
|
||||
} else { /* отсчёт 1..10 — дрожащий кадр */
|
||||
pop_set_redraw(pos, POP_RD_LOOSE, 1);
|
||||
}
|
||||
} else if (m >= 11 && pop_loose_fell) {
|
||||
/* Сигнал прошлого провала ещё не разобран главным циклом (он
|
||||
* ОДИН на кадр) — придержим плиту кадр, откатив фазу к 10.
|
||||
* Иначе из двух плит, провалившихся в одном кадре, дыру
|
||||
* запекала бы только вторая, а первая оставалась нарисованной
|
||||
* целой (уровень 13: check_fall_flo роняет гряду разом). */
|
||||
*lm = 10;
|
||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||
* Debris появляется ТОЛЬКО там, где приземлился падающий
|
||||
* кусок (mob), т.е. рядом ниже. Loose в нижнем ряду (2,6)
|
||||
* — кусок улетает из комнаты вниз, на месте остаётся пусто
|
||||
* (стена-вниз от тайла снизу + чёрный фон). */
|
||||
g_fg[pos] = TILE_EMPTY;
|
||||
/* remove_loose возвращает ТИП УРОВНЯ, и вызывающий кладёт
|
||||
* его модификатором пустой клетки (seg007:846/1083): по нему
|
||||
* draw_tile_right выбирает blueline_fram1 у дыры. */
|
||||
pop_trob_modif(g_room)[pos] = pop_palace;
|
||||
*lm = 0;
|
||||
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
|
||||
/* ...и СОСЕДА СПРАВА: передний торец пола заезжает в его
|
||||
* клетку, и без этой пометки он оставался висеть над пустым
|
||||
* тайлом. То же, что делает pop_skel_wake_tile. */
|
||||
if ((pos % 10) < 9)
|
||||
pop_set_redraw((uint8_t)(pos + 1), POP_RD_FLOOR, 2);
|
||||
pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
|
||||
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению */
|
||||
} else { /* отсчёт 1..10 — дрожащий кадр */
|
||||
pop_set_redraw(pos, POP_RD_LOOSE, 1);
|
||||
}
|
||||
}
|
||||
if (++col == 10) { col = 0; row++; } /* row/col без деления */
|
||||
}
|
||||
/* Плиты-ПОТОЛКИ (ряд 2 комнаты сверху): тот же animate_loose, но кадры
|
||||
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
|
||||
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
|
||||
lm = pop_ceil_modif; /* тот же указательный обход */
|
||||
for (col = 0; col < 10; col++, lm++) {
|
||||
if (*lm != 0) {
|
||||
uint8_t m = ++*lm;
|
||||
if (m == 0) m = *lm = 1; /* см. pop_loose_tick выше */
|
||||
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
||||
/* Тот же спецкейс уровня 13, и здесь он ГЛАВНЫЙ: плиты,
|
||||
* которым check_fall_flo раздал отложенный старт, лежат
|
||||
* именно в ряду 2 комнаты СВЕРХУ, то есть ведёт их этот
|
||||
* цикл, а не соседний. */
|
||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||
/* ждём молча */
|
||||
} else if (m >= 0x84) { *lm = 0;
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
|
||||
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
|
||||
} else if (m >= 11 && pop_ceil_fell) {
|
||||
*lm = 10; /* сигнал занят — кадр подождём */
|
||||
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
||||
*lm = 0;
|
||||
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */
|
||||
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
|
||||
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
|
||||
} else {
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL, 1); /* отсчёт 1..10 */
|
||||
if (*lm) found = 1; /* фаза ещё жива — гейт держим */
|
||||
}
|
||||
if (++col == 10) { col = 0; row++; } /* row/col без деления */
|
||||
}
|
||||
/* Плиты-ПОТОЛКИ (ряд 2 комнаты сверху): тот же animate_loose, но кадры
|
||||
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
|
||||
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
|
||||
lm = pop_ceil_modif; /* тот же указательный обход */
|
||||
for (col = 0; col < 10; col++, lm++) {
|
||||
if (*lm != 0) {
|
||||
uint8_t m = ++*lm;
|
||||
if (m == 0) m = *lm = 1; /* см. pop_loose_tick выше */
|
||||
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
||||
/* Тот же спецкейс уровня 13, и здесь он ГЛАВНЫЙ: плиты,
|
||||
* которым check_fall_flo раздал отложенный старт, лежат
|
||||
* именно в ряду 2 комнаты СВЕРХУ, то есть ведёт их этот
|
||||
* цикл, а не соседний. */
|
||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||
/* ждём молча */
|
||||
} else if (m >= 0x84) { *lm = 0;
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
|
||||
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
|
||||
} else if (m >= 11 && pop_ceil_fell) {
|
||||
*lm = 10; /* сигнал занят — кадр подождём */
|
||||
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
||||
*lm = 0;
|
||||
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */
|
||||
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
|
||||
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
|
||||
} else {
|
||||
pop_set_redraw_above(col, POP_RDA_CEIL, 1); /* отсчёт 1..10 */
|
||||
}
|
||||
if (*lm) found = 1; /* фаза ещё жива — гейт держим */
|
||||
}
|
||||
}
|
||||
loose_any = found;
|
||||
}
|
||||
pop_dbg_m10(); /* ЗАМЕР: оба цикла по тайлам пройдены */
|
||||
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||||
|
||||
Reference in New Issue
Block a user