diff --git a/applications/PoP/roomtest/guards.c b/applications/PoP/roomtest/guards.c index b2cd844..d3a7143 100644 --- a/applications/PoP/roomtest/guards.c +++ b/applications/PoP/roomtest/guards.c @@ -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; } } diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index ebe95d7..bcd1d71 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -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 (в diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index 6269868..3b03845 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -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; diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index aead726..d97db23 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -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 */