Оптимизация логики: окно коллизии как в оригинале + деление таблицей
Замерено брейкпоинтами в 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_left_coll) − 1` до
|
||||||
`col(char_x_right_coll) + 2`), поэтому коллизии слотов у него практически
|
`col(char_x_right_coll) + 2`), поэтому коллизии слотов у него практически
|
||||||
не случаются. Мы намеренно считаем все четырнадцать колонок сразу
|
не случаются. Мы держим четырнадцать колонок (−2…11) — при узком окне это
|
||||||
(FIX_COLL_FLAGS из SDLPoP — чтобы не оставалось протухших ячеек), а при
|
не мешает, а вот в десять слотов колонки −2/−1 и 8/9 сядут поверх 8/9.
|
||||||
таком переборе колонки −2/−1 и 8/9 сядут в одни и те же слоты 8/9. То
|
|
||||||
есть вариант A тянет за собой ещё и сужение окна перебора — то есть отказ
|
**Окно перебора с 2026-08-09 у нас такое же, как в оригинале** (было: все
|
||||||
от FIX_COLL_FLAGS, — и это заметно рискованнее точечного сдвига.
|
четырнадцать колонок каждый кадр). Признак годности слота при этом не
|
||||||
|
массив номеров комнат, как у оригинала, а ГРАНИЦЫ окна — четыре байта,
|
||||||
|
которые `move_coll_to_prev` переносит в `prev` вместе с флагами; сравнение
|
||||||
|
идёт по пересечению двух окон. Очистки массивов нет вовсе, то есть это
|
||||||
|
дешевле оригинала, а смысл тот же (у него слот вне окна помечен
|
||||||
|
`row_coll_room = −1` и в цикл бампа не попадает). `check_chomped_flags`
|
||||||
|
тоже ограничен окном — иначе протухшие слоты дали бы фантомный перемол.
|
||||||
|
|
||||||
### Чем платим
|
### Чем платим
|
||||||
|
|
||||||
- Расхождение структур: если в будущем понадобится знать, из какой комнаты
|
- Расхождение структур: если в будущем понадобится знать, из какой комнаты
|
||||||
пришёл тайл конкретного слота, этого у нас нет — придётся идти в вариант A.
|
пришёл тайл конкретного слота, этого у нас нет — придётся идти в вариант A.
|
||||||
|
- Границы окна надо переносить везде, где переносятся флаги: `pop_coll_shift`
|
||||||
|
двигает и их, `move_coll_to_prev` снимает их в `prev`. Забыть один из
|
||||||
|
переносов = молча потерять или, наоборот, разрешить лишний бамп.
|
||||||
- Сдвиг работает только для чисто горизонтальных переходов на ровно 10
|
- Сдвиг работает только для чисто горизонтальных переходов на ровно 10
|
||||||
колонок. Любая будущая диагональ/иная ширина комнаты его сломает молча.
|
колонок. Любая будущая диагональ/иная ширина комнаты его сломает молча.
|
||||||
- `coll_last_row`: `pop_coll_invalidate` прячет прошлый ряд, чтобы
|
- `coll_last_row`: `pop_coll_invalidate` прячет прошлый ряд, чтобы
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
|
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
|
||||||
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
|
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
|
||||||
| 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
|
| 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 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||||
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
| — | [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 отдельных и мержить
|
понадобится — держать не один прямоугольник, а 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 (СЛЕДУЮЩИЙ, назначен пользователем): ДВИЖУЩИЙСЯ Кид
|
### Шаг 2 (СЛЕДУЮЩИЙ, назначен пользователем): ДВИЖУЩИЙСЯ Кид
|
||||||
|
|
||||||
Пропуск закрывает только покой. Наблюдение пользователя 2026-08-08: как
|
Пропуск закрывает только покой. Наблюдение пользователя 2026-08-08: как
|
||||||
|
|||||||
@@ -367,16 +367,16 @@ static uint8_t get_tile_behind_above_char(void)
|
|||||||
(int8_t)(Char.curr_row - 1));
|
(int8_t)(Char.curr_row - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t wall_type(uint8_t t)
|
/* Таблицей, а не switch: тайл уже маскирован &0x1F, значит 32 байта покрывают
|
||||||
{
|
* весь домен, и вместо шести cp+jr выходит одно чтение по индексу. Зовётся
|
||||||
switch (t) {
|
* на каждую колонку перебора коллизии — там это заметно. */
|
||||||
case 4: case 7: case 12: return 1; /* стена справа (gate/doortop) */
|
static const uint8_t wall_type_tbl[32] = {
|
||||||
case 13: return 2; /* стена слева (mirror) */
|
0,0,0,0,1,0,0,1, /* 4,7 — стена справа (gate/doortop) */
|
||||||
case 18: return 3; /* chomper */
|
0,0,0,0,1,2,0,0, /* 12 — то же; 13 — стена слева (mirror) */
|
||||||
case 20: return 4; /* стена с обеих сторон */
|
0,0,3,0,4,0,0,0, /* 18 — chomper; 20 — стена с обеих сторон */
|
||||||
default: return 0;
|
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)
|
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;
|
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). */
|
/* col = (xpos-58)/14 округл. вниз; g_obj_xl = позиция в тайле (0..13). */
|
||||||
static int8_t get_tile_div_mod(int xpos)
|
static int8_t get_tile_div_mod(int xpos)
|
||||||
{
|
{
|
||||||
|
if ((unsigned int)xpos < 256u) {
|
||||||
|
g_obj_xl = tile_mod_tbl[xpos];
|
||||||
|
return tile_div_tbl[xpos];
|
||||||
|
} else {
|
||||||
int x = xpos - SCREENSPACE_X;
|
int x = xpos - SCREENSPACE_X;
|
||||||
int xh = x / TILE_SIZEX;
|
int xh = x / TILE_SIZEX;
|
||||||
int xl = x % TILE_SIZEX;
|
int xl = x % TILE_SIZEX;
|
||||||
if (xl < 0) { --xh; xl += TILE_SIZEX; } /* округление вниз */
|
if (xl < 0) { --xh; xl += TILE_SIZEX; } /* округление вниз */
|
||||||
g_obj_xl = (uint8_t)xl;
|
g_obj_xl = (uint8_t)xl;
|
||||||
return (int8_t)xh;
|
return (int8_t)xh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int8_t get_tile_div_mod_m7(int xpos) { return get_tile_div_mod(xpos - 7); }
|
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_prev[COLL_N], coll_curr[COLL_N];
|
||||||
static uint8_t coll_above[COLL_N], coll_below[COLL_N];
|
static uint8_t coll_above[COLL_N], coll_below[COLL_N];
|
||||||
static int8_t coll_prev_row = COLL_ROW_NONE; /* ряд, к которому относится coll_prev */
|
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 — законные колонки шва. */
|
/* «нет колонки»: НЕ −1, потому что −1/−2 — законные колонки шва. */
|
||||||
#define BUMP_NONE ((int8_t)-128)
|
#define BUMP_NONE ((int8_t)-128)
|
||||||
static int8_t bump_col_l; /* bump_col_left_of_wall (Kid лицом ВПРАВО) */
|
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_curr, dcol);
|
||||||
coll_shift_one(coll_above, dcol);
|
coll_shift_one(coll_above, dcol);
|
||||||
coll_shift_one(coll_below, 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), горизонтальная часть: габарит персонажа
|
/* 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; }
|
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 = «персонаж
|
/* get_row_collision_data (seg004:0185) для одного ряда: бит0 = «персонаж
|
||||||
* достал левую грань стены колонки», бит1 = «правую». Оригинал считает
|
* достал левую грань стены колонки», бит1 = «правую». Считаем ОКНО
|
||||||
* колонки от col(char_x_left)−1 до col(char_x_right)+2 и оставляет прочие
|
* win_lo..win_hi (см. calc_coll_window), слоты вне окна не трогаем — их
|
||||||
* как есть; мы считаем все четырнадцать — они дешёвые (индекс в массив +
|
* годность определяют границы окна, а не содержимое. */
|
||||||
* switch), зато не остаётся протухших ячеек (FIX_COLL_FLAGS SDLPoP). */
|
|
||||||
/* move_coll_to_prev (seg004:00DF): в prev кладём флаги ряда, в котором
|
/* move_coll_to_prev (seg004:00DF): в prev кладём флаги ряда, в котором
|
||||||
* персонаж был НА ПРОШЛОМ кадре. Массивы curr/above/below в этот момент
|
* персонаж был НА ПРОШЛОМ кадре. Массивы curr/above/below в этот момент
|
||||||
* ещё хранят прошлый кадр, поэтому нужный ряд просто выбирается из них по
|
* ещё хранят прошлый кадр, поэтому нужный ряд просто выбирается из них по
|
||||||
@@ -1466,8 +1558,12 @@ static void move_coll_to_prev(int8_t row)
|
|||||||
{
|
{
|
||||||
const uint8_t *src;
|
const uint8_t *src;
|
||||||
int8_t i;
|
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) {
|
if (coll_prev_row == COLL_ROW_NONE) {
|
||||||
for (i = 0; i < COLL_N; i++) coll_prev[i] = 3;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (row == coll_prev_row || row + 3 == coll_prev_row || row - 3 == coll_prev_row)
|
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)
|
static void get_row_collision_data(int8_t row, uint8_t *flags)
|
||||||
{
|
{
|
||||||
int8_t col;
|
int8_t col;
|
||||||
for (col = COLL_C0; col < COLL_C0 + COLL_N; col++) {
|
/* Ряд разрешается ОДИН раз на весь перебор, а не в каждом get_tile.
|
||||||
uint8_t f = 0;
|
* get_tile — общий вход: он каждый вызов заново решает «ряд −1? за
|
||||||
uint8_t wt = wall_type(get_tile(col, row));
|
* комнатой? слева/справа?» и считает 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) {
|
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 (wall_dl[wt] + coll_left < coll_xr) f |= 1;
|
||||||
if (coll_left - wall_dr[wt] + TILE_RIGHTX > coll_xl) f |= 2;
|
if (coll_left - wall_dr[wt] + TILE_RIGHTX > coll_xl) f |= 2;
|
||||||
}
|
}
|
||||||
flags[COLL_IDX(col)] = f;
|
flags[COLL_IDX(col)] = f;
|
||||||
|
coll_left += TILE_SIZEX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1511,20 +1631,28 @@ static void check_collisions(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_char_collision();
|
set_char_collision();
|
||||||
move_coll_to_prev(Char.curr_row);
|
move_coll_to_prev(Char.curr_row); /* заодно снимет окно prev */
|
||||||
coll_prev_row = Char.curr_row;
|
coll_prev_row = Char.curr_row;
|
||||||
|
calc_coll_window();
|
||||||
|
coll_lo = win_lo; coll_hi = win_hi;
|
||||||
/* Порядок важен: prev уже забран, теперь три ряда пересчитываются
|
/* Порядок важен: prev уже забран, теперь три ряда пересчитываются
|
||||||
* НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). */
|
* НА ЭТОТ кадр (в оригинале ровно так же, seg004:0004). */
|
||||||
get_row_collision_data(Char.curr_row, coll_curr);
|
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_below);
|
||||||
get_row_collision_data((int8_t)(Char.curr_row - 1), coll_above);
|
get_row_collision_data((int8_t)(Char.curr_row - 1), coll_above);
|
||||||
/* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка,
|
/* Обход СВЕРХУ ВНИЗ, как в оригинале (9→0): побеждает МЛАДШАЯ колонка,
|
||||||
* в которой флаг перешёл 0→1. */
|
* в которой флаг перешёл 0→1. Только по ПЕРЕСЕЧЕНИЮ окон: вне его
|
||||||
for (col = COLL_C0 + COLL_N - 1; col >= COLL_C0; --col) {
|
* сравнивать нечего (у оригинала там 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)];
|
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 & 1) && (c & 1)) bump_col_l = col;
|
||||||
if (!(p & 2) && (c & 2)) bump_col_r = col;
|
if (!(p & 2) && (c & 2)) bump_col_r = col;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* is_obstacle (seg004:037E): препятствие ли тайл колонки на самом деле.
|
/* is_obstacle (seg004:037E): препятствие ли тайл колонки на самом деле.
|
||||||
@@ -2202,12 +2330,16 @@ static void chomped(uint8_t tilepos, int8_t col)
|
|||||||
* перекрывает тайл ОБЕИМИ гранями (у оригинала curr_row_coll_flags == 0xFF,
|
* перекрывает тайл ОБЕИМИ гранями (у оригинала curr_row_coll_flags == 0xFF,
|
||||||
* у нас оба бита) и этот тайл — СОМКНУТЫЙ чомпер (modif & 0x7F == 2),
|
* у нас оба бита) и этот тайл — СОМКНУТЫЙ чомпер (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;
|
int8_t row = Char.curr_row, col;
|
||||||
uint8_t tp;
|
uint8_t tp;
|
||||||
if (row < 0 || row > 2) return;
|
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 (flags[COLL_IDX(col)] != 3) continue;
|
||||||
if (get_tile(col, row) != TILE_CHOMPER) continue;
|
if (get_tile(col, row) != TILE_CHOMPER) continue;
|
||||||
tp = (uint8_t)(row * 10 + col);
|
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_chomped_guard (seg004:0989) — у оригинала ОТДЕЛЬНОЕ тело: страж не
|
||||||
* проходит через check_collisions, поэтому там перекрытие считается прямо по
|
* проходит через check_collisions, поэтому там перекрытие считается прямо по
|
||||||
@@ -2229,8 +2361,9 @@ static void check_chomped_guard(void)
|
|||||||
uint8_t flags[COLL_N];
|
uint8_t flags[COLL_N];
|
||||||
if (Char.curr_row < 0 || Char.curr_row > 2) return;
|
if (Char.curr_row < 0 || Char.curr_row > 2) return;
|
||||||
set_char_collision();
|
set_char_collision();
|
||||||
|
calc_coll_window(); /* своё окно; coll_lo/hi Кида не трогаем */
|
||||||
get_row_collision_data(Char.curr_row, flags);
|
get_row_collision_data(Char.curr_row, flags);
|
||||||
check_chomped_flags(flags);
|
check_chomped_flags(flags, win_lo, win_hi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Кадровая физика: одна и та же над Char, два входа --------------- *
|
/* ---- Кадровая физика: одна и та же над Char, два входа --------------- *
|
||||||
|
|||||||
Reference in New Issue
Block a user