diff --git a/applications/PoP/docs/impl_diff.md b/applications/PoP/docs/impl_diff.md index 71bbd97..b7ac7f1 100644 --- a/applications/PoP/docs/impl_diff.md +++ b/applications/PoP/docs/impl_diff.md @@ -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` прячет прошлый ряд, чтобы diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index ccdf6fb..3249120 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -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: как diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 76fc3e4..04b9f3e 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -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, два входа --------------- *