Оптимизация логики: окно коллизии как в оригинале + деление таблицей
Замерено брейкпоинтами в MAME (уровень 1 комната 1, Кид стоит у факела). Калибровка, без которой цифры не сходятся: такт totalcycles != номинальный T-такт Z80, wait-state'ы ОЗУ Sprinter дают ~2,4x (get_tile 574 против 1422). 1. Окно перебора коллизии — как у оригинала (left_checked_col..right_checked_col, seg004:0047), было: все 14 колонок каждый кадр. Признак годности слота у нас дешевле оригинального: не массив номеров комнат с очисткой, а границы окна, которые move_coll_to_prev переносит в prev вместе с флагами; бамп считается по пересечению двух окон. check_chomped_flags тоже ограничен окном, иначе протухшие слоты дают фантомный перемол. 2. get_tile_div_mod — таблицами tile_div_tbl/tile_mod_tbl (seg006:702), было /14 и %14. SDCC разворачивал это в __divsint + __modsint, а __modsint внутри зовёт __divsint ещё раз: 5 400 тактов на вызов, 13 вызовов за кадр = 16 % кадрового периода на «в какой колонке точка». 3. get_row_collision_data: ряд разрешается один раз на весь перебор (было — get_tile на каждую колонку, 1 422 такта), грань идёт шагом TILE_SIZEX как в оригинале, wall_type таблицей вместо switch. Итог: check_collisions 60 888 -> 42 750, физика Кида 100 578 -> 67 518, синяя полоса ~60 % -> ~30 % кадрового периода. Профиль остатка и следующие цели (отрисовка Кида 47 %, process_trobs 21 %) — в TASKS_OPEN.md#draw-cost. tests-host: все 5 наборов прошли. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -79,16 +79,25 @@ for (short column = 9; column >= 0; --column) {
|
||||
Не взяли по одной причине: **десяти слотов нам не хватит**. Оригинал
|
||||
перебирает узкое окно вокруг Кида (от `col(char_x_left_coll) − 1` до
|
||||
`col(char_x_right_coll) + 2`), поэтому коллизии слотов у него практически
|
||||
не случаются. Мы намеренно считаем все четырнадцать колонок сразу
|
||||
(FIX_COLL_FLAGS из SDLPoP — чтобы не оставалось протухших ячеек), а при
|
||||
таком переборе колонки −2/−1 и 8/9 сядут в одни и те же слоты 8/9. То
|
||||
есть вариант A тянет за собой ещё и сужение окна перебора — то есть отказ
|
||||
от FIX_COLL_FLAGS, — и это заметно рискованнее точечного сдвига.
|
||||
не случаются. Мы держим четырнадцать колонок (−2…11) — при узком окне это
|
||||
не мешает, а вот в десять слотов колонки −2/−1 и 8/9 сядут поверх 8/9.
|
||||
|
||||
**Окно перебора с 2026-08-09 у нас такое же, как в оригинале** (было: все
|
||||
четырнадцать колонок каждый кадр). Признак годности слота при этом не
|
||||
массив номеров комнат, как у оригинала, а ГРАНИЦЫ окна — четыре байта,
|
||||
которые `move_coll_to_prev` переносит в `prev` вместе с флагами; сравнение
|
||||
идёт по пересечению двух окон. Очистки массивов нет вовсе, то есть это
|
||||
дешевле оригинала, а смысл тот же (у него слот вне окна помечен
|
||||
`row_coll_room = −1` и в цикл бампа не попадает). `check_chomped_flags`
|
||||
тоже ограничен окном — иначе протухшие слоты дали бы фантомный перемол.
|
||||
|
||||
### Чем платим
|
||||
|
||||
- Расхождение структур: если в будущем понадобится знать, из какой комнаты
|
||||
пришёл тайл конкретного слота, этого у нас нет — придётся идти в вариант A.
|
||||
- Границы окна надо переносить везде, где переносятся флаги: `pop_coll_shift`
|
||||
двигает и их, `move_coll_to_prev` снимает их в `prev`. Забыть один из
|
||||
переносов = молча потерять или, наоборот, разрешить лишний бамп.
|
||||
- Сдвиг работает только для чисто горизонтальных переходов на ровно 10
|
||||
колонок. Любая будущая диагональ/иная ширина комнаты его сломает молча.
|
||||
- `coll_last_row`: `pop_coll_invalidate` прячет прошлый ряд, чтобы
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
|
||||
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
|
||||
| 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
|
||||
| — | [DRAW-COST](#draw-cost) | шаг 1 сделан (пропуск неизменившегося персонажа): в покое 210 % -> **116 %**. ШАГ 2 — цена ОДНОЙ перерисовки: бегущий Кид даёт циан почти 100 % | плавность на ВСЕХ уровнях |
|
||||
| — | [DRAW-COST](#draw-cost) | шаг 1 (пропуск персонажа) и логика сделаны: синяя полоса 60 % -> 30 %. ОСТАЛОСЬ: отрисовка одного Кида = 47 % кадра, `process_trobs` = 21 % | плавность на ВСЕХ уровнях |
|
||||
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
||||
|
||||
@@ -358,6 +358,67 @@ W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)).
|
||||
понадобится — держать не один прямоугольник, а 3-4 отдельных и мержить
|
||||
только при переполнении.
|
||||
|
||||
### Шаг 2 сделан частично: ЛОГИКА (синяя полоса) 2026-08-09
|
||||
|
||||
Пользователь: «на стоящем Киде с двумя факелами на логику уходит 60 %
|
||||
кадрового периода — недопустимо». Разобрано брейкпоинтами в MAME
|
||||
(`z80_profiling_method`), сцена: уровень 1 комната 1, Кид СТОИТ вплотную к
|
||||
левому факелу (то есть пропуск персонажа НЕ срабатывает — худший случай).
|
||||
|
||||
**Калибровка, которую надо знать заранее.** Один такт `totalcycles` в MAME
|
||||
— НЕ один номинальный T-такт Z80: у Sprinter на обращениях к ОЗУ есть
|
||||
wait-state'ы, и замеренная стоимость выходит **≈ 2,4× номинала**
|
||||
(`get_tile`: 574 номинальных против 1 422 замеренных). Считать бюджет по
|
||||
таблице T-тактов из справочника нельзя — только мерить. Кадр растра =
|
||||
430 000; главный цикл спейсится тремя `gfx_wait_vsync`, поэтому работа
|
||||
СВЫШЕ 430 000 стоит сразу целый лишний кадр.
|
||||
|
||||
**Что нашли и починили:**
|
||||
|
||||
| правка | что было | стало |
|
||||
|---|---|---|
|
||||
| окно перебора коллизии как в оригинале (было: все 14 колонок каждый кадр) | `check_collisions` 60 888 | 50 940 |
|
||||
| `get_tile_div_mod` — таблицей (`tile_div_tbl`/`tile_mod_tbl`), было `/14` и `%14` | 5 400 тактов на вызов, 13 вызовов за кадр ≈ 70 000 = **16 % кадра** | ~250 на вызов |
|
||||
| разрешение ряда вынесено из цикла колонок + грань шагом 14 + `wall_type` таблицей | `check_collisions` 58 026 | **42 750** |
|
||||
|
||||
Самое дорогое было НЕ там, где ожидалось: `/14` и `%14` SDCC разворачивает
|
||||
в `__divsint` + `__modsint`, а `__modsint` внутри зовёт `__divsint` ещё раз —
|
||||
два полноценных 16-битных деления на каждый вопрос «в какой колонке точка».
|
||||
Оригинал делит таблицей (seg006:702) — мы просто не портировали это место.
|
||||
|
||||
**Профиль работы за один логический кадр ПОСЛЕ правок (470 964 такта):**
|
||||
|
||||
| блок | тактов | % растрового кадра |
|
||||
|---|---|---|
|
||||
| see_kid + ctrl_tick + heal | 46 920 | 11 |
|
||||
| kid_tick (play_seq) | 6 468 | 1,5 |
|
||||
| физика Кида (`pop_phys_tick`) | 67 518 | 16 |
|
||||
| страж + боёвка | 10 350 | 2,4 |
|
||||
| `pop_loose_tick` | 27 438 | 6,4 |
|
||||
| **`pop_process_trobs`** | **92 346** | **21,5** |
|
||||
| `pop_redraw_needed` + шов | 6 474 | 1,5 |
|
||||
| skip + отрисовка соперника (пусто) | 11 178 | 2,6 |
|
||||
| **`pop_char_draw` + `pop_char_fore` Кида** | **201 336** | **47** |
|
||||
| борта | 732 | 0,2 |
|
||||
|
||||
Синяя полоса (ввод+heal+логика) была ~60 % → стала ~30 %. Внутри физики
|
||||
`check_collisions` — по-прежнему больше половины (42 750 из 67 518), и в ней
|
||||
15 разрешений тайла на кадр: три ряда × окно 4–5 колонок, как в оригинале.
|
||||
|
||||
**Что осталось — по убыванию (это и есть остаток шага 2):**
|
||||
|
||||
1. **Отрисовка одного Кида 201 336 тактов = 47 % кадра.** Ровно тот «циан»,
|
||||
который пользователь назвал следующей целью. План — ниже.
|
||||
2. **`pop_process_trobs` 92 346 на ДВА факела** (46 000 на факел). У
|
||||
оригинала `process_trobs` только двигает состояние, рисование —
|
||||
в `redraw_needed`; у нас `redraw_needed` стоит 6 474, значит блиты факелов
|
||||
сидят внутри process_trobs. Разобрать, где именно.
|
||||
3. **`pop_loose_tick` 27 438** при полном отсутствии падающих плит в комнате —
|
||||
похоже, безусловный проход по всем 30 тайлам.
|
||||
4. Работа за цикл 470 964 против 430 000 бюджета: **не хватает ~41 000**,
|
||||
чтобы уложиться в три растровых кадра вместо четырёх. Любая из правок
|
||||
выше даёт игре сразу +25 % скорости — граница проходит рядом.
|
||||
|
||||
### Шаг 2 (СЛЕДУЮЩИЙ, назначен пользователем): ДВИЖУЩИЙСЯ Кид
|
||||
|
||||
Пропуск закрывает только покой. Наблюдение пользователя 2026-08-08: как
|
||||
|
||||
@@ -367,16 +367,16 @@ static uint8_t get_tile_behind_above_char(void)
|
||||
(int8_t)(Char.curr_row - 1));
|
||||
}
|
||||
|
||||
static uint8_t wall_type(uint8_t t)
|
||||
{
|
||||
switch (t) {
|
||||
case 4: case 7: case 12: return 1; /* стена справа (gate/doortop) */
|
||||
case 13: return 2; /* стена слева (mirror) */
|
||||
case 18: return 3; /* chomper */
|
||||
case 20: return 4; /* стена с обеих сторон */
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
/* Таблицей, а не switch: тайл уже маскирован &0x1F, значит 32 байта покрывают
|
||||
* весь домен, и вместо шести cp+jr выходит одно чтение по индексу. Зовётся
|
||||
* на каждую колонку перебора коллизии — там это заметно. */
|
||||
static const uint8_t wall_type_tbl[32] = {
|
||||
0,0,0,0,1,0,0,1, /* 4,7 — стена справа (gate/doortop) */
|
||||
0,0,0,0,1,2,0,0, /* 12 — то же; 13 — стена слева (mirror) */
|
||||
0,0,3,0,4,0,0,0, /* 18 — chomper; 20 — стена с обеих сторон */
|
||||
0,0,0,0,0,0,0,0
|
||||
};
|
||||
static uint8_t wall_type(uint8_t t) { return wall_type_tbl[t & 0x1F]; }
|
||||
|
||||
/* ---- Координаты ---------------------------------------------------- */
|
||||
static int char_dx_forward(int8_t dx)
|
||||
@@ -386,15 +386,72 @@ static int char_dx_forward(int8_t dx)
|
||||
return (int)Char.x + d;
|
||||
}
|
||||
|
||||
/* tile_div_tbl (data:22A6). Оригинал делит xpos на 14 ТАБЛИЦЕЙ, а не
|
||||
* арифметикой — и это оказалось не мелочью: `/14` и `%` 14 SDCC разворачивает
|
||||
* в __divsint + __modsint, причём __modsint внутри зовёт __divsint ещё раз.
|
||||
* Замер в MAME (стоящий Кид, комната 1.1): один вызов get_tile_div_mod —
|
||||
* ~5 400 тактов, 13 вызовов за кадр = ~70 000 = 16 % кадрового периода
|
||||
* на одно только «в какой колонке точка».
|
||||
*
|
||||
* Таблица сверена с SDLPoP seg006:702 побайтно. Индекс — xpos как есть,
|
||||
* смещение -58 уже внутри значений. Вне 0..255 (у нас xpos - int, у
|
||||
* DOS-оригинала байт) остаётся честная арифметика: путь редкий. */
|
||||
static const int8_t tile_div_tbl[256] = {
|
||||
-5, -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4,
|
||||
-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8,
|
||||
8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
|
||||
10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
|
||||
11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14,
|
||||
};
|
||||
|
||||
/* tile_mod_tbl (data:23A6) — остаток от того же деления. Сначала я считал
|
||||
* его из xh тремя сдвигами, чтобы сэкономить 256 байт; SDCC развернул это в
|
||||
* десяток команд со спиллами через IY. Вторая таблица и дешевле, и это
|
||||
* ровно то, что делает оригинал. */
|
||||
static const uint8_t tile_mod_tbl[256] = {
|
||||
12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1,
|
||||
2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3,
|
||||
4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5,
|
||||
6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,
|
||||
12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1,
|
||||
2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3,
|
||||
4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5,
|
||||
6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,
|
||||
12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1,
|
||||
};
|
||||
|
||||
/* col = (xpos-58)/14 округл. вниз; g_obj_xl = позиция в тайле (0..13). */
|
||||
static int8_t get_tile_div_mod(int xpos)
|
||||
{
|
||||
int x = xpos - SCREENSPACE_X;
|
||||
int xh = x / TILE_SIZEX;
|
||||
int xl = x % TILE_SIZEX;
|
||||
if (xl < 0) { --xh; xl += TILE_SIZEX; } /* округление вниз */
|
||||
g_obj_xl = (uint8_t)xl;
|
||||
return (int8_t)xh;
|
||||
if ((unsigned int)xpos < 256u) {
|
||||
g_obj_xl = tile_mod_tbl[xpos];
|
||||
return tile_div_tbl[xpos];
|
||||
} else {
|
||||
int x = xpos - SCREENSPACE_X;
|
||||
int xh = x / TILE_SIZEX;
|
||||
int xl = x % TILE_SIZEX;
|
||||
if (xl < 0) { --xh; xl += TILE_SIZEX; } /* округление вниз */
|
||||
g_obj_xl = (uint8_t)xl;
|
||||
return (int8_t)xh;
|
||||
}
|
||||
}
|
||||
|
||||
static int8_t get_tile_div_mod_m7(int xpos) { return get_tile_div_mod(xpos - 7); }
|
||||
@@ -1368,6 +1425,24 @@ static void bumped_floor(void)
|
||||
static uint8_t coll_prev[COLL_N], coll_curr[COLL_N];
|
||||
static uint8_t coll_above[COLL_N], coll_below[COLL_N];
|
||||
static int8_t coll_prev_row = COLL_ROW_NONE; /* ряд, к которому относится coll_prev */
|
||||
/* ОКНО перебора: какие слоты реально посчитаны. Оригинал (seg004:0047) тоже
|
||||
* считает не всю комнату, а полосу вокруг персонажа — left_checked_col /
|
||||
* right_checked_col; прочие слоты у него помечены «комнаты нет»
|
||||
* (row_coll_room_ptr = −1) и в цикл бампа не попадают.
|
||||
*
|
||||
* Раньше мы считали все четырнадцать колонок, чтобы не заводить признак
|
||||
* годности слота. Замер по сгенерированному асму показал, чего это стоит:
|
||||
* ~940 тактов на колонку (из них 574 — get_tile), 3 ряда × 14 = ~39 600 за
|
||||
* кадр у Кида плюс ~13 200 у стража (check_chomped_guard) — вместе ~12 %
|
||||
* кадрового периода. Окно из 4–5 колонок режет это втрое.
|
||||
*
|
||||
* Признак годности у нас дешевле оригинального: не массив номеров комнат,
|
||||
* который надо чистить каждый кадр, а ГРАНИЦЫ окна — четыре байта. Слот
|
||||
* годен, если лежит в окне; сравнивать prev с curr можно только на
|
||||
* ПЕРЕСЕЧЕНИИ двух окон. Очистки массивов нет вовсе. */
|
||||
static int8_t coll_lo, coll_hi; /* окно coll_curr/above/below */
|
||||
static int8_t coll_prev_lo, coll_prev_hi; /* окно coll_prev */
|
||||
static int8_t win_lo, win_hi; /* результат calc_coll_window */
|
||||
/* «нет колонки»: НЕ −1, потому что −1/−2 — законные колонки шва. */
|
||||
#define BUMP_NONE ((int8_t)-128)
|
||||
static int8_t bump_col_l; /* bump_col_left_of_wall (Kid лицом ВПРАВО) */
|
||||
@@ -1434,6 +1509,10 @@ void pop_coll_shift(int8_t dcol) __banked
|
||||
coll_shift_one(coll_curr, dcol);
|
||||
coll_shift_one(coll_above, dcol);
|
||||
coll_shift_one(coll_below, dcol);
|
||||
/* окно посчитанных слотов едет вместе с ними; выйти за [COLL_C0..] оно
|
||||
* может свободно — пересечение с окном следующего кадра само обрежет. */
|
||||
coll_lo = (int8_t)(coll_lo + dcol);
|
||||
coll_hi = (int8_t)(coll_hi + dcol);
|
||||
}
|
||||
|
||||
/* set_char_collision (seg006:0723), горизонтальная часть: габарит персонажа
|
||||
@@ -1448,11 +1527,24 @@ static void set_char_collision(void)
|
||||
if (kid_cur_flags() & FRAME_THIN) { coll_xl += 4; coll_xr -= 4; }
|
||||
}
|
||||
|
||||
/* Окно перебора (seg004:0047). Считать ПОСЛЕ set_char_collision — берётся из
|
||||
* coll_xl/coll_xr. Клампы: справа как в оригинале (не дальше последней
|
||||
* колонки), слева — по нашему COLL_C0 (оригиналу клампить нечего, у него
|
||||
* индекс приходит уже разрешённым внутрь комнаты). */
|
||||
static void calc_coll_window(void)
|
||||
{
|
||||
int8_t hi = (int8_t)(get_tile_div_mod_m7(coll_xr) + 2);
|
||||
int8_t lo = (int8_t)(get_tile_div_mod_m7(coll_xl) - 1);
|
||||
if (hi > COLL_C0 + COLL_N - 1) hi = COLL_C0 + COLL_N - 1;
|
||||
if (lo < COLL_C0) lo = COLL_C0;
|
||||
if (hi < lo) hi = lo;
|
||||
win_lo = lo; win_hi = hi;
|
||||
}
|
||||
|
||||
/* get_row_collision_data (seg004:0185) для одного ряда: бит0 = «персонаж
|
||||
* достал левую грань стены колонки», бит1 = «правую». Оригинал считает
|
||||
* колонки от col(char_x_left)−1 до col(char_x_right)+2 и оставляет прочие
|
||||
* как есть; мы считаем все четырнадцать — они дешёвые (индекс в массив +
|
||||
* switch), зато не остаётся протухших ячеек (FIX_COLL_FLAGS SDLPoP). */
|
||||
* достал левую грань стены колонки», бит1 = «правую». Считаем ОКНО
|
||||
* win_lo..win_hi (см. calc_coll_window), слоты вне окна не трогаем — их
|
||||
* годность определяют границы окна, а не содержимое. */
|
||||
/* move_coll_to_prev (seg004:00DF): в prev кладём флаги ряда, в котором
|
||||
* персонаж был НА ПРОШЛОМ кадре. Массивы curr/above/below в этот момент
|
||||
* ещё хранят прошлый кадр, поэтому нужный ряд просто выбирается из них по
|
||||
@@ -1466,8 +1558,12 @@ static void move_coll_to_prev(int8_t row)
|
||||
{
|
||||
const uint8_t *src;
|
||||
int8_t i;
|
||||
/* Окно prev — то, по которому считались curr/above/below (у всех трёх оно
|
||||
* одно). Забираем ДО того, как check_collisions пересчитает своё. */
|
||||
coll_prev_lo = coll_lo; coll_prev_hi = coll_hi;
|
||||
if (coll_prev_row == COLL_ROW_NONE) {
|
||||
for (i = 0; i < COLL_N; i++) coll_prev[i] = 3;
|
||||
coll_prev_lo = COLL_C0; coll_prev_hi = COLL_C0 + COLL_N - 1;
|
||||
return;
|
||||
}
|
||||
if (row == coll_prev_row || row + 3 == coll_prev_row || row - 3 == coll_prev_row)
|
||||
@@ -1482,15 +1578,39 @@ static void move_coll_to_prev(int8_t row)
|
||||
static void get_row_collision_data(int8_t row, uint8_t *flags)
|
||||
{
|
||||
int8_t col;
|
||||
for (col = COLL_C0; col < COLL_C0 + COLL_N; col++) {
|
||||
uint8_t f = 0;
|
||||
uint8_t wt = wall_type(get_tile(col, row));
|
||||
/* Ряд разрешается ОДИН раз на весь перебор, а не в каждом get_tile.
|
||||
* get_tile — общий вход: он каждый вызов заново решает «ряд −1? за
|
||||
* комнатой? слева/справа?» и считает row*10+col сдвигами; замер в MAME —
|
||||
* 1 422 такта на вызов, то есть больше половины стоимости колонки.
|
||||
* Здесь ряд фиксирован, значит хватает трёх баз. Побочки g_curr_tile
|
||||
* (curr_tile2) тут никто не читает — она нужна только тем, кто зовёт
|
||||
* get_tile ради одного тайла. */
|
||||
const uint8_t *own = 0, *lft = 0, *rgt = 0;
|
||||
int coll_left;
|
||||
if (row >= 0 && row <= 2) {
|
||||
own = g_fg + row * 10;
|
||||
if (g_link_l) lft = g_lcol + row * 10 + 10; /* индексируется col < 0 */
|
||||
if (g_link_r) rgt = g_rcol + row * 10 - 10; /* индексируется col > 9 */
|
||||
} else if (row == -1 && g_above && g_link_u) {
|
||||
own = g_above; /* ряд 2 комнаты сверху; за её края — стена */
|
||||
}
|
||||
/* Грань колонки идёт шагом ровно в тайл (pop_x_bump линейна, шаг 14) —
|
||||
* оригинал так и делает: coll_tile_left_xpos += TILE_SIZEX (seg004:0185).
|
||||
* Окно всегда внутри −2..11, поэтому индекс таблицы законный, а own/lft/
|
||||
* rgt не выходят за свои 30-байтовые раскладки. */
|
||||
coll_left = pop_x_bump[win_lo + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
for (col = win_lo; col <= win_hi; col++) {
|
||||
uint8_t f = 0, t, wt;
|
||||
if (col < 0) t = lft ? (uint8_t)(lft[col] & 0x1F) : TILE_WALL;
|
||||
else if (col > 9) t = rgt ? (uint8_t)(rgt[col] & 0x1F) : TILE_WALL;
|
||||
else t = own ? (uint8_t)(own[col] & 0x1F) : TILE_WALL;
|
||||
wt = wall_type(t);
|
||||
if (wt) {
|
||||
int coll_left = pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
if (wall_dl[wt] + coll_left < coll_xr) f |= 1;
|
||||
if (coll_left - wall_dr[wt] + TILE_RIGHTX > coll_xl) f |= 2;
|
||||
}
|
||||
flags[COLL_IDX(col)] = f;
|
||||
coll_left += TILE_SIZEX;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1511,19 +1631,27 @@ static void check_collisions(void)
|
||||
}
|
||||
|
||||
set_char_collision();
|
||||
move_coll_to_prev(Char.curr_row);
|
||||
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;
|
||||
/* Порядок важен: prev уже забран, теперь три ряда пересчитываются
|
||||
* НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). */
|
||||
get_row_collision_data(Char.curr_row, coll_curr);
|
||||
get_row_collision_data((int8_t)(Char.curr_row + 1), coll_below);
|
||||
get_row_collision_data((int8_t)(Char.curr_row - 1), coll_above);
|
||||
/* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка,
|
||||
* в которой флаг перешёл 0→1. */
|
||||
for (col = COLL_C0 + COLL_N - 1; col >= COLL_C0; --col) {
|
||||
uint8_t p = coll_prev[COLL_IDX(col)], c = coll_curr[COLL_IDX(col)];
|
||||
if (!(p & 1) && (c & 1)) bump_col_l = col;
|
||||
if (!(p & 2) && (c & 2)) bump_col_r = col;
|
||||
* в которой флаг перешёл 0→1. Только по ПЕРЕСЕЧЕНИЮ окон: вне его
|
||||
* сравнивать нечего (у оригинала там prev_coll_room != curr_row_coll_room
|
||||
* или curr_row_coll_room < 0 — тот же смысл). */
|
||||
{
|
||||
int8_t lo = coll_lo > coll_prev_lo ? coll_lo : coll_prev_lo;
|
||||
int8_t hi = coll_hi < coll_prev_hi ? coll_hi : coll_prev_hi;
|
||||
for (col = hi; col >= lo; --col) {
|
||||
uint8_t p = coll_prev[COLL_IDX(col)], c = coll_curr[COLL_IDX(col)];
|
||||
if (!(p & 1) && (c & 1)) bump_col_l = col;
|
||||
if (!(p & 2) && (c & 2)) bump_col_r = col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2202,12 +2330,16 @@ static void chomped(uint8_t tilepos, int8_t col)
|
||||
* перекрывает тайл ОБЕИМИ гранями (у оригинала curr_row_coll_flags == 0xFF,
|
||||
* у нас оба бита) и этот тайл — СОМКНУТЫЙ чомпер (modif & 0x7F == 2),
|
||||
* персонажа перемалывает. */
|
||||
static void check_chomped_flags(const uint8_t *flags)
|
||||
/* lo..hi — ОКНО, по которому посчитан flags: вне него слоты протухшие (там
|
||||
* лежат значения прошлых кадров), и «== 3» дал бы фантомный перемол. */
|
||||
static void check_chomped_flags(const uint8_t *flags, int8_t lo, int8_t hi)
|
||||
{
|
||||
int8_t row = Char.curr_row, col;
|
||||
uint8_t tp;
|
||||
if (row < 0 || row > 2) return;
|
||||
for (col = 0; col <= 9; col++) {
|
||||
if (lo < 0) lo = 0;
|
||||
if (hi > 9) hi = 9;
|
||||
for (col = lo; col <= hi; col++) {
|
||||
if (flags[COLL_IDX(col)] != 3) continue;
|
||||
if (get_tile(col, row) != TILE_CHOMPER) continue;
|
||||
tp = (uint8_t)(row * 10 + col);
|
||||
@@ -2215,7 +2347,7 @@ static void check_chomped_flags(const uint8_t *flags)
|
||||
}
|
||||
}
|
||||
|
||||
static void check_chomped_kid(void) { check_chomped_flags(coll_curr); }
|
||||
static void check_chomped_kid(void) { check_chomped_flags(coll_curr, coll_lo, coll_hi); }
|
||||
|
||||
/* check_chomped_guard (seg004:0989) — у оригинала ОТДЕЛЬНОЕ тело: страж не
|
||||
* проходит через check_collisions, поэтому там перекрытие считается прямо по
|
||||
@@ -2229,8 +2361,9 @@ static void check_chomped_guard(void)
|
||||
uint8_t flags[COLL_N];
|
||||
if (Char.curr_row < 0 || Char.curr_row > 2) return;
|
||||
set_char_collision();
|
||||
calc_coll_window(); /* своё окно; coll_lo/hi Кида не трогаем */
|
||||
get_row_collision_data(Char.curr_row, flags);
|
||||
check_chomped_flags(flags);
|
||||
check_chomped_flags(flags, win_lo, win_hi);
|
||||
}
|
||||
|
||||
/* ---- Кадровая физика: одна и та же над Char, два входа --------------- *
|
||||
|
||||
Reference in New Issue
Block a user