Оптимизация логики: окно коллизии как в оригинале + деление таблицей

Замерено брейкпоинтами в 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:
2026-08-09 14:38:08 +03:00
parent 6960e1cc76
commit 8c4bc4f621
3 changed files with 243 additions and 40 deletions
+62 -1
View File
@@ -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: как