Луч видимости стража: колонка из таблицы вместо деления (Кид у шва)

tile_at_kid (guards.c) считала колонку честным / и %, хотя резидентная
POP_TILE_DIV — это и есть tile_div_tbl оригинала, и остальной порт давно на
неё переведён.  У SDCC z80 пара / и % над int это __divsint плюс __modsint,
который внутри снова зовёт __divsint — ~5 400 тактов на вызов.

Зовут её в ЦИКЛЕ по колонкам между стражем и Кидом
(check_can_guard_see_kid, seg003:761).  Когда Кид у шва, его curr_col = −1,
луч тянется через всю комнату: замер дал ВОСЕМЬ пар делений за кадр,
около 43 000 тактов = 10 % растрового кадра, в фазе логики.  После фикса
таких вызовов не остаётся.

Как ловилось: брейкпоинт на __divsint с печатью адреса возврата дал
ret=C033 восемь раз за кадр; остановка на нём с dasm при замапленном банке
показала HL−65 / ld de,#14 / call __divsint по смещению 0x24 банка 1.

Снята и ложная тревога из прошлого коммита: пролог pop_char_fore на шве НЕ
разбухает до 134 730 — это была ошибка зонда (адрес fore_tile в банке
совпадает с кодом других банков, в интервал попадали чужие срабатывания).
Чистый замер: пролог 16 950, как и в середине комнаты.  Урок записан в
«Как мерить» в docs/perf_backlog.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 18:41:53 +03:00
parent 08ac38ce3d
commit ec3cca5e1e
2 changed files with 62 additions and 7 deletions
+38 -1
View File
@@ -24,7 +24,16 @@ MAME (`z80_profiling_method`) на роомтесте, уровень 1 комн
только по ОДНОЙ функции с одинаковыми входами, а не по общей работе за
кадр.
- Кто делит: брейкпоинт на `__divsint`/`__divuint`/`__divuchar` с печатью
адреса возврата — `bpset <addr>,1,{printf "ret=%04X\n",w@(sp); g}`.
адреса возврата — `bpset <addr>,1,{printf "ret=%04X\n",w@(sp); g}`. Если
адрес возврата в банке (>= 0xC000), поставить тот же брейкпоинт с условием
`w@(sp)==<адрес>` и БЕЗ `g`: машина встанет с нужным банком в окне, и
`dasm` покажет вызывающего.
- **Брейкпоинт по адресу в банке ловит ВСЕ банки.** 0xC000..0xFFFF — общее
окно, и один и тот же адрес есть у семи модулей сразу. Либо ловить через
трамплин (`hl==<адрес>&&(de&0xff)==<банк>`), либо перепроверять, что
срабатывания идут из нужной фазы: иначе в интервал попадает чужой код и
цифры врут (так я намерил несуществующие 134 730 тактов в прологе
`pop_char_fore`).
- Трасса вызовов графики с параметрами: скрипт в истории сессии, ставит
маркеры фаз на трамплин `___sdcc_bcall_ehl` (условие `hl==<адрес>&&(de&0xff)==<банк>`)
и брейкпоинты на листья libbgi с печатью аргументов
@@ -97,6 +106,34 @@ MAME (`z80_profiling_method`) на роомтесте, уровень 1 комн
---
## СДЕЛАНО 2026-08-10: деление в луче видимости стража (Кид у шва)
`tile_at_kid` (`guards.c`) считала колонку честным `/` и `%`, тогда как везде
уже стоит резидентная таблица `POP_TILE_DIV` (это и есть `tile_div_tbl`
оригинала). У SDCC z80 это `__divsint` плюс `__modsint`, а тот внутри снова
зовёт `__divsint` — ~5 400 тактов на вызов.
Зовут её В ЦИКЛЕ по колонкам между стражем и Кидом
(`check_can_guard_see_kid`, seg003:761). Когда Кид стоит У ШВА, его
`curr_col = 1`, луч тянется через всю комнату, и за кадр набегало ВОСЕМЬ пар
делений — около 43 000 тактов, 10 % растрового кадра, в фазе ЛОГИКИ.
Замер: брейкпоинт на `__divsint` с печатью адреса возврата дал `ret=C033`
восемь раз за кадр; остановка на нём и дизассемблирование с правильным банком
показали `HL65`, `ld de,#14`, `call __divsint` по смещению 0x24 банка 1 —
`tile_at_kid`. После фикса пар `C033` не остаётся ни одной.
**Заодно снята ложная тревога.** В прошлом замере я записал, что на шве
пролог `pop_char_fore` разбухает до 134 730 тактов. Это была ошибка зонда:
брейкпоинт на `fore_tile` стоял по адресу, который совпадает с кодом ДРУГИХ
банков, и в интервал попадали чужие срабатывания. Чистый замер (Кид в
колонке 0, спрайт свисает за левый край, окно `x 8..5`): пролог **16 950**
ровно как в середине комнаты, весь fore-проход 41 803, `pop_room_clip_borders`
14 364. Урок в «Как мерить» выше: адрес в банке нужно либо проверять на
уникальность, либо ловить через трамплин с условием на банк.
---
## 0. Дворцовая кладка в fore-проходе — **СДЕЛАНО 2026-08-10**
Все три шага выполнены; замер после — в конце пункта. Ниже сохранён исходный
+24 -6
View File
@@ -23,6 +23,7 @@
#include "pop_trob.h"
#include "pop_level.h" /* pop_current_level — спецсобытие уровня 3 */
#include "pop_state.h" /* pop_immortal, pop_droppedout */
#include "pop_tile.h" /* POP_TILE_DIV — таблица колонок (tile_div_tbl) */
#define TILE_MIDX 7 /* середина тайла в координатах персонажа */
#define SCREENSPACE_X 58 /* = pop_x_bump[FIRST_ONSCREEN_COLUMN] */
@@ -61,12 +62,29 @@ 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_map. */
int16_t x = xpos - 7 - SCREENSPACE_X;
int16_t col = x / TILE_SIZEX;
if (x % TILE_SIZEX < 0) --col;
tile_col = (int8_t)col;
* Оригинал берёт её из tile_div_tbl — и мы теперь тоже: резидентная
* POP_TILE_DIV[] это ровно она (индекс = xpos, значение = floor((xpos58)/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);
}