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:
@@ -1769,18 +1769,67 @@ static void calc_coll_window(void)
|
|||||||
* Замер одной итерации в MAME: пустая колонка 750 тактов, колонка-стена
|
* Замер одной итерации в MAME: пустая колонка 750 тактов, колонка-стена
|
||||||
* ~1 700 (там две 16-битные знаковые сверки граней). */
|
* ~1 700 (там две 16-битные знаковые сверки граней). */
|
||||||
static uint8_t scan_n; /* сколько колонок осталось */
|
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 {
|
do {
|
||||||
uint8_t wt = wall_type_tbl[*src++ & 0x1F];
|
uint8_t wt = wall_type_tbl[*src++ & 0x1F];
|
||||||
uint8_t f = 0;
|
uint8_t f = 0;
|
||||||
if (wt) {
|
if (wt) {
|
||||||
if (wall_dl[wt] + scan_left < coll_xr) f = 1;
|
/* ВСЯ арифметика в 8 битах — см. доказательство диапазонов
|
||||||
if (scan_left - wall_dr[wt] + TILE_RIGHTX > coll_xl) f |= 2;
|
* над 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;
|
scan_left += TILE_SIZEX;
|
||||||
} while (--scan_n);
|
} while (--scan_n);
|
||||||
}
|
}
|
||||||
@@ -1789,14 +1838,18 @@ static void coll_scan(const uint8_t *src, uint8_t *dst)
|
|||||||
* в теле ряда, и пролог одного ряда стоил тысячи тактов при том, что сам
|
* в теле ряда, и пролог одного ряда стоил тысячи тактов при том, что сам
|
||||||
* цикл по четырём-пяти колонкам — около трёх. */
|
* цикл по четырём-пяти колонкам — около трёх. */
|
||||||
static uint8_t scan_n0;
|
static uint8_t scan_n0;
|
||||||
static int scan_left0;
|
static uint8_t scan_left0;
|
||||||
static uint8_t scan_off; /* индекс первого слота в flags[] */
|
static uint8_t scan_off; /* индекс первого слота в flags[] */
|
||||||
|
|
||||||
static void coll_scan_prepare(void)
|
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_n0 = (uint8_t)(win_hi - win_lo + 1);
|
||||||
scan_off = (uint8_t)COLL_IDX(win_lo);
|
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;
|
last = win_hi < -1 ? win_hi : -1;
|
||||||
n = (uint8_t)(last - lo + 1);
|
n = (uint8_t)(last - lo + 1);
|
||||||
scan_n = n; /* coll_scan обнулит */
|
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;
|
dst += n;
|
||||||
lo = (int8_t)(last + 1);
|
lo = (int8_t)(last + 1);
|
||||||
}
|
}
|
||||||
@@ -1846,13 +1899,13 @@ static void coll_row(uint8_t *flags)
|
|||||||
last = win_hi < 9 ? win_hi : 9;
|
last = win_hi < 9 ? win_hi : 9;
|
||||||
n = (uint8_t)(last - lo + 1);
|
n = (uint8_t)(last - lo + 1);
|
||||||
scan_n = n;
|
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;
|
dst += n;
|
||||||
lo = (int8_t)(last + 1);
|
lo = (int8_t)(last + 1);
|
||||||
}
|
}
|
||||||
if (lo <= win_hi) { /* комната СПРАВА */
|
if (lo <= win_hi) { /* комната СПРАВА */
|
||||||
scan_n = (uint8_t)(win_hi - lo + 1);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pop_dbg_p2(); /* ЗАМЕР: вход в тело */
|
||||||
set_char_collision();
|
set_char_collision();
|
||||||
|
pop_dbg_p3(); /* ЗАМЕР: set_char_collision */
|
||||||
move_coll_to_prev(Char.curr_row); /* заодно снимет окно prev */
|
move_coll_to_prev(Char.curr_row); /* заодно снимет окно prev */
|
||||||
coll_prev_row = Char.curr_row;
|
coll_prev_row = Char.curr_row;
|
||||||
calc_coll_window();
|
calc_coll_window();
|
||||||
coll_lo = win_lo; coll_hi = win_hi;
|
coll_lo = win_lo; coll_hi = win_hi;
|
||||||
coll_scan_prepare();
|
coll_scan_prepare();
|
||||||
|
pop_dbg_p4(); /* ЗАМЕР: окно посчитано */
|
||||||
/* Порядок важен: prev уже забран, теперь три ряда пересчитываются
|
/* Порядок важен: prev уже забран, теперь три ряда пересчитываются
|
||||||
* НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). Ряд здесь
|
* НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). Ряд здесь
|
||||||
* гарантированно 0..2, поэтому соседние ряды — это те же базы ±10, без
|
* гарантированно 0..2, поэтому соседние ряды — это те же базы ±10, без
|
||||||
@@ -1947,6 +2003,7 @@ static void check_collisions(void)
|
|||||||
}
|
}
|
||||||
coll_row(coll_above);
|
coll_row(coll_above);
|
||||||
}
|
}
|
||||||
|
pop_dbg_p5(); /* ЗАМЕР: три ряда просканированы */
|
||||||
/* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка,
|
/* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка,
|
||||||
* в которой флаг перешёл 0→1. Только по ПЕРЕСЕЧЕНИЮ окон: вне его
|
* в которой флаг перешёл 0→1. Только по ПЕРЕСЕЧЕНИЮ окон: вне его
|
||||||
* сравнивать нечего (у оригинала там prev_coll_room != curr_row_coll_room
|
* сравнивать нечего (у оригинала там prev_coll_room != curr_row_coll_room
|
||||||
@@ -3066,9 +3123,12 @@ static void kid_phys(void)
|
|||||||
determine_col();
|
determine_col();
|
||||||
bump_into_opponent(); /* seg003: безоружный Кид отскакивает от стража */
|
bump_into_opponent(); /* seg003: безоружный Кид отскакивает от стража */
|
||||||
check_collisions(); /* seg004: флаги перекрытия по колонкам ряда */
|
check_collisions(); /* seg004: флаги перекрытия по колонкам ряда */
|
||||||
|
pop_dbg_p6(); /* ЗАМЕР: check_collisions целиком */
|
||||||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||||||
check_action();
|
check_action();
|
||||||
|
pop_dbg_p7(); /* ЗАМЕР */
|
||||||
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
|
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
|
||||||
|
pop_dbg_p8(); /* ЗАМЕР */
|
||||||
check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */
|
check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */
|
||||||
check_spiked(); /* seg006: напоролся на вредные пики → смерть */
|
check_spiked(); /* seg006: напоролся на вредные пики → смерть */
|
||||||
check_chomped_kid(); /* seg004: перемололо в сомкнутых челюстях → смерть */
|
check_chomped_kid(); /* seg004: перемололо в сомкнутых челюстях → смерть */
|
||||||
@@ -3128,6 +3188,7 @@ void pop_phys_tick(void) __banked
|
|||||||
* оригинале: кадр смерти не двигается сам (dx/dy нулевые, sequence
|
* оригинале: кадр смерти не двигается сам (dx/dy нулевые, sequence
|
||||||
* кончился), а уход из комнаты и сотрясение отсечены внутри kid_phys. */
|
* кончился), а уход из комнаты и сотрясение отсечены внутри kid_phys. */
|
||||||
pop_loadkid_and_opp();
|
pop_loadkid_and_opp();
|
||||||
|
pop_dbg_p1(); /* ЗАМЕР: окно Char загружено */
|
||||||
kid_phys();
|
kid_phys();
|
||||||
pop_savekid_and_opp();
|
pop_savekid_and_opp();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,16 @@ void pop_dbg_b4(void) { } /* pop_cd_touch */
|
|||||||
void pop_dbg_b5(void) { } /* gfx_w0_unmap (конец) */
|
void pop_dbg_b5(void) { } /* gfx_w0_unmap (конец) */
|
||||||
void pop_dbg_b6(void) { } /* взведён = пошли в gfx_blit_noclip, а не в _part */
|
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 упакованы в один
|
/* ВРЕМЕННО (регрессия цены блита 2026-08-13): w и h упакованы в один
|
||||||
* 16-битный аргумент (arg1 -> HL при __sdcccall(1)), брейкпоинт логирует
|
* 16-битный аргумент (arg1 -> HL при __sdcccall(1)), брейкпоинт логирует
|
||||||
* HL — дальше цена раскладывается как a + b*h + c*w*h, где b и есть
|
* HL — дальше цена раскладывается как a + b*h + c*w*h, где b и есть
|
||||||
|
|||||||
@@ -105,6 +105,12 @@ void pop_dbg_m15(void);
|
|||||||
void pop_dbg_kind(uint8_t k); void pop_dbg_m16(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_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);
|
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);
|
void pop_dbg_wh(uint16_t wh);
|
||||||
|
|
||||||
/* ВРЕМЕННО: трасса kidobj (см. pop_state.c). Порядок байт:
|
/* ВРЕМЕННО: трасса kidobj (см. pop_state.c). Порядок байт:
|
||||||
|
|||||||
Reference in New Issue
Block a user