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). Порядок байт: