P2b: луч видимости стража 36 786 -> 13 002 (-65 %)
Замер отделил луч от pop_frame_timers: таймеры со всеми тремя
спецсобытиями уровней стоят 1 962, луч — 36 786, то есть 5,6 % работы
кадра на девять чтений байта.
Причина оказалась НЕ в алгоритме. Сверка трёх референсов:
SDLPoP (seg003:688) — идёт по x с шагом 14 и на каждом шаге переводит x
в колонку делением. Причём сам SDLPoP признаёт в комментарии, что
«DOS PoP does this: tile_div_tbl[xpos]» — то есть оригинал брал
таблицу, а порт заменил её на / и %, потому что на 32 битах так проще.
Apple II (MISC.S CHECKALERT) — тот же алгоритм байт в байт, но перевод
x -> блок через таблицу BlockTable[x]. Ровно то, что у нас уже было
сделано (POP_TILE_DIV, 2026-08-10).
mininim — другая архитектура (тайловые позиции, своя механика), для
сравнения реализации не годится.
То есть алгоритмически мы уже были на уровне Apple II, а платили за
другое: pop_tile_at объявлен __banked, луч живёт в guards.c (банк 1), и
на КАЖДУЮ колонку шёл трамплин банк 1 -> банк 3. На сцене 11/15 (Кид в
колонке 2, страж в 8) это девять трамплинов за кадр.
Сделано:
1. луч переведён на КОЛОНКИ вместо x-координат. Это эквивалентно:
начальные x — ровно центры тайлов персонажей, а обратный перевод даёт
ту же колонку (floor((58 + col*14 - 58)/14) == col). Ушли 16-битный
шаг, 16-битное сравнение и индексация таблицы на каждой итерации;
2. тайлы отрезка забираются ОДНИМ банковым вызовом (pop_row_tiles)
вместо девяти;
3. внутри pop_row_tiles — быстрый путь для отрезка целиком внутри
комнаты: get_tile при ряде 0..2 и колонке 0..9 сводится ровно к
g_fg[row*10+col] & 0x1F, идём указателем;
4. буфер тайлов — file-scope, а не локальный массив (иначе каждое
чтение это -n(ix)).
Замер по шагам: 36 786 -> 24 048 (колонки + один вызов) -> 13 002
(быстрый путь + буфер). Синяя фаза 283 215 -> 259 500, работа кадра
654 990 -> 628 542, то есть -26 448 при ожидании -30 000.
Кэш-гейт «пересчитывать только при смене позиции» НЕ понадобился:
расхождения с оригиналом нет, луч считается каждый кадр, как и должен.
Поведение проверено в MAME: страж в боевой стойке, но не идёт — между ним
и Кидом чомпер, то есть can_guard_see_kid = 1 («видит, но не пойдёт»).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,39 +56,16 @@ static uint8_t tile_is_floor(uint8_t t)
|
||||
}
|
||||
}
|
||||
|
||||
/* Тайл в ряду Кида по X-координате персонажа (порт get_tile_at_kid,
|
||||
* seg003:761): xpos − 7 делится на ширину тайла с округлением вниз. */
|
||||
static int8_t tile_col; /* колонка последнего tile_at_kid */
|
||||
/* get_tile_at_kid (seg003:761) СНЯТ вместе с переходом луча на колонки:
|
||||
* единственным его вызывающим был этот луч, а он больше не работает с
|
||||
* X-координатами (разбор — в теле pop_check_can_guard_see_kid ниже).
|
||||
* Таблица POP_TILE_DIV осталась в резиденте, ею пользуется физика. */
|
||||
|
||||
static uint8_t tile_at_kid(int16_t xpos)
|
||||
{
|
||||
/* get_tile_div_mod_m7 (seg006:697): колонка = floor((xpos − 7 − 58)/14),
|
||||
* СРАЗУ в координатах комнаты (58 = x_bump[FIRST_ONSCREEN_COLUMN]).
|
||||
* Оригинал берёт её из tile_div_tbl — и мы теперь тоже: резидентная
|
||||
* POP_TILE_DIV[] это ровно она (индекс = xpos, значение = floor((xpos−58)/14)),
|
||||
* поэтому здесь индексируем её на xpos−7.
|
||||
*
|
||||
* Было честное `/` и `%`: у SDCC z80 это __divsint плюс __modsint, а тот
|
||||
* внутри снова зовёт __divsint — около 5 400 тактов на вызов. А зовут
|
||||
* нас В ЦИКЛЕ по колонкам между стражем и Кидом
|
||||
* (check_can_guard_see_kid, seg003:761). Когда Кид у шва, его
|
||||
* curr_col = −1, луч тянется через всю комнату, и за кадр набегало ВОСЕМЬ
|
||||
* пар делений — ~43 000 тактов, 10 % растрового кадра, в фазе логики
|
||||
* (замер MAME 2026-08-10, уровень 1 комната 2).
|
||||
*
|
||||
* Медленный хвост остаётся для xpos за пределами таблицы (страж и Кид в
|
||||
* разных комнатах: логическая X уезжает на ±140). */
|
||||
int16_t x = xpos - 7;
|
||||
if ((uint16_t)x < 256u) {
|
||||
tile_col = POP_TILE_DIV[x];
|
||||
} else {
|
||||
int16_t v = x - SCREENSPACE_X;
|
||||
int16_t col = v / TILE_SIZEX;
|
||||
if (v % TILE_SIZEX < 0) --col;
|
||||
tile_col = (int8_t)col;
|
||||
}
|
||||
return pop_tile_at(tile_col, Kid.curr_row);
|
||||
}
|
||||
/* Буфер тайлов луча — file-scope, а не локальный массив: локальный уезжает
|
||||
* в стековый кадр, и каждое чтение из него стоит `-n(ix)` (19 тактов
|
||||
* номинала, ~46 с wait-state'ами). Тот же приём, что у draw_tile и
|
||||
* pop_blit_b. Размер — колонки −1..10 плюс запас. */
|
||||
static uint8_t row_t[12];
|
||||
|
||||
/* check_can_guard_see_kid (seg003:688) — луч видимости по ряду.
|
||||
* Результат в can_guard_see_kid: 0 не видит / 1 видит, но не пойдёт /
|
||||
@@ -96,7 +73,6 @@ static uint8_t tile_at_kid(int16_t xpos)
|
||||
void pop_check_can_guard_see_kid(void) __banked
|
||||
{
|
||||
uint8_t kid_frame = Kid.frame;
|
||||
int16_t left_pos, right_pos;
|
||||
uint8_t tile;
|
||||
|
||||
/* МЫШЬ Кида «не видит» (seg003:0965): иначе он выхватит меч на зверька,
|
||||
@@ -122,34 +98,53 @@ void pop_check_can_guard_see_kid(void) __banked
|
||||
}
|
||||
|
||||
can_guard_see_kid = 2;
|
||||
left_pos = pop_x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
right_pos = pop_x_bump[Guard.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
if (left_pos > right_pos) {
|
||||
int16_t t = left_pos; left_pos = right_pos; right_pos = t;
|
||||
}
|
||||
/* Чомпер стоит у ЛЕВОГО края тайла — свой тайл он не перекрывает. */
|
||||
if (tile_at_kid(left_pos) == TILE_CHOMPER)
|
||||
left_pos += TILE_SIZEX;
|
||||
/* Ворота — у ПРАВОГО края, поэтому крайний правый тайл не считается. */
|
||||
if (tile_at_kid(right_pos) == TILE_GATE)
|
||||
right_pos -= TILE_SIZEX;
|
||||
/* ПО КОЛОНКАМ, а не по X-координатам.
|
||||
*
|
||||
* Оригинал (и SDLPoP, seg003:688) идёт по x с шагом TILE_SIZEX и на
|
||||
* каждом шаге переводит x в колонку — потому что x у него уже под рукой.
|
||||
* Но начальные x — это РОВНО центры тайлов персонажей
|
||||
* (x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX), а перевод обратно
|
||||
* даёт ту же колонку: floor((58 + col*14 - 58) / 14) == col. Значит
|
||||
* весь проход эквивалентен обходу колонок, и ни одна x-координата тут не
|
||||
* нужна: уходят 16-битный шаг, 16-битное сравнение и индексация таблицы
|
||||
* POP_TILE_DIV на каждой итерации.
|
||||
*
|
||||
* Тайлы отрезка забираем ОДНИМ банковым вызовом. Раньше на каждую
|
||||
* колонку шёл свой pop_tile_at (__banked, банк 1 -> банк 3): на сцене
|
||||
* 11/15 это девять трамплинов и 36 786 тактов на весь луч (замер
|
||||
* 2026-08-19). */
|
||||
{
|
||||
int8_t lcol = Kid.curr_col, rcol = Guard.curr_col, col, lcol0;
|
||||
|
||||
while (left_pos <= right_pos) {
|
||||
tile = tile_at_kid(left_pos);
|
||||
/* Сквозь это не видно вовсе. */
|
||||
if (tile == TILE_WALL || tile == TILE_DOORTOP_FLOOR || tile == TILE_DOORTOP) {
|
||||
can_guard_see_kid = 0;
|
||||
return;
|
||||
if (lcol > rcol) { int8_t t = lcol; lcol = rcol; rcol = t; }
|
||||
lcol0 = lcol; /* БАЗА индексации буфера — фиксируем
|
||||
* ДО сдвигов ниже: row_t[0] это
|
||||
* тайл lcol0, и сдвиг lcol/rcol на
|
||||
* чомпере и воротах её менять не
|
||||
* должен. */
|
||||
pop_row_tiles(Kid.curr_row, lcol, rcol, row_t);
|
||||
|
||||
/* Чомпер стоит у ЛЕВОГО края тайла — свой тайл он не перекрывает. */
|
||||
if (row_t[0] == TILE_CHOMPER) lcol++;
|
||||
/* Ворота — у ПРАВОГО края, поэтому крайний правый тайл не считается. */
|
||||
if (row_t[rcol - lcol0] == TILE_GATE) rcol--;
|
||||
|
||||
for (col = lcol; col <= rcol; col++) {
|
||||
tile = row_t[col - lcol0];
|
||||
/* Сквозь это не видно вовсе. */
|
||||
if (tile == TILE_WALL || tile == TILE_DOORTOP_FLOOR || tile == TILE_DOORTOP) {
|
||||
can_guard_see_kid = 0;
|
||||
return;
|
||||
}
|
||||
/* Видно, но страж не пойдёт: провалится / попадёт под чомпер /
|
||||
* упрётся в неоткрытые ворота / шагнёт в дыру. */
|
||||
if (tile == TILE_LOOSE || tile == TILE_CHOMPER || !tile_is_floor(tile)) {
|
||||
can_guard_see_kid = 1;
|
||||
} else if (tile == TILE_GATE &&
|
||||
pop_gate_modif(col, Kid.curr_row) < 112) {
|
||||
can_guard_see_kid = 1; /* ворота подняты не до конца */
|
||||
}
|
||||
}
|
||||
/* Видно, но страж не пойдёт: провалится / попадёт под чомпер /
|
||||
* упрётся в неоткрытые ворота / шагнёт в дыру. */
|
||||
if (tile == TILE_LOOSE || tile == TILE_CHOMPER || !tile_is_floor(tile)) {
|
||||
can_guard_see_kid = 1;
|
||||
} else if (tile == TILE_GATE &&
|
||||
pop_gate_modif(tile_col, Kid.curr_row) < 112) {
|
||||
can_guard_see_kid = 1; /* ворота подняты не до конца */
|
||||
}
|
||||
left_pos += TILE_SIZEX;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,6 +330,38 @@ uint8_t pop_tile_at(int8_t col, int8_t row) __banked
|
||||
return get_tile(col, row);
|
||||
}
|
||||
|
||||
/* Тайлы ОТРЕЗКА ряда одним вызовом — для луча видимости стража.
|
||||
*
|
||||
* Зачем: pop_tile_at объявлен __banked, а луч живёт в guards.c (банк 1) и
|
||||
* звал его ПО ОДНОЙ КОЛОНКЕ. На сцене 11/15 (Кид в колонке 2, страж в 8)
|
||||
* это девять трамплинов банк 1 -> банк 3 за кадр, и весь луч стоил 36 786
|
||||
* тактов — 5,6 % работы кадра при том, что делает он девять чтений байта
|
||||
* (замер 2026-08-19). Цена трамплина здесь та же, что уже измерена у
|
||||
* pop_clip_char_top (8 892 такта ради одной проверки тайла).
|
||||
*
|
||||
* Колонки за пределами 0..9 разрешает сам get_tile (шов с соседней
|
||||
* комнатой), поэтому диапазон отдаём как есть, без клипа.
|
||||
*
|
||||
* out обязан вмещать c1 - c0 + 1 байт; вызывающий даёт буфер на 12
|
||||
* (колонки −1..10 плюс запас). */
|
||||
void pop_row_tiles(int8_t row, int8_t c0, int8_t c1, uint8_t *out) __banked
|
||||
{
|
||||
int8_t col;
|
||||
/* БЫСТРЫЙ ПУТЬ — отрезок целиком внутри комнаты. get_tile для ряда
|
||||
* 0..2 и колонки 0..9 сводится ровно к `g_fg[row*10+col] & 0x1F`
|
||||
* (см. его тело выше), а всё остальное там — разбор швов и краёв
|
||||
* уровня. Идём указателем: иначе на каждую колонку заново считается
|
||||
* row*10 + col. */
|
||||
if (row >= 0 && row <= 2 && c0 >= 0 && c1 <= 9 && g_fg) {
|
||||
const uint8_t *p = g_fg + (int)row * 10 + c0;
|
||||
for (col = c0; col <= c1; col++)
|
||||
*out++ = (uint8_t)(*p++ & 0x1F);
|
||||
return;
|
||||
}
|
||||
for (col = c0; col <= c1; col++)
|
||||
*out++ = get_tile(col, row);
|
||||
}
|
||||
|
||||
/* can_bump_into_gate (seg004:373): ОТКРЫТЫЕ ворота проходимы — Kid не бампит и
|
||||
* идёт сквозь них (на шве — уходит в соседнюю комнату; внутри комнаты — просто
|
||||
* проходит по полу под поднятыми барами). ВАЖНО: ворота остаются тайлом 4 (в
|
||||
|
||||
@@ -58,6 +58,10 @@ void pop_map_set_room(uint8_t room) __banked;
|
||||
* (guards.c): он идёт по ряду между Кидом и стражем. */
|
||||
uint8_t pop_tile_at(int8_t col, int8_t row) __banked;
|
||||
|
||||
/* Тайлы отрезка ряда c0..c1 одним банковым вызовом (см. тело в pop_map.c):
|
||||
* луч видимости стража иначе платит по трамплину за КАЖДУЮ колонку. */
|
||||
void pop_row_tiles(int8_t row, int8_t c0, int8_t c1, uint8_t *out) __banked;
|
||||
|
||||
/* determine_col (seg006:014D): Kid.curr_col = m7(dx_weight()). Наружу — для
|
||||
* pop_load_fram_det_col (pop_kid.c), порт load_fram_det_col. */
|
||||
void pop_determine_col(void) __banked;
|
||||
|
||||
@@ -367,6 +367,7 @@ int main(void)
|
||||
pop_check_killed_shadow(); /* спецсобытие ур.12: смерть тени = смерть Кида */
|
||||
pop_dbg_b6(); /* ЗАМЕР: три спецсобытия уровней сделаны */
|
||||
pop_frame_timers(); /* timers (seg003:0735): вспышка слияния */
|
||||
pop_dbg_m9(); /* ЗАМЕР: pop_frame_timers сделан */
|
||||
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
||||
pop_dbg_m14(); /* ЗАМЕР: спецсобытия + луч видимости */
|
||||
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
||||
|
||||
Reference in New Issue
Block a user