diff --git a/applications/SprPoP/docs/sdlpop_audit.md b/applications/SprPoP/docs/sdlpop_audit.md index e1cdcf4..383b3f0 100644 --- a/applications/SprPoP/docs/sdlpop_audit.md +++ b/applications/SprPoP/docs/sdlpop_audit.md @@ -547,3 +547,165 @@ слоёв, `clip_char`. 4. **`do_fall`/`check_bumped`/`check_grab`** — остаток физики. 5. **ИИ стражей** и особенности скелета/Тени/Джафара. + + +--- + +# Глубокое ревью находок А и Б: можно ли починить и чем платим + +> 2026-08-31. КОД ПО-ПРЕЖНЕМУ НЕ МЕНЯЛСЯ. Здесь только оценка. +> +> **Главное ограничение (требование пользователя): фикс не должен заметно +> замедлять игру.** Поэтому у каждой находки первым делом указана ЧАСТОТА +> вызова места, а уже потом сама правка. +> +> **Оговорка к ограничению:** для КРИТИЧНЫХ фиксов скорость — не вето. +> Если такой фикс всерьёз бьёт по производительности, он выносится в +> отдельный разбор, где ищется способ получить правильное поведение +> дёшево (иной момент вызова, кэш, предвычисление, перенос в холодный +> путь). То есть порядок такой: сначала решаем, критично ли поведение, и +> только потом — какой ценой его добиться. + +## Снятое препятствие + +Обоснование сразу двух упрощений (`guards.c:1011` — «нужны тайловые +запросы ОТ Char, а pop_map умеет только от Kid») **устарело**. Проверено: +`get_tile_at_char`, `get_tile_infrontof_char`, `get_tile_behind_char` и +`distance_to_edge_weight` в `pop_map.c` УЖЕ работают от `Char` +(строки 460, 465, 477, 561). Они лишь не выведены в `pop_map.h`. + +Так же обстоит с данными: `pop_char_set_seq()` ставит любую из 115 +последовательностей по индексу, то есть `seq_81` и `seq_64` доступны без +единого нового байта данных — таблица генерируется из оригинала целиком. + +То есть три находки (12, 14, 20) упираются не в архитектуру, а в четыре +строки объявлений. + +## Классификация мест по частоте вызова + +| место | частота | вывод | +|---|---|---| +| `play_seq` (находки 7, 8) | КАЖДЫЙ кадр каждого персонажа | правка обязана быть бесплатной | +| `check_hurting` (18) | каждый кадр боя, дважды | почти горячий | +| ИИ стража (20) | каждый кадр, пока страж активен | почти горячий | +| `land`, `in_wall`, `bumped` (1, 2, 3, 14, 24) | событие раз в несколько секунд | холодный, цена не важна | +| `hurt_by_sword` (12, 13) | момент попадания | холодный | + +## Разбор по находкам + +### 12 + 13 (ранг А) — смерть на краю. Чинится, цена нулевая + +*Место:* `guards.c`, `hurt_by_sword` — холодный путь. + +*Правка:* (а) перенести две строки прижатия к полу внутрь ветки +выжившего удара — это чистое перемещение, минус ноль байт; (б) добавить +ветку выбора `seq_81` по тайлу позади и расстоянию до кромки. + +*Что нужно:* экспорт `get_tile_behind_char()` и `distance_to_edge_weight()` +из `pop_map.c` в `pop_map.h` как `__banked`. + +*Цена скорости:* два межбанковых вызова (`guards.c` — банк 1, `pop_map.c` +— банк 3) в момент попадания мечом, то есть несколько раз за бой. +Незаметно. + +*Цена памяти:* банк 1 занят на 19,8 % (13 142 Б свободно) — места вдоволь; +банк 3 занят на 81,1 % (3 100 Б), но там прибавятся только две обёртки. + +*Риск:* низкий. Ветка симметрична существующей, данные есть. + +### 1 (ранг А) — лишний `determine_col()` в `land` + +*Место:* холодный путь. *Правка:* убрать вызов и проверить, не +понадобился ли он нам вместо оригинального `load_fram_det_col`, который +оригинал делает в другом месте цепочки. *Цена:* отрицательная (кода +меньше). *Риск:* СРЕДНИЙ — вызов мог компенсировать наш иной порядок +загрузки кадра; убирать только с прогоном сцен падения и приземления. + +### 2, 3 (ранг Б) — `land`: `fall_x` и порядок проверки пик + +*Место:* холодный. *Правка 2:* сбрасывать только `fall_y` и после +`play_seq`, как оригинал. *Правка 3:* перенести проверку пик после +коррекции X. *Цена:* нулевая, это перестановка строк. *Риск:* низкий, +но обе меняют поведение на краю тайла — нужны прогоны с пиками. + +### 24 (ранг Б) — `in_wall` не перезагружает кадр. Одна строка + +*Место:* холодный. *Правка:* заменить `determine_col()` на +`pop_load_fram_det_col()` — он УЖЕ экспортирован (`pop_kid.h:109`) и, что +важно, НЕ банковый, то есть вызов прямой. *Цена скорости:* одна +перезагрузка кадра при выталкивании из стены — доли процента кадра. +*Риск:* низкий; это возврат к оригиналу. + +### 14 (ранг Б) — нет `seq_64` + +*Место:* `bumped`, холодный. *Правка:* добавить выбор между 64 и 65 по +направлению толчка. *Цена:* нулевая. *Риск:* низкий. + +### 18 (ранг Б) — звук удара + +*Место:* `check_hurting` — дважды за кадр боя. *Правка:* перенести звук +в конец функции и обвесить тремя условиями оригинала. *Цена:* +ОТРИЦАТЕЛЬНАЯ — звук перестанет играть в двух случаях из трёх, то есть +уменьшится и число обращений к звуковой очереди. *Риск:* низкий. + +### 20 (ранг Б) — «стена» у стражей. Требует осторожности со скоростью + +*Место:* ИИ стража — вызывается каждый кадр, пока страж активен. + +*Плохой вариант:* экспортировать `wall_type` из `pop_map.c` и звать из +`guards.c`. Это МЕЖБАНКОВЫЙ вызов (банк 1 → банк 3) в почти горячем +пути — трамплин с переключением W3 на каждый шаг ИИ. Против требования +по скорости. + +*Хороший вариант:* завести копию таблицы `wall_type_tbl` (32 байта) в +rodata банка 1 и обращаться к ней напрямую — стоимость чтения байта, +ноль переключений банка. Дублирование данных здесь оправдано: таблица +константная и вшита в формат уровней. + +*Риск:* низкий, но нужно следить, чтобы копия не разошлась с оригиналом — +лучше генерировать обе из одного места или снабдить перекрёстным +комментарием. + +### 7 (ранг Б) — перо только для Кида. Правка ускоряет + +*Место:* `play_seq`, самый горячий путь. *Правка:* убрать лишнее условие +по `charid`. *Цена:* ОТРИЦАТЕЛЬНАЯ — из горячего цикла уходит сравнение. +*Риск:* средний: надо убедиться, что физика пера у нас применяется к +любому персонажу так же, как в оригинале, иначе анимация разойдётся с +физикой. + +### 8 (ранг Б) — отложенные чомперы. Чинить ДЕШЁВЫМ способом + +*Место:* `play_seq`, горячий путь. + +*Почему отложено:* `play_seq` маппит страницу байткода в окно W0 ОДИН раз +перед циклом (`pop_kid.c:284`) и снимает после (`:389`). Вызвать +`start_chompers` внутри цикла — значит снять окно, позвать, вернуть окно, +и так на каждый переход ряда. Это прямая деградация горячего пути и +против требования по скорости. + +*Дешёвая замена:* сейчас копится ОДИН флаг, из-за чего теряются +промежуточные ряды. Достаточно копить не флаг, а НОМЕРА рядов — один +байт-битовую маску (рядов всего 0..3) плюс запомненную колонку. После +цикла пройти по взведённым битам и разбудить чомперов в каждом. Цена в +цикле: одна операция «выставить бит» вместо присваивания флага, то есть +ноль. Разница с оригиналом останется только в МОМЕНТЕ побудки (после +цикла, а не внутри), но ряды перестанут теряться. + +*Риск:* низкий. Полное совпадение с оригиналом здесь недостижимо без +потери скорости — это осознанный компромисс, который надо записать в +`docs/impl_diff.md`. + +## Сводка: что делать в каком порядке + +| приоритет | находки | почему | +|---|---|---| +| 1 | 13, 24 | по одной-две строки, риск низкий, оба в узле «смерть/стена» | +| 2 | 12, 14 | нужен экспорт двух функций; закрывают наблюдения пользователя | +| 3 | 18, 8 | обе УСКОРЯЮТ или бесплатны; 8 — по дешёвому варианту | +| 4 | 2, 3, 20 | перестановки и копия таблицы; нужны прогоны | +| 5 | 1, 7 | риск средний: обе могут компенсировать наши отличия в другом месте | + +**Ни один фикс не требует переделки архитектуры и ни один не ложится на +горячий путь с накладными расходами** — при условии, что находка 20 +делается копией таблицы, а находка 8 — битовой маской рядов.