Ускорение холостого хода: указательный обход вместо arr[i] в горячих циклах
Замер зелёного блока (комната 23 уровня 13, MAME, такты эмулятора) показал, что 86% его стоимости в ПОКОЕ — это pop_loose_tick, который не делает ничего. Причина — кодоген SDCC, подтверждена чтением .asm. С `int pos` и записью pop_loose_modif[pos] компилятор держал счётчик в IX-фрейме, каждую итерацию заново складывал 16-битный адрес элемента, клал его в локал и тут же вычитывал обратно парами `pop bc / pop hl / push hl / push bc`. 40 холостых итераций (30 тайлов + 10 потолков) стоили 27 936 тактов. То же в pop_loose_mob_tick: запись mobs[i] заставляла умножать i на sizeof(mob_t)=15 заново под КАЖДОЕ поле (.active/.clean/.x/.y), 14 пустых слотов — 35 790. Правка — обход указателем, счётчик uint8_t, пустые слоты отсеиваются в вызывающем цикле (а не гардом внутри mob_tick_one, до которого надо ещё дойти). Холостая итерация стала `ld a,(de) / or a,a / jp Z` — три инструкции вместо дюжины с обращениями к памяти. Результат (такты MAME, холостой кадр): циклы по тайлам 27 936 -> 9 852 (2,8x) pop_loose_mob_tick 35 790 -> 7 416 (4,8x) pop_loose_tick 70 866 -> 24 384 (2,9x) ЗЕЛЁНЫЙ БЛОК 82 242 -> 35 760 (2,3x) Банки ужались: BANK3 -90 Б, BANK7 -74 Б. Все 8 наборов tests-host проходят; комната 23 в MAME рисуется корректно. Плюс ВРЕМЕННАЯ оснастка замера (маркеры m9..m16, pop_dbg_kind) — она же показала, что пик зелёного блока сидит НЕ в тряске плит, а в запекании тайлов; разбор продолжается, оснастку снять перед закрытием темы. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -256,6 +256,32 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
|
|||||||
Осознанное расхождение: мигания Кида спрайтами тени во время вспышки нет —
|
Осознанное расхождение: мигания Кида спрайтами тени во время вспышки нет —
|
||||||
запись в [`../docs/impl_diff.md`](../docs/impl_diff.md).
|
запись в [`../docs/impl_diff.md`](../docs/impl_diff.md).
|
||||||
|
|
||||||
|
<a id="loose-shake-runs"></a>
|
||||||
|
### LOOSE-SHAKE-RUNS. Дрожание плит: пометки по сменам кадра, а не по кадрам
|
||||||
|
|
||||||
|
**Идея пользователя 2026-08-13, на этап полиша.**
|
||||||
|
|
||||||
|
`pop_loose_tick` на КАЖДОМ кадре отсчёта ставит `pop_set_redraw(pos,
|
||||||
|
POP_RD_LOOSE, 1)` — полную перерисовку тайла, десять раз за отсчёт. А
|
||||||
|
кадр дрожания меняется не каждую фазу: таблицы
|
||||||
|
(`POP_LOOSE_FRAM_BOTTOM` = 43,73,43,74,74,43,43,43,74,74,74) дают
|
||||||
|
|
||||||
|
фаза 1 2 3 4 5 6 7 8 9 10
|
||||||
|
кадр 73 43 74 74 43 43 43 74 74 74
|
||||||
|
^^^^^ ^^^^^^^^ ^^^^^^^^
|
||||||
|
парами и тройками одно и то же
|
||||||
|
|
||||||
|
С фазы 5 кадры идут ТРОЙКАМИ. Значит вместо шести пометок с `pages = 1`
|
||||||
|
хватит двух — на фазах 5 и 8 — но с `pages = 2` (обе страницы дабл-буфера,
|
||||||
|
иначе на второй застынет старый спрайт и пойдёт мерцание через кадр).
|
||||||
|
|
||||||
|
**Выигрыш ~20 % работы зелёного блока и ТОЛЬКО на дрожащих плитах.**
|
||||||
|
Наивный вариант «ставить лишь на смене кадра» выигрыша НЕ даёт: пять смен
|
||||||
|
по две страницы — те же десять перерисовок (проверено арифметикой
|
||||||
|
2026-08-13, до правки).
|
||||||
|
|
||||||
|
Вопрос к этапу полиша: стоит ли неочевидный код такого узкого выигрыша.
|
||||||
|
|
||||||
<a id="bg-once"></a>
|
<a id="bg-once"></a>
|
||||||
### BG-ONCE. Задний фон рисуется ОДИН раз; вместо чёрных баров — heal
|
### BG-ONCE. Задний фон рисуется ОДИН раз; вместо чёрных баров — heal
|
||||||
|
|
||||||
|
|||||||
@@ -2494,7 +2494,13 @@ static void check_loose_fall_on_kid(void)
|
|||||||
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
|
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
|
||||||
void pop_loose_tick(void) __banked
|
void pop_loose_tick(void) __banked
|
||||||
{
|
{
|
||||||
int pos;
|
uint8_t pos;
|
||||||
|
/* Массивы обходим УКАЗАТЕЛЕМ, а не индексом. С `int pos` SDCC держал
|
||||||
|
* счётчик в IX-фрейме, каждую итерацию заново складывал 16-битный адрес
|
||||||
|
* элемента, клал его в локал и тут же вычитывал обратно парами pop/push:
|
||||||
|
* 40 холостых итераций стоили 27 936 тактов (замер 2026-08-13). Ходьба
|
||||||
|
* указателем убирает и адресную арифметику, и IX-фрейм. */
|
||||||
|
uint8_t *lm = pop_loose_modif;
|
||||||
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
|
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
|
||||||
* нет аппаратного деления, и SDCC выносил оба деления (__divuchar/
|
* нет аппаратного деления, и SDCC выносил оба деления (__divuchar/
|
||||||
* __moduchar) в начало КАЖДОЙ итерации (общее подвыражение аргументов
|
* __moduchar) в начало КАЖДОЙ итерации (общее подвыражение аргументов
|
||||||
@@ -2502,14 +2508,15 @@ void pop_loose_tick(void) __banked
|
|||||||
* комнатах без loose-плит (условия if к тому моменту ещё не проверены).
|
* комнатах без loose-плит (условия if к тому моменту ещё не проверены).
|
||||||
* Инкремент+сравнение считают row/col без деления. */
|
* Инкремент+сравнение считают row/col без деления. */
|
||||||
uint8_t row = 0, col = 0;
|
uint8_t row = 0, col = 0;
|
||||||
for (pos = 0; pos < 30; pos++) {
|
pop_dbg_m9(); /* ЗАМЕР: вход pop_loose_tick */
|
||||||
if (pop_loose_modif[pos] != 0) {
|
for (pos = 0; pos < 30; pos++, lm++) {
|
||||||
uint8_t m = ++pop_loose_modif[pos];
|
if (*lm != 0) {
|
||||||
|
uint8_t m = ++*lm;
|
||||||
/* Отложенный старт уровня 13 досчитал до нуля. Ноль у нас
|
/* Отложенный старт уровня 13 досчитал до нуля. Ноль у нас
|
||||||
* означает «не анимируется», и плита застряла бы навсегда —
|
* означает «не анимируется», и плита застряла бы навсегда —
|
||||||
* перескакиваем на 1. Стартовое значение выбрано с учётом
|
* перескакиваем на 1. Стартовое значение выбрано с учётом
|
||||||
* этого перескока (см. pop_check_fall_flo). */
|
* этого перескока (см. pop_check_fall_flo). */
|
||||||
if (m == 0) m = pop_loose_modif[pos] = 1;
|
if (m == 0) m = *lm = 1;
|
||||||
if (m & 0x80) { /* тряска (do_knock) */
|
if (m & 0x80) { /* тряска (do_knock) */
|
||||||
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:823): фазу со старшим битом НЕ
|
/* СПЕЦКЕЙС УРОВНЯ 13 (seg007:823): фазу со старшим битом НЕ
|
||||||
* гасим и не трясём. На нём check_fall_flo раздаёт плитам
|
* гасим и не трясём. На нём check_fall_flo раздаёт плитам
|
||||||
@@ -2521,7 +2528,7 @@ void pop_loose_tick(void) __banked
|
|||||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||||
/* ждём молча */
|
/* ждём молча */
|
||||||
} else if (m >= 0x84) { /* конец тряски: сброс + покой */
|
} else if (m >= 0x84) { /* конец тряски: сброс + покой */
|
||||||
pop_loose_modif[pos] = 0;
|
*lm = 0;
|
||||||
/* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
|
/* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
|
||||||
* правая грань loose живёт в соседе). ОБЕ страницы —
|
* правая грань loose живёт в соседе). ОБЕ страницы —
|
||||||
* иначе на одной застывает дрожащий кадр → мерцание. */
|
* иначе на одной застывает дрожащий кадр → мерцание. */
|
||||||
@@ -2535,7 +2542,7 @@ void pop_loose_tick(void) __banked
|
|||||||
* Иначе из двух плит, провалившихся в одном кадре, дыру
|
* Иначе из двух плит, провалившихся в одном кадре, дыру
|
||||||
* запекала бы только вторая, а первая оставалась нарисованной
|
* запекала бы только вторая, а первая оставалась нарисованной
|
||||||
* целой (уровень 13: check_fall_flo роняет гряду разом). */
|
* целой (уровень 13: check_fall_flo роняет гряду разом). */
|
||||||
pop_loose_modif[pos] = 10;
|
*lm = 10;
|
||||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||||
* Debris появляется ТОЛЬКО там, где приземлился падающий
|
* Debris появляется ТОЛЬКО там, где приземлился падающий
|
||||||
@@ -2547,7 +2554,7 @@ void pop_loose_tick(void) __banked
|
|||||||
* его модификатором пустой клетки (seg007:846/1083): по нему
|
* его модификатором пустой клетки (seg007:846/1083): по нему
|
||||||
* draw_tile_right выбирает blueline_fram1 у дыры. */
|
* draw_tile_right выбирает blueline_fram1 у дыры. */
|
||||||
pop_trob_modif(g_room)[pos] = pop_palace;
|
pop_trob_modif(g_room)[pos] = pop_palace;
|
||||||
pop_loose_modif[pos] = 0;
|
*lm = 0;
|
||||||
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
|
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
|
||||||
/* ...и СОСЕДА СПРАВА: передний торец пола заезжает в его
|
/* ...и СОСЕДА СПРАВА: передний торец пола заезжает в его
|
||||||
* клетку, и без этой пометки он оставался висеть над пустым
|
* клетку, и без этой пометки он оставался висеть над пустым
|
||||||
@@ -2565,10 +2572,11 @@ void pop_loose_tick(void) __banked
|
|||||||
/* Плиты-ПОТОЛКИ (ряд 2 комнаты сверху): тот же animate_loose, но кадры
|
/* Плиты-ПОТОЛКИ (ряд 2 комнаты сверху): тот же animate_loose, но кадры
|
||||||
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
|
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
|
||||||
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
|
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
|
||||||
for (col = 0; col < 10; col++) {
|
lm = pop_ceil_modif; /* тот же указательный обход */
|
||||||
if (pop_ceil_modif[col] != 0) {
|
for (col = 0; col < 10; col++, lm++) {
|
||||||
uint8_t m = ++pop_ceil_modif[col];
|
if (*lm != 0) {
|
||||||
if (m == 0) m = pop_ceil_modif[col] = 1; /* см. pop_loose_tick выше */
|
uint8_t m = ++*lm;
|
||||||
|
if (m == 0) m = *lm = 1; /* см. pop_loose_tick выше */
|
||||||
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
||||||
/* Тот же спецкейс уровня 13, и здесь он ГЛАВНЫЙ: плиты,
|
/* Тот же спецкейс уровня 13, и здесь он ГЛАВНЫЙ: плиты,
|
||||||
* которым check_fall_flo раздал отложенный старт, лежат
|
* которым check_fall_flo раздал отложенный старт, лежат
|
||||||
@@ -2576,13 +2584,13 @@ void pop_loose_tick(void) __banked
|
|||||||
* цикл, а не соседний. */
|
* цикл, а не соседний. */
|
||||||
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
if (pop_current_level == POP_LOOSE_TILES_LEVEL) {
|
||||||
/* ждём молча */
|
/* ждём молча */
|
||||||
} else if (m >= 0x84) { pop_ceil_modif[col] = 0;
|
} else if (m >= 0x84) { *lm = 0;
|
||||||
pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
|
pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
|
||||||
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
|
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
|
||||||
} else if (m >= 11 && pop_ceil_fell) {
|
} else if (m >= 11 && pop_ceil_fell) {
|
||||||
pop_ceil_modif[col] = 10; /* сигнал занят — кадр подождём */
|
*lm = 10; /* сигнал занят — кадр подождём */
|
||||||
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
||||||
pop_ceil_modif[col] = 0;
|
*lm = 0;
|
||||||
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
||||||
pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */
|
pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */
|
||||||
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
|
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
|
||||||
@@ -2592,8 +2600,11 @@ void pop_loose_tick(void) __banked
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pop_dbg_m10(); /* ЗАМЕР: оба цикла по тайлам пройдены */
|
||||||
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||||||
|
pop_dbg_m11(); /* ЗАМЕР: pop_loose_mob_tick сделан */
|
||||||
check_loose_fall_on_kid(); /* seg007:1192 — плита падает Киду на голову */
|
check_loose_fall_on_kid(); /* seg007:1192 — плита падает Киду на голову */
|
||||||
|
pop_dbg_m12(); /* ЗАМЕР: check_loose_fall_on_kid сделан */
|
||||||
/* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД
|
/* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД
|
||||||
* разбором нового: иначе при двух плитах, севших подряд, land_bake
|
* разбором нового: иначе при двух плитах, севших подряд, land_bake
|
||||||
* затирался новым и на одной странице оставался старый пол. */
|
* затирался новым и на одной странице оставался старый пол. */
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "pop_redraw.h"
|
#include "pop_redraw.h"
|
||||||
#include "pop_bg.h"
|
#include "pop_bg.h"
|
||||||
|
#include "pop_state.h" /* ВРЕМЕННО: счётчики состава перерисовок */
|
||||||
|
|
||||||
#define NTILES 30
|
#define NTILES 30
|
||||||
#define NCOLS 10
|
#define NCOLS 10
|
||||||
@@ -79,9 +80,13 @@ void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked
|
|||||||
void pop_redraw_needed(void) __banked
|
void pop_redraw_needed(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t i, row = 0, col = 0, left = 0;
|
uint8_t i, row = 0, col = 0, left = 0;
|
||||||
|
uint8_t cnt[10], tot = 0; /* ВРЕМЕННО: состав кадра */
|
||||||
if (!rd_pending) return;
|
if (!rd_pending) return;
|
||||||
|
for (i = 0; i < 10; i++) cnt[i] = 0;
|
||||||
for (i = 0; i < NTILES; i++) {
|
for (i = 0; i < NTILES; i++) {
|
||||||
if (rd_cnt[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]) {
|
switch (rd_kind[i]) {
|
||||||
case POP_RD_SPIKE: pop_spike_redraw(row, col); break;
|
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: pop_loose_shake_draw(row, col); break;
|
||||||
@@ -92,6 +97,7 @@ void pop_redraw_needed(void) __banked
|
|||||||
case POP_RD_CHOMP: pop_chomp_redraw(row, col); break;
|
case POP_RD_CHOMP: pop_chomp_redraw(row, col); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
pop_dbg_m16(); /* ВРЕМЕННО */
|
||||||
if (--rd_cnt[i]) left++;
|
if (--rd_cnt[i]) left++;
|
||||||
else rd_kind[i] = 0;
|
else rd_kind[i] = 0;
|
||||||
}
|
}
|
||||||
@@ -99,11 +105,18 @@ void pop_redraw_needed(void) __banked
|
|||||||
}
|
}
|
||||||
for (i = 0; i < NCOLS; i++) {
|
for (i = 0; i < NCOLS; i++) {
|
||||||
if (rda_cnt[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);
|
if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i);
|
||||||
else pop_ceil_bake_empty(i);
|
else pop_ceil_bake_empty(i);
|
||||||
|
pop_dbg_m16(); /* ВРЕМЕННО */
|
||||||
if (--rda_cnt[i]) left++;
|
if (--rda_cnt[i]) left++;
|
||||||
else rda_kind[i] = 0;
|
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;
|
rd_pending = left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -570,16 +570,31 @@ void pop_room_redraw_seam_left(uint8_t rows) __banked
|
|||||||
* shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту
|
* shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту
|
||||||
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
|
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
|
||||||
|
|
||||||
|
/* Полоса кладки у потолка: дрожащая плита ряда −1 — порт redraw_needed_above
|
||||||
|
* (seg008:0221).
|
||||||
|
*
|
||||||
|
* Оригинал рисует ТОЛЬКО СВОЮ колонку: кусок, свисающий вправо, кладёт её же
|
||||||
|
* draw_tile_right. Здесь дополнительно звался `draw_tile(-1, col + 1)` —
|
||||||
|
* целый соседний тайл, — и это удваивало работу. Замер 2026-08-13: пик
|
||||||
|
* зелёного блока 887 000 тактов при кадровом интервале 430 000 состоял ровно
|
||||||
|
* из шести вызовов этой функции, то есть двенадцати отрисовок тайла.
|
||||||
|
*
|
||||||
|
* Батчинг смежных колонок (один heal на диапазон) пробовался и убран: плиты
|
||||||
|
* стартуют со случайными задержками (check_fall_flo), в кадре дрожат
|
||||||
|
* разрозненные колонки, пробег почти всегда длиной в одну — механика не
|
||||||
|
* окупалась, но крутилась каждый кадр. */
|
||||||
void pop_ceil_shake_draw(int col) __banked
|
void pop_ceil_shake_draw(int col) __banked
|
||||||
{
|
{
|
||||||
int x = POP_COL_XH[col] * 8;
|
int x = POP_COL_XH[col] * 8;
|
||||||
pop_heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
|
pop_dbg_m13(); /* ЗАМЕР: вход pop_ceil_shake_draw */
|
||||||
|
pop_heal_off(x, 0, 64, CEIL_BAND_H);
|
||||||
|
pop_dbg_m14(); /* ЗАМЕР: heal сделан */
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
pop_t_clip_top = POP_YOFF;
|
pop_t_clip_top = POP_YOFF;
|
||||||
draw_tile(-1, col);
|
draw_tile(-1, col);
|
||||||
if (col + 1 < 10) draw_tile(-1, col + 1);
|
|
||||||
pop_t_clip_top = 0;
|
pop_t_clip_top = 0;
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
pop_dbg_m15(); /* ЗАМЕР: draw_tile сделан */
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_ceil_bake_empty(int col) __banked
|
void pop_ceil_bake_empty(int col) __banked
|
||||||
@@ -850,6 +865,8 @@ typedef struct {
|
|||||||
* разбора артефактов множественного падения; наружу таблица не объявлена и
|
* разбора артефактов множественного падения; наружу таблица не объявлена и
|
||||||
* никем не читается. */
|
* никем не читается. */
|
||||||
mob_t mobs[MOB_MAX];
|
mob_t mobs[MOB_MAX];
|
||||||
|
/* Сколько кусков сейчас летит (active). Считает pop_loose_mob_tick. */
|
||||||
|
static uint8_t mobs_live;
|
||||||
|
|
||||||
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
||||||
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
||||||
@@ -863,14 +880,20 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
|
|||||||
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
||||||
#define MOB_Y_NONE 0x7FFF
|
#define MOB_Y_NONE 0x7FFF
|
||||||
|
|
||||||
static void mob_spawn(uint8_t room, int row, int col)
|
/* Свободный слот = не летит И дочистка завершена (иначе затрём хвост,
|
||||||
|
* который ещё не стёрт со второй страницы). Общий для обычного отрыва и
|
||||||
|
* для копии из loose_fall — раньше цикл был написан дважды. */
|
||||||
|
static mob_t *mob_alloc(void)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
mob_t *m = 0;
|
|
||||||
/* свободный слот = не летит И дочистка завершена (иначе затрём хвост,
|
|
||||||
* который ещё не стёрт со второй страницы) */
|
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; }
|
if (!mobs[i].active && !mobs[i].clean) return &mobs[i];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mob_spawn(uint8_t room, int row, int col)
|
||||||
|
{
|
||||||
|
mob_t *m = mob_alloc();
|
||||||
if (!m) return; /* таблица полна — кусок без спрайта (тайл всё равно пуст) */
|
if (!m) return; /* таблица полна — кусок без спрайта (тайл всё равно пуст) */
|
||||||
m->x = POP_COL_XH[col] * 8;
|
m->x = POP_COL_XH[col] * 8;
|
||||||
m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
|
m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
|
||||||
@@ -897,8 +920,7 @@ void pop_loose_mob_spawn(int row, int col) __banked
|
|||||||
* Через границу комнаты — как mob_down_a_row: y −= 192, ряд 0, комната снизу. */
|
* Через границу комнаты — как mob_down_a_row: y −= 192, ряд 0, комната снизу. */
|
||||||
static void mob_spawn_copy(const mob_t *src)
|
static void mob_spawn_copy(const mob_t *src)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
mob_t *m;
|
||||||
mob_t *m = 0;
|
|
||||||
int16_t y = (int16_t)(src->y + 6);
|
int16_t y = (int16_t)(src->y + 6);
|
||||||
int8_t row = (int8_t)(src->row + 1);
|
int8_t row = (int8_t)(src->row + 1);
|
||||||
uint8_t room = src->room;
|
uint8_t room = src->room;
|
||||||
@@ -909,8 +931,7 @@ static void mob_spawn_copy(const mob_t *src)
|
|||||||
room = pop_room_link(room, 3 /* вниз */);
|
room = pop_room_link(room, 3 /* вниз */);
|
||||||
if (!room) return; /* ушло из уровня — куску негде лететь */
|
if (!room) return; /* ушло из уровня — куску негде лететь */
|
||||||
}
|
}
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
m = mob_alloc();
|
||||||
if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; }
|
|
||||||
if (!m) return;
|
if (!m) return;
|
||||||
m->x = src->x;
|
m->x = src->x;
|
||||||
m->y = y;
|
m->y = y;
|
||||||
@@ -1193,6 +1214,11 @@ static void mob_mark_neighbour(const mob_t *m)
|
|||||||
static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
|
static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
|
||||||
{
|
{
|
||||||
uint8_t order[MOB_MAX], n = 0, i, j;
|
uint8_t order[MOB_MAX], n = 0, i, j;
|
||||||
|
/* Ранний выход: проход зовётся ДВАЖДЫ за кадр (под Kid и поверх него), и
|
||||||
|
* без него мы перебирали бы все MOB_MAX слотов 28 раз в кадре даже
|
||||||
|
* когда в воздухе пусто. Счётчик ведёт тик, которому проход по слотам
|
||||||
|
* нужен и так. */
|
||||||
|
if (!mobs_live) return;
|
||||||
for (i = 0; i < MOB_MAX; i++) {
|
for (i = 0; i < MOB_MAX; i++) {
|
||||||
mob_t *m = &mobs[i];
|
mob_t *m = &mobs[i];
|
||||||
if (!m->active || m->room != pop_t_room) continue;
|
if (!m->active || m->room != pop_t_room) continue;
|
||||||
@@ -1218,25 +1244,34 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
|
|||||||
|
|
||||||
void pop_loose_mob_tick(void) __banked
|
void pop_loose_mob_tick(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t pg = gfx_get_draw_page() & 1, i;
|
uint8_t pg = gfx_get_draw_page() & 1, i, live = 0;
|
||||||
for (i = 0; i < MOB_MAX; i++) {
|
/* Слоты обходим УКАЗАТЕЛЕМ и пустые отсеиваем ЗДЕСЬ, а не внутри
|
||||||
|
* mob_tick_one. Индексная запись mobs[i] заставляла SDCC умножать i на
|
||||||
|
* sizeof(mob_t)=15 заново под каждое поле (.active/.clean/.x/.y), и 14
|
||||||
|
* пустых слотов стоили 35 790 тактов в кадре, где не падает НИЧЕГО
|
||||||
|
* (замер 2026-08-13). Гард в самом mob_tick_one оставлен: функция
|
||||||
|
* зовётся и из других мест. */
|
||||||
|
mob_t *m = mobs;
|
||||||
|
for (i = 0; i < MOB_MAX; i++, m++) {
|
||||||
/* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом,
|
/* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом,
|
||||||
* мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя
|
* мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя
|
||||||
* (DRAW-COST). Взводим ДО тика: кусок может исчезнуть прямо в нём, а
|
* (DRAW-COST). Взводим ДО тика: кусок может исчезнуть прямо в нём, а
|
||||||
* стереть себя ему всё равно надо будет. */
|
* стереть себя ему всё равно надо будет. */
|
||||||
if (mobs[i].active || mobs[i].clean)
|
if (!m->active && !m->clean) continue; /* пустой слот — ни адреса, ни вызова */
|
||||||
/* Метка «фон трогали вот здесь» обязана покрывать ВЕСЬ коридор
|
/* Метка «фон трогали вот здесь» обязана покрывать ВЕСЬ коридор
|
||||||
* heal'а, а не габарит спрайта. Иначе персонаж, попавший в
|
* heal'а, а не габарит спрайта. Иначе персонаж, попавший в
|
||||||
* расширенную часть коридора, стирается heal'ом, но перерисовать
|
* расширенную часть коридора, стирается heal'ом, но перерисовать
|
||||||
* себя не просит (pop_char_skip_mask его пропускает) — и просто
|
* себя не просит (pop_char_skip_mask его пропускает) — и просто
|
||||||
* исчезает с экрана. Ровно это и случилось, когда коридор
|
* исчезает с экрана. Ровно это и случилось, когда коридор
|
||||||
* расширили под транзитную перерисовку соседа (прогон
|
* расширили под транзитную перерисовку соседа (прогон
|
||||||
* 2026-08-13: в комнате 23 после падения гряды пропадал Кид).
|
* 2026-08-13: в комнате 23 после падения гряды пропадал Кид).
|
||||||
* Запас сверху — на путь, пройденный за кадр (speed до 29): heal
|
* Запас сверху — на путь, пройденный за кадр (speed до 29): heal
|
||||||
* стирает ПРОШЛУЮ позицию, она выше текущей. */
|
* стирает ПРОШЛУЮ позицию, она выше текущей. */
|
||||||
pop_cd_touch(MOB_X0(mobs[i].x), mobs[i].y - 27 + POP_YOFF, MOB_W, 64);
|
pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64);
|
||||||
mob_tick_one(&mobs[i], pg);
|
mob_tick_one(m, pg);
|
||||||
|
if (m->active) live++;
|
||||||
}
|
}
|
||||||
|
mobs_live = live;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mob_render(mob_t *m, uint8_t pg)
|
static void mob_render(mob_t *m, uint8_t pg)
|
||||||
|
|||||||
@@ -113,3 +113,36 @@ void pop_dbg_m1(void) { }
|
|||||||
void pop_dbg_m2(void) { }
|
void pop_dbg_m2(void) { }
|
||||||
void pop_dbg_m3(void) { }
|
void pop_dbg_m3(void) { }
|
||||||
void pop_dbg_m4(void) { }
|
void pop_dbg_m4(void) { }
|
||||||
|
|
||||||
|
/* ВРЕМЕННО (разбор зелёного блока 2026-08-13): состав перерисовок в САМОМ
|
||||||
|
* тяжёлом кадре. pop_redraw_needed считает вызовы по видам, и если кадр
|
||||||
|
* оказался тяжелее прежнего максимума — сохраняет расклад сюда.
|
||||||
|
* Индексы 0..7 — POP_RD_*, 8..9 — POP_RDA_* (полоса у потолка). */
|
||||||
|
uint8_t pop_dbg_rdmax[10];
|
||||||
|
uint8_t pop_dbg_rdmax_tot;
|
||||||
|
|
||||||
|
/* ВРЕМЕННО (замер внутренностей 2026-08-13): m5..m8 — границы внутри
|
||||||
|
* pop_ceil_shake_draw (heal / draw_tile) и mob_render (heal / блиты). */
|
||||||
|
void pop_dbg_m5(void) { }
|
||||||
|
void pop_dbg_m6(void) { }
|
||||||
|
void pop_dbg_m7(void) { }
|
||||||
|
void pop_dbg_m8(void) { }
|
||||||
|
|
||||||
|
/* ВРЕМЕННО (разбор pop_loose_tick 2026-08-13): m9..m12 — границы ВНУТРИ
|
||||||
|
* самого pop_loose_tick. Он банковый, но зовёт резидент W1 напрямую,
|
||||||
|
* так что брейкпоинты по этим адресам однозначны. */
|
||||||
|
void pop_dbg_m9(void) { }
|
||||||
|
void pop_dbg_m10(void) { }
|
||||||
|
void pop_dbg_m11(void) { }
|
||||||
|
void pop_dbg_m12(void) { }
|
||||||
|
|
||||||
|
/* ВРЕМЕННО (разбор pop_ceil_shake_draw 2026-08-13): heal / draw_tile. */
|
||||||
|
void pop_dbg_m13(void) { }
|
||||||
|
void pop_dbg_m14(void) { }
|
||||||
|
void pop_dbg_m15(void) { }
|
||||||
|
|
||||||
|
/* ВРЕМЕННО (разбор pop_redraw_needed по ВИДАМ 2026-08-13): вид приезжает в
|
||||||
|
* A (__sdcccall(1), uint8 arg1), брейкпоинт на первой инструкции логирует
|
||||||
|
* его вместе с тактами — так видно цену КАЖДОГО вида, а не число пометок. */
|
||||||
|
void pop_dbg_kind(uint8_t k) { (void)k; }
|
||||||
|
void pop_dbg_m16(void) { }
|
||||||
|
|||||||
@@ -80,3 +80,16 @@ extern int8_t pop_jumped_mirror;
|
|||||||
/* ВРЕМЕННО: маркеры фаз кадра (замер). */
|
/* ВРЕМЕННО: маркеры фаз кадра (замер). */
|
||||||
void pop_dbg_m1(void); void pop_dbg_m2(void);
|
void pop_dbg_m1(void); void pop_dbg_m2(void);
|
||||||
void pop_dbg_m3(void); void pop_dbg_m4(void);
|
void pop_dbg_m3(void); void pop_dbg_m4(void);
|
||||||
|
|
||||||
|
/* ВРЕМЕННО: состав перерисовок в самом тяжёлом кадре (замер). */
|
||||||
|
extern uint8_t pop_dbg_rdmax[10];
|
||||||
|
extern uint8_t pop_dbg_rdmax_tot;
|
||||||
|
|
||||||
|
/* ВРЕМЕННО: маркеры внутренних замеров. */
|
||||||
|
void pop_dbg_m5(void); void pop_dbg_m6(void);
|
||||||
|
void pop_dbg_m7(void); void pop_dbg_m8(void);
|
||||||
|
void pop_dbg_m9(void); void pop_dbg_m10(void);
|
||||||
|
void pop_dbg_m11(void); void pop_dbg_m12(void);
|
||||||
|
void pop_dbg_m13(void); void pop_dbg_m14(void);
|
||||||
|
void pop_dbg_m15(void);
|
||||||
|
void pop_dbg_kind(uint8_t k); void pop_dbg_m16(void);
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ int main(void)
|
|||||||
|
|
||||||
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
||||||
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
||||||
|
pop_dbg_m5(); /* ЗАМЕР: pop_loose_tick сделан */
|
||||||
pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */
|
pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */
|
||||||
/* process_trobs — рисования внутри нет) */
|
/* process_trobs — рисования внутри нет) */
|
||||||
if (pop_neigh_dirty) {
|
if (pop_neigh_dirty) {
|
||||||
@@ -478,7 +479,9 @@ int main(void)
|
|||||||
pop_level_access_end();
|
pop_level_access_end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */
|
pop_dbg_m6(); /* ЗАМЕР: pop_process_trobs сделан */
|
||||||
|
pop_redraw_needed();
|
||||||
|
pop_dbg_m7(); /* ЗАМЕР: pop_redraw_needed сделан */ /* и разобрать пометки перерисовки тайлов (порт */
|
||||||
/* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */
|
/* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */
|
||||||
{ /* ворота соседа слева (room8) видны в col0 — */
|
{ /* ворота соседа слева (room8) видны в col0 — */
|
||||||
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
||||||
|
|||||||
Reference in New Issue
Block a user