From 2bdaf0f4cd844f373c7c3940b375288aed98c23e Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 19 Aug 2026 12:06:43 +0300 Subject: [PATCH] =?UTF-8?q?P2a:=20coll=5Fscan=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D1=91=D0=BD=20=D0=BD=D0=B0=208=20=D0=B1?= =?UTF-8?q?=D0=B8=D1=82=20=D0=B8=20=D1=81=D0=BD=D1=8F=D1=82=20=D1=81=20IX?= =?UTF-8?q?=20=E2=80=94=20=D0=BC=D0=B8=D0=BD=D1=83=D1=81=203=20486=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B8=D0=B7=D0=B8=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Разбор pop_phys_tick (61 266 тактов на НЕПОДВИЖНОМ Киде) зондами по звеньям kid_phys: check_collisions 33 846 55 % хвост (spike/spiked/chomped/knock/leave/save) 16 188 26 % check_press 4 140 check_action 2 622 loadkid_and_opp 2 148 determine_col 1 182 fall_accel+fall_speed 582 bump_into_opponent 198 Внутри check_collisions: три coll_row (сканирование рядов) — 23 256, подготовка окна 3 240, set_char_collision 1 788, обход пересечения 5 562. Сгенерированный asm coll_scan показал 322 такта Z80 на ПУСТУЮ колонку (с wait-state'ами 773 — ровно замеренные 750), из них 137 (43 %) — обращения через IX-фрейм, и четыре 16-битные операции на колонку там, где от колонки зависит один операнд. Сделано: 1. вся арифметика цикла в 8 битах. scan_left = x_bump[col+5] + TILE_MIDX при колонках окна -2..11 лежит в [37, 233], wall_dl в [-1, 10], wall_dr в [0, 13] — суммы в [36, 246], переполниться не могут. Границы персонажа приводятся к 8 битам с клипом, и клип точен: порог ниже 37 означает «условие не выполнится никогда», выше 233 — «всегда». 2. dst снят с IX-фрейма в file-scope (scan_dst). ОТРИЦАТЕЛЬНЫЙ РЕЗУЛЬТАТ, не повторять: предпосчёт таблиц порогов по типу стены (thr_l[6]/thr_r[6] на кадр) сделал ХУЖЕ — check_collisions 33 846 -> 36 570, синяя фаза +10 269. Колонок в окне четыре-пять, а типов стен пять: кэша получилось больше, чем потребления. Проверено на кодогенерации: register на параметре-указателе SDCC 4.5 z80 проигнорировал (asm байт в байт), а file-scope дал 607 -> 454 такта. Итог: check_collisions 33 846 -> 30 360 (-10 %), работа кадра 657 882 -> 654 990. Крупной статьи в физике нет: остаток размазан по десятку честных проверок. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/pop_map.c | 81 +++++++++++++++++++++++---- applications/PoP/roomtest/pop_state.c | 10 ++++ applications/PoP/roomtest/pop_state.h | 6 ++ 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 7ab3454..ebe95d7 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -1769,18 +1769,67 @@ static void calc_coll_window(void) * Замер одной итерации в MAME: пустая колонка 750 тактов, колонка-стена * ~1 700 (там две 16-битные знаковые сверки граней). */ static uint8_t scan_n; /* сколько колонок осталось */ -static int scan_left; /* левая грань текущей колонки */ +static uint8_t scan_left; /* левая грань текущей колонки */ -static void coll_scan(const uint8_t *src, uint8_t *dst) +/* ПОРОГИ СРАВНЕНИЯ, предпосчитанные на кадр (по типу стены 1..5). + * + * В теле цикла стояло `wall_dl[wt] + scan_left < coll_xr` и + * `scan_left - wall_dr[wt] + TILE_RIGHTX > coll_xl` — четыре 16-битных + * операции на КАЖДУЮ колонку, при том что от колонки зависит ровно один + * операнд (scan_left). Переносим всё остальное в порог: + * + * scan_left < coll_xr - wall_dl[wt] = thr_l[wt] + * scan_left > coll_xl + wall_dr[wt] - TILE_RIGHTX = thr_r[wt] + * + * Оригинал считает это в лоб (get_left_wall_xpos / get_right_wall_xpos, + * seg004:0226) — он писался под 386, где 16-битная арифметика бесплатна. + * + * ПОЧЕМУ 8 БИТ КОРРЕКТНЫ (доказательство относится и к границам, и к + * слагаемым в самом цикле). scan_left = x_bump[col + 5] + TILE_MIDX, а + * колонка окна лежит в COLL_C0 .. COLL_C0+COLL_N-1, то есть −2..11: + * значит scan_left ∈ [x_bump[3]+7, x_bump[16]+7] = [37, 219], и после + * последней колонки максимум 233. Ни одного выхода за uint8_t. + * Сами пороги считаются в int и КЛИПУЮТСЯ к 0..255 — это не приближение, + * а точное сохранение результата: при пороге ниже 37 условие «меньше» не + * выполнится никогда (клип к 0 даёт то же), при пороге выше 219 оно + * выполнится всегда (клип к 255 даёт то же), и симметрично для «больше». */ +static uint8_t coll_xr8, coll_xl8; + +static uint8_t clip_thr(int v) +{ + if (v < 0) return 0; + if (v > 255) return 255; + return (uint8_t)v; +} + +/* Две границы персонажа, приведённые к 8 битам. ТАБЛИЦЫ ПОРОГОВ ПО ТИПУ + * СТЕНЫ ЗДЕСЬ БЫЛА И ОКАЗАЛАСЬ ХУЖЕ: предпосчёт пяти пар порогов стоил + * дороже, чем экономил, потому что колонок в окне всего четыре-пять, а + * типов стен пять (замер 2026-08-19: check_collisions 33 846 -> 36 570, + * синяя фаза +10 269). Классическая ошибка — кэшировать больше, чем + * потребляешь. */ +static void coll_thresholds(void) +{ + coll_xr8 = clip_thr(coll_xr); + coll_xl8 = clip_thr(coll_xl); +} + +static uint8_t *scan_dst; + +static void coll_scan(const uint8_t *src) { do { uint8_t wt = wall_type_tbl[*src++ & 0x1F]; uint8_t f = 0; if (wt) { - if (wall_dl[wt] + scan_left < coll_xr) f = 1; - if (scan_left - wall_dr[wt] + TILE_RIGHTX > coll_xl) f |= 2; + /* ВСЯ арифметика в 8 битах — см. доказательство диапазонов + * над coll_thresholds: scan_left ∈ [37, 233], wall_dl ∈ [−1, 10], + * wall_dr ∈ [0, 13], значит обе суммы лежат в [36, 246] и + * переполниться не могут. */ + if ((uint8_t)(scan_left + wall_dl[wt]) < coll_xr8) f = 1; + if ((uint8_t)(scan_left - wall_dr[wt] + TILE_RIGHTX) > coll_xl8) f |= 2; } - *dst++ = f; + *scan_dst++ = f; scan_left += TILE_SIZEX; } while (--scan_n); } @@ -1789,14 +1838,18 @@ static void coll_scan(const uint8_t *src, uint8_t *dst) * в теле ряда, и пролог одного ряда стоил тысячи тактов при том, что сам * цикл по четырём-пяти колонкам — около трёх. */ static uint8_t scan_n0; -static int scan_left0; +static uint8_t scan_left0; static uint8_t scan_off; /* индекс первого слота в flags[] */ static void coll_scan_prepare(void) { - scan_left0 = pop_x_bump[win_lo + FIRST_ONSCREEN_COLUMN] + TILE_MIDX; + scan_left0 = (uint8_t)(pop_x_bump[win_lo + FIRST_ONSCREEN_COLUMN] + TILE_MIDX); scan_n0 = (uint8_t)(win_hi - win_lo + 1); scan_off = (uint8_t)COLL_IDX(win_lo); + coll_thresholds(); /* здесь же, чтобы порог и окно всегда были от + * ОДНОГО персонажа — та же причина, по которой + * тут считается scan_left0 (см. BUG-GUARD-IX-1 + * в шапке get_row_collision_data). */ } /* Базы ряда: указатели, которые индексируются НОМЕРОМ КОЛОНКИ (в том числе @@ -1838,7 +1891,7 @@ static void coll_row(uint8_t *flags) last = win_hi < -1 ? win_hi : -1; n = (uint8_t)(last - lo + 1); scan_n = n; /* coll_scan обнулит */ - coll_scan(rb_lft ? rb_lft + lo : wall_row, dst); + scan_dst = dst; coll_scan(rb_lft ? rb_lft + lo : wall_row); dst += n; lo = (int8_t)(last + 1); } @@ -1846,13 +1899,13 @@ static void coll_row(uint8_t *flags) last = win_hi < 9 ? win_hi : 9; n = (uint8_t)(last - lo + 1); scan_n = n; - coll_scan(rb_own ? rb_own + lo : wall_row, dst); + scan_dst = dst; coll_scan(rb_own ? rb_own + lo : wall_row); dst += n; lo = (int8_t)(last + 1); } if (lo <= win_hi) { /* комната СПРАВА */ scan_n = (uint8_t)(win_hi - lo + 1); - coll_scan(rb_rgt ? rb_rgt + lo : wall_row, dst); + scan_dst = dst; coll_scan(rb_rgt ? rb_rgt + lo : wall_row); } } @@ -1908,12 +1961,15 @@ static void check_collisions(void) return; } + pop_dbg_p2(); /* ЗАМЕР: вход в тело */ set_char_collision(); + pop_dbg_p3(); /* ЗАМЕР: set_char_collision */ move_coll_to_prev(Char.curr_row); /* заодно снимет окно prev */ coll_prev_row = Char.curr_row; calc_coll_window(); coll_lo = win_lo; coll_hi = win_hi; coll_scan_prepare(); + pop_dbg_p4(); /* ЗАМЕР: окно посчитано */ /* Порядок важен: prev уже забран, теперь три ряда пересчитываются * НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). Ряд здесь * гарантированно 0..2, поэтому соседние ряды — это те же базы ±10, без @@ -1947,6 +2003,7 @@ static void check_collisions(void) } coll_row(coll_above); } + pop_dbg_p5(); /* ЗАМЕР: три ряда просканированы */ /* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка, * в которой флаг перешёл 0→1. Только по ПЕРЕСЕЧЕНИЮ окон: вне его * сравнивать нечего (у оригинала там prev_coll_room != curr_row_coll_room @@ -3066,9 +3123,12 @@ static void kid_phys(void) determine_col(); bump_into_opponent(); /* seg003: безоружный Кид отскакивает от стража */ check_collisions(); /* seg004: флаги перекрытия по колонкам ряда */ + pop_dbg_p6(); /* ЗАМЕР: check_collisions целиком */ check_bumped(); /* удержать у стены до check_action (порядок PoP) */ check_action(); + pop_dbg_p7(); /* ЗАМЕР */ check_press(); /* seg006: стойка/пробой loose → make_loose_fall */ + pop_dbg_p8(); /* ЗАМЕР */ check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */ check_spiked(); /* seg006: напоролся на вредные пики → смерть */ check_chomped_kid(); /* seg004: перемололо в сомкнутых челюстях → смерть */ @@ -3128,6 +3188,7 @@ void pop_phys_tick(void) __banked * оригинале: кадр смерти не двигается сам (dx/dy нулевые, sequence * кончился), а уход из комнаты и сотрясение отсечены внутри kid_phys. */ pop_loadkid_and_opp(); + pop_dbg_p1(); /* ЗАМЕР: окно Char загружено */ kid_phys(); pop_savekid_and_opp(); } diff --git a/applications/PoP/roomtest/pop_state.c b/applications/PoP/roomtest/pop_state.c index e108528..4304f34 100644 --- a/applications/PoP/roomtest/pop_state.c +++ b/applications/PoP/roomtest/pop_state.c @@ -180,6 +180,16 @@ void pop_dbg_b4(void) { } /* pop_cd_touch */ void pop_dbg_b5(void) { } /* gfx_w0_unmap (конец) */ void pop_dbg_b6(void) { } /* взведён = пошли в gfx_blit_noclip, а не в _part */ +/* ВРЕМЕННО (разбор pop_phys_tick 2026-08-19): звенья цепочки kid_phys. */ +void pop_dbg_p1(void) { } /* loadkid_and_opp сделан */ +void pop_dbg_p2(void) { } /* fall_accel + fall_speed */ +void pop_dbg_p3(void) { } /* determine_col */ +void pop_dbg_p4(void) { } /* bump_into_opponent */ +void pop_dbg_p5(void) { } /* check_collisions */ +void pop_dbg_p6(void) { } /* check_bumped */ +void pop_dbg_p7(void) { } /* check_action */ +void pop_dbg_p8(void) { } /* check_press */ + /* ВРЕМЕННО (регрессия цены блита 2026-08-13): w и h упакованы в один * 16-битный аргумент (arg1 -> HL при __sdcccall(1)), брейкпоинт логирует * HL — дальше цена раскладывается как a + b*h + c*w*h, где b и есть diff --git a/applications/PoP/roomtest/pop_state.h b/applications/PoP/roomtest/pop_state.h index 482b1ca..704b0f4 100644 --- a/applications/PoP/roomtest/pop_state.h +++ b/applications/PoP/roomtest/pop_state.h @@ -105,6 +105,12 @@ void pop_dbg_m15(void); void pop_dbg_kind(uint8_t k); void pop_dbg_m16(void); void pop_dbg_b1(void); void pop_dbg_b2(void); void pop_dbg_b3(void); void pop_dbg_b4(void); void pop_dbg_b5(void); void pop_dbg_b6(void); +/* ВРЕМЕННО (разбор pop_phys_tick 2026-08-19, позиция P2a): физика Кида — + * 61 266 тактов на НЕПОДВИЖНОМ персонаже. По одному зонду на звено + * цепочки kid_phys. */ +void pop_dbg_p1(void); void pop_dbg_p2(void); void pop_dbg_p3(void); +void pop_dbg_p4(void); void pop_dbg_p5(void); void pop_dbg_p6(void); +void pop_dbg_p7(void); void pop_dbg_p8(void); void pop_dbg_wh(uint16_t wh); /* ВРЕМЕННО: трасса kidobj (см. pop_state.c). Порядок байт: