P2a: coll_scan переведён на 8 бит и снят с IX — минус 3 486 в коллизиях

Разбор 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 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 12:06:43 +03:00
parent de68eb5cec
commit 2bdaf0f4cd
3 changed files with 87 additions and 10 deletions
+71 -10
View File
@@ -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();
}
+10
View File
@@ -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 и есть
+6
View File
@@ -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). Порядок байт: