4e12aa50d1
Портировано опциональное исправление SDLPoP (fix_glide_through_wall, seg005 в do_fall). В ванили персонаж, падающий после разворота в беге, может оказаться внутри кладки и лететь «в стене» — баг оригинала, воспроизведённый пользователем в игре и затем на host-тесте. Решением 2026-08-31 фикс взят в ТЕКУЩИЙ билд: играбельность важнее буквальности. Реализация вынесена отдельной функцией glide_through_wall_guard() в pop_map.c намеренно — при разделении VANILLA/ENHANCED это готовая точка отвязки, достаточно не звать её в ванильном режиме. ПРОВЕРКА. Набор t_wall был заранее написан так, чтобы сторожить ЧИСЛО заходов в кладку: до фикса их было ровно два из четырнадцати стартовых позиций, после — ноль. Остальные 15 наборов (в том числе phys с 1733 проверками и grab) остались зелёными. Живая проверка в MAME пользователем: корректно. Ожидание в тесте обновлено ОСОЗНАННО, прежнее число сохранено рядом отдельной константой с пометкой «сколько было до фикса»: оно измерено, и понадобится, когда появится режим VANILLA — там ожидание станет зависеть от режима. ЦЕНА: +57 байт в банке 3 (свободно 3043), резидент и куча не изменились. По скорости попадание только на кадры падения: пересчёт колонки — одно деление, дистанция до кромки считается лишь если персонаж действительно внутри кладки. Документы: в аудите находка 21 переведена в «портировано» с сохранением исходного разбора; в vanilla_vs_bugfixed статус фикса стал ВЗЯТ, сводка пересчитана (5 взято, 32 кандидата в ENHANCED). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
756 lines
55 KiB
Markdown
756 lines
55 KiB
Markdown
# Аудит расхождений с SDLPoP: Кид, стражи, seqtbl, отрисовка
|
||
|
||
> Начат 2026-08-31. КОД НЕ МЕНЯЛСЯ — это только разбор. Задача: найти
|
||
> места, где наш движок может вести себя иначе, чем оригинал, и оценить
|
||
> вероятность того, что расхождение реально.
|
||
|
||
## Как читать
|
||
|
||
Ранги вероятности того, что расхождение ЕСТЬ и проявляется в игре:
|
||
|
||
| ранг | смысл |
|
||
|---|---|
|
||
| **А** | гарантированное различие: код объективно разный, эффект понятен |
|
||
| **Б** | весьма вероятное: код разный, эффект вероятен, но не доказан |
|
||
| **В** | средневероятное: код разный, но эффект может гаситься другим местом |
|
||
| **Г** | маловероятное: различие есть в форме, эффект скорее отсутствует |
|
||
| **Д** | почти невероятное: сходство подтверждено, остаётся крайний случай |
|
||
|
||
Ссылки вида `seg005:114` — строка в `assets/orig/SDLPoP/src/`. Наши
|
||
ссылки — `файл:строка` в `src/`.
|
||
|
||
## Метод и охват
|
||
|
||
Сравниваются НАШИ реализации с оригиналом построчно по функциям. Первый
|
||
проход (2026-08-31) охватил:
|
||
|
||
* диспетчер `control()` (seg005:251) — целиком;
|
||
* `land()` (seg005:114) и `start_fall()` (seg006:1099);
|
||
* цепочку смерти: `control_kid` (seg006:1390), `play_kid` (seg006:1348),
|
||
`take_hp` (seg006:986), `control()` ветка `alive >= 0`.
|
||
|
||
НЕ охвачено первым проходом (список для следующих):
|
||
|
||
* интерпретатор `play_seq` и полный набор опкодов seqtbl;
|
||
* `frame_table` и модификаторы кадров;
|
||
* бой целиком: `control_with_sword`, парирование, `strike`, `hurt_by_sword`;
|
||
* ИИ стражей (`guard_ai`), особенности скелета, Тени, Джафара;
|
||
* `add_kid_to_objtable`/`add_guard_to_objtable`, порядок слоёв, `clip_char`;
|
||
* `check_bumped` — сверен ЧАСТИЧНО (находки 14, 15); `check_grab`,
|
||
`in_wall`, `check_bumped_look_left` — нет;
|
||
* `do_fall` целиком (проверен только вход).
|
||
|
||
---
|
||
|
||
## Симптом, с которого начат аудит
|
||
|
||
**Наблюдение (пользователь, 2026-08-31):** идёт бой, за Кидом провал на
|
||
этаж. Страж колет на последнем HP, Кид отшатывается назад и падает.
|
||
Кид умирает — но на экране он этажом ниже В ПРИСЕДЕ, как после мягкого
|
||
приземления.
|
||
|
||
**Что говорит код SDLPoP.** Разбор цепочки:
|
||
|
||
1. `start_fall` (seg006:1099) ПЕРВЫМ ДЕЛОМ убирает меч
|
||
(`Char.sword = sword_0_sheathed`) — для любого падения, независимо от
|
||
здоровья. Значит к моменту приземления меч уже в ножнах.
|
||
2. `land` (seg005:114) при падении на один ряд выбирает
|
||
`seq_63_guard_active_after_fall`, только если `charid >= guard` ИЛИ
|
||
меч вынут; иначе — `seq_17_soft_land`, то есть ПРИСЕД. Из-за п.1 для
|
||
Кида это всегда присед.
|
||
3. `land` НЕ смотрит ни на `alive`, ни на `hitp_curr` вовсе.
|
||
4. `control` (seg005:251) при мёртвом персонаже (`alive >= 0`) не
|
||
диспетчеризует ничего; он переводит в `seq_71_dying` ТОЛЬКО из
|
||
четырёх кадров стойки (15, 166, 158, 171). Присед в этот список не
|
||
входит.
|
||
|
||
**Вывод:** по букве оригинала мёртвый Кид, застигнутый смертью в полёте,
|
||
тоже долетает, приземляется в присед и остаётся в нём — `control` его не
|
||
трогает. То есть наблюдаемое, СКОРЕЕ ВСЕГО, воспроизводится и в SDLPoP.
|
||
|
||
Гипотеза «hp стал нулевым, поэтому спрятали меч» кодом НЕ подтверждается:
|
||
единственное место, где SDLPoP связывает `hitp_curr == 0` с чем-либо, —
|
||
`control_kid` (seg006:1395), и там взводится только `Char.alive = 0`.
|
||
|
||
**Как проверить окончательно:** прогнать сцену на живом SDLPoP (он
|
||
собирается в `assets/orig/SDLPoP/`), где для этого уже есть наши врезки
|
||
`POP_TRACE`. До проверки считаю симптом НЕ доказанным расхождением —
|
||
ранг **В** (см. находку 5).
|
||
|
||
---
|
||
|
||
## Находки первого прохода
|
||
|
||
### 1. `land()`: лишний `determine_col()` — ранг **А**
|
||
|
||
*Оригинал:* `land` (seg005:114) заканчивается тремя действиями:
|
||
`seqtbl_offset_char(seq_id)`, `play_seq()`, `Char.fall_y = 0`. Никакого
|
||
пересчёта колонки.
|
||
|
||
*У нас:* `pop_map.c:715` (и в ветке смерти `:705`) после `play_seq()`
|
||
дополнительно вызывается `determine_col()`.
|
||
|
||
*Чем грозит:* `determine_col` пересчитывает `Char.curr_col` из `Char.x`.
|
||
Оригинал делает это в другом месте и в другой момент —
|
||
`load_fram_det_col` (seg006:0144) перед разбором кадра. Лишний пересчёт
|
||
сразу после приземления может дать другую колонку, если `play_seq`
|
||
успел сдвинуть `x` первым же `dx`. Колонка — вход для проверок тайла,
|
||
пик и коллизий.
|
||
|
||
*Замечание:* приём применён у нас системно (`pop_map.c` — 8 вызовов), то
|
||
есть это, вероятно, осознанная адаптация, но в `docs/impl_diff.md` она НЕ
|
||
записана. Нужно либо обосновать и записать, либо снять.
|
||
|
||
### 2. `land()`: обнуляется ещё и `fall_x` — ранг **Б**
|
||
|
||
*Оригинал:* в `land` обнуляется ТОЛЬКО `fall_y`, и притом в самом конце,
|
||
ПОСЛЕ `play_seq()`.
|
||
|
||
*У нас:* `pop_map.c:697` и `:713` обнуляют пару `Char.fall_x = Char.fall_y = 0`,
|
||
причём ДО `pop_char_set_seq`/`play_seq`.
|
||
|
||
*Чем грозит:* два отличия сразу. Во-первых, `fall_x` в оригинале
|
||
переживает приземление — если наш сброс лишний, теряется горизонтальный
|
||
импульс, влияющий на последующие кадры. Во-вторых, момент: если
|
||
`play_seq` читает `fall_y` (а он читает при обработке своих опкодов
|
||
движения), оригинал видит ещё НЕ обнулённое значение, а мы — уже ноль.
|
||
|
||
### 3. `land()`: проверка пик до коррекции X — ранг **Б**
|
||
|
||
*Оригинал:* сначала (внутри ветки «тайл под ногами не пика») делается
|
||
коррекция `Char.x = char_dx_forward(-3)` при `distance_to_edge_weight() < 3`,
|
||
и лишь ПОТОМ проверяется падение на пики, причём условие для пики ПОЗАДИ
|
||
использует `distance_to_edge_weight() >= 12`.
|
||
|
||
*У нас:* `pop_map.c:664` — `fell_on_spikes()` вызывается ПЕРВЫМ, до
|
||
коррекции X.
|
||
|
||
*Чем грозит:* `distance_to_edge_weight()` считается от `Char.x`, а
|
||
коррекция этот `x` меняет на 3 пикселя. У края тайла порядок решает,
|
||
попадёт ли персонаж в ветку «пика позади» — то есть умрёт он или нет.
|
||
|
||
### 4. Смерть: у нас свой флаг вместо счётчика — ранг **В**
|
||
|
||
*Оригинал:* `play_kid` (seg006:1348) после смерти ведёт СЧЁТЧИК
|
||
`Char.alive`, и по его значениям запускает музыку смерти (`alive == 6`) и
|
||
надпись «Press Button to Continue» (`alive == 7`), причём переход
|
||
задерживается, пока играет звук (`check_sound_playing`).
|
||
|
||
*У нас:* `pop_ctrl.c` (`ctrl_kid_death`) взводит `pop_kid_dead` — сигнал
|
||
главному циклу на респавн; счётчика стадий нет.
|
||
|
||
*Чем грозит:* момент респавна и порядок «музыка смерти → сообщение →
|
||
рестарт» могут отличаться, особенно если смерть застала персонажа в
|
||
длинной анимации (падение). Сюда же относится симптом выше: в оригинале
|
||
поза сохраняется, пока крутится счётчик.
|
||
|
||
### 5. Мёртвый доигрывает приземление — ранг **В**
|
||
|
||
Разобрано выше. Код у нас и в оригинале В ЭТОМ МЕСТЕ совпадает, поэтому
|
||
ранг не выше среднего: расхождение может сидеть не в `land`, а в моменте
|
||
взведения смерти (находка 4) — тогда оригинал успевает поставить кадр
|
||
смерти до приземления, а мы нет. Проверяется прогоном на живом SDLPoP.
|
||
|
||
### 6. Диспетчер `control()` — ранг **Д**
|
||
|
||
Сверен ветка в ветку (seg005:251 против `pop_ctrl.c:618`): совпадают и
|
||
порядок проверок (`bumped/freefall` → меч → charid → кадры), и границы
|
||
диапазонов кадров, и обработка мёртвого. Расхождений не видно; остаются
|
||
только опциональные `#ifdef`-фиксы SDLPoP, которых у нас нет намеренно.
|
||
|
||
### 7. `JMP_IF_FEATHER`: у нас эффект только для Кида — ранг **Б**
|
||
|
||
*Оригинал:* опкод `SEQ_JMP_IF_FEATHER` (seg006, play_seq) смотрит ТОЛЬКО
|
||
на глобальный `is_feather_fall`. Кто именно проигрывает последовательность,
|
||
роли не играет.
|
||
|
||
*У нас:* `pop_kid.c:306` добавляет условие `Char.charid != CHARID_0_KID` —
|
||
для всех, кроме Кида, ветка «пера» не берётся никогда.
|
||
|
||
*Чем грозит:* под зельем медленного падения любой НЕ-Кид, попавший в
|
||
последовательности `stepfloat`/`bumpfloat`, у нас пойдёт по обычной ветке
|
||
(с уроном), а в оригинале — по парящей. Практически это Тень (charid 1) на
|
||
уровне 4-6 и скелет; страж в эти seq попадает редко, но попадает через
|
||
`bumpfloat` при отскоке.
|
||
|
||
*Замечание:* отличие ОСОЗНАННОЕ (в комментарии сказано «эффект достаётся
|
||
только Киду — как и сама физика пера»), но в `docs/impl_diff.md` не
|
||
записано, хотя правило проекта этого требует.
|
||
|
||
### 8. `start_chompers` отложен до конца `play_seq` — ранг **Б**
|
||
|
||
*Оригинал:* опкоды `SEQ_UP`/`SEQ_DOWN` меняют ряд и ТУТ ЖЕ зовут
|
||
`start_chompers()` — то есть внутри цикла интерпретатора, до разбора
|
||
следующих опкодов.
|
||
|
||
*У нас:* `pop_kid.c:318-325` только взводит `chomp_pending`, а сам вызов
|
||
происходит после выхода из цикла (`:396`). Причина архитектурная и
|
||
описана в коде: seqtbl читается через окно W0, а `start_chompers` лезет в
|
||
другое окно.
|
||
|
||
*Чем грозит:* два следствия. Во-первых, последовательность с ДВУМЯ
|
||
сменами ряда (`UP` `UP`, спуск/подъём по лестнице) в оригинале будит
|
||
чомперов в обоих рядах, у нас — только в конечном. Во-вторых, между
|
||
`SEQ_UP` и концом цикла успевают отработать `dx`/`dy`/`action`, то есть
|
||
оригинал будит чомперов с ДРУГИМИ координатами персонажа.
|
||
|
||
Симптом «челюсти не заводятся» уже ловился в этом проекте (memory
|
||
`pop_chomper_needs_trigger`), и это место — кандидат в его причины.
|
||
|
||
### 9. Набор опкодов seqtbl — ранг **Д**
|
||
|
||
Сверены все пятнадцать кодов (`0xF1`..`0xFF`): совпадают и значения, и
|
||
семантика, включая проваливание `JMP_IF_FEATHER` в `JMP` и то, что
|
||
`SEQ_DIE` — пустышка в обоих движках. Отличия только в находках 7 и 8.
|
||
|
||
### 10. Отрисовка: шаги те же, но разнесены — ранг **В**
|
||
|
||
*Оригинал:* `add_kid_to_objtable` (seg008:1667) и его двойник для стража —
|
||
это строго упорядоченная цепочка: `loadkid`/`loadshad` →
|
||
`load_fram_det_col` → `load_frame_to_obj` → `stuck_lower` →
|
||
`set_char_collision` → `set_objtile_at_char` → `redraw_at_char` →
|
||
`redraw_at_char2` → `clip_char` → `add_objtable`.
|
||
|
||
*У нас:* все звенья присутствуют, но распределены по слоям: `clip_char`,
|
||
`load_frame_to_obj`, `check_mirror`, брызги — в `pop_cdraw.c`;
|
||
`redraw_at_char`/`set_objtile_at_char`/`set_char_collision` — в `pop_bg.c`
|
||
(единый проход на всех Char, см. CLAUDE.md).
|
||
|
||
*Чем грозит:* сам по себе перенос не ошибка, но ПОРЯДОК внутри цепочки
|
||
влияет на результат: `set_char_collision` и `set_objtile_at_char` готовят
|
||
данные, которыми пользуются `redraw_at_char` и `clip_char`. Если наш
|
||
общий проход выполняет их для ОБОИХ персонажей до отрисовки, а оригинал —
|
||
для каждого непосредственно перед его выводом, то при наложении Кида и
|
||
стража состояние на момент клипа будет разным.
|
||
|
||
*Отдельно:* `stuck_lower` найден только в `pop_cdraw.h` — надо убедиться,
|
||
что он реализован, а не только объявлен. Если его нет, персонаж,
|
||
застрявший на границе тайла, будет рисоваться на пиксель выше.
|
||
|
||
### 11. Порядок вывода Кида и стража — ранг **В**
|
||
|
||
*Оригинал:* `draw_people` (seg008:1635) всегда ставит сначала Кида
|
||
(`draw_kid`), затем стража (`draw_guard`), а КТО ОКАЖЕТСЯ СВЕРХУ решает
|
||
`add_objtable` — таблица объектов упорядочена по позиции тайла.
|
||
|
||
*У нас:* по CLAUDE.md порядок задаёт обход тайлов, «кто позже — тот
|
||
поверх». Это близко по смыслу, но не тождественно сортировке objtable.
|
||
|
||
*Чем грозит:* при наложении персонажей (бой вплотную, страж перед Кидом)
|
||
верхний может оказаться другим. Проверять сравнением кадров боя вплотную
|
||
с эталонным SDLPoP (метод — memory `pop_pixel_diff_vs_sdlpop`).
|
||
|
||
### 12. `hurt_by_sword`: ветка «сбит с уступа» не портирована — ранг **А**
|
||
|
||
*Оригинал:* `hurt_by_sword` (seg002:911) при уколе ВООРУЖЁННОГО персонажа
|
||
выбирает одну из двух смертей по обстановке ПОЗАДИ:
|
||
|
||
* тайл позади не пустой ИЛИ до кромки меньше 4 → `seq_85_stabbed_to_death`
|
||
(заколот на месте);
|
||
* иначе → `seq_81_kid_pushed_off_ledge` — отдельная последовательность
|
||
«убит и сброшен с уступа», которая сама отыгрывает падение замертво.
|
||
|
||
*У нас:* `guards.c:1015` — ветки `seq_81` НЕТ вовсе, всегда `seq_85`.
|
||
Упрощение ЗАДОКУМЕНТИРОВАНО в комментарии (`guards.c:1011`): ей нужны
|
||
тайловые запросы от `Char`, а `pop_map` умеет их только от `Kid`.
|
||
|
||
*Чем грозит:* именно тем, что наблюдал пользователь. Заколотый на краю
|
||
обрыва Кид в оригинале уходит в собственную анимацию падения с уступа; у
|
||
нас он получает «смерть на месте», продолжая при этом висеть в воздухе —
|
||
дальше им распоряжается обычная физика падения, и он приземляется этажом
|
||
ниже по общим правилам (а с убранным в `start_fall` мечом — в присед,
|
||
находка 5).
|
||
|
||
*Как проверить:* поставить Кида спиной к обрыву с 1 HP и дать стражу
|
||
уколоть. В оригинале — падение замертво (кадры seq_81), у нас — смерть
|
||
на месте с последующим отдельным падением.
|
||
|
||
### 13. `hurt_by_sword`: прижатие к полу стало безусловным — ранг **А**
|
||
|
||
*Оригинал:* `Char.y = y_land[Char.curr_row + 1]` и `Char.fall_y = 0`
|
||
выполняются ТОЛЬКО в ветке выжившего удара (seg002:962, рядом с
|
||
`seq_74_hit_by_sword`). Смертельные ветки координату не трогают.
|
||
|
||
*У нас:* `guards.c` — те же две строки стоят ПОСЛЕ всего `if/else`, то
|
||
есть выполняются и при смерти тоже.
|
||
|
||
*Чем грозит:* персонажа, убитого в воздухе, мы принудительно ставим на
|
||
пол текущего ряда и обнуляем накопленную скорость падения. Дальше физика
|
||
обнаруживает, что пола под ним нет, и запускает падение ЗАНОВО — уже без
|
||
`fall_y`, то есть с другой высотой и другим исходом приземления. Это
|
||
вторая половина механизма из находки 12 и вероятная причина того, что
|
||
мёртвый Кид доезжает до нижнего этажа «своим ходом».
|
||
|
||
*Как проверить:* тот же сценарий; в отладчике смотреть `Char.y` и
|
||
`fall_y` сразу после попадания — оригинал их не меняет.
|
||
|
||
### 14. Отскок с мечом: нет `seq_64` — ранг **Б**
|
||
|
||
*Оригинал:* при отскоке (`bumped`, seg004:328) живой персонаж с вынутым
|
||
мечом получает ОДНУ ИЗ ДВУХ последовательностей по направлению толчка:
|
||
толкнули вперёд — `seq_65_bump_forward_with_sword`, отбросило назад —
|
||
`seq_64_pushed_back_with_sword`.
|
||
|
||
*У нас:* `pop_map.c` знает только `SEQ_65_BUMP_FWD_SWORD` (объявлен на
|
||
`:103`, используется на `:2231`); константы и ветки `seq_64` нет вовсе.
|
||
|
||
*Чем грозит:* персонаж, отброшенный назад с мечом (страж у стены, Кид в
|
||
тесной комнате), проигрывает не ту анимацию — либо ветку без меча, либо
|
||
`seq_65`. Кадры разные, а вместе с ними расходятся и смещения `dx` в
|
||
последовательности, то есть итоговая позиция после отскока.
|
||
|
||
*Как проверить:* бой вплотную к стене, толчок в сторону стены и от неё;
|
||
сверять кадры с эталонным прогоном SDLPoP.
|
||
|
||
### 15. `check_bumped_look_right`: гейт по направлению — ранг **В**
|
||
|
||
В нашей реализации (`pop_map.c:2148`) стоит ранний выход по
|
||
`Char.direction` с пометкой «(меча в руке у нас нет)». Пометка означает,
|
||
что ветка писалась до появления боя, а оригинал в этом месте учитывает и
|
||
меч, и `push_direction` (находка 14). Область `check_bumped_look_left`
|
||
не сверялась вовсе — её надо пройти отдельно.
|
||
|
||
### 16. `control_with_sword` — ранг **Д**
|
||
|
||
Сверен целиком (seg005:964 против `pop_ctrl.c:587`): гейт по `action`,
|
||
условие «пол под ногами loose ИЛИ страж видит Кида», пороги дистанции
|
||
(90 и −4), `seq_60_turn_with_sword`, ветка «соперник умер» с
|
||
`seq_92_put_sword_away`, разделение по `charid`. Совпадает.
|
||
|
||
Отдельно отмечу: в оригинале сравнение дистанции сделано ЗНАКОВО-НЕЯВНО
|
||
(приведением к `word`), из-за чего ветка «соперник за спиной» вообще
|
||
достижима. У нас то же самое выражено явными знаковыми сравнениями — и
|
||
диапазоны совпадают, включая «вплотную за спиной» (−4..−1), где обе
|
||
реализации ведут бой, а не разворачиваются.
|
||
|
||
### 17. `parry` — ранг **Д**
|
||
|
||
Сверен целиком (seg005:1064 против `pop_ctrl.c:513`): список кадров
|
||
стойки, порог 32 для не-Кида, обработка кадров соперника (151/152/162,
|
||
особый случай 153 с отложенным `play_seq`), ветка стража по кадру 152,
|
||
ветка `frame_167_blocked` с `seq_61`, сброс автоповтора `control_up`.
|
||
Совпадает вплоть до порядка условий.
|
||
|
||
### 18. `check_hurting`: звук «меч в движении» в других условиях — ранг **Б**
|
||
|
||
*Оригинал:* звук 11 играется в САМОМ КОНЦЕ `check_hurting` (seg002) и
|
||
только при трёх условиях сразу: направление персонажа не `none`, его кадр
|
||
— укол (154), а соперник при этом НЕ парирует и НЕ ранен. Первое условие
|
||
добавлено в SDLPoP специально против зацикливания звука.
|
||
|
||
*У нас:* `guards.c:1068` — звук играется в начале ветки укола,
|
||
безусловно, ещё до того, как определено попадание.
|
||
|
||
*Чем грозит:* лишние срабатывания в двух ситуациях, где оригинал молчит —
|
||
когда удар парирован и когда он попал. То есть в самой частой части боя
|
||
звук звучит чаще, чем должен. Плюс отсутствует защита от зацикливания
|
||
при `direction == none`.
|
||
|
||
*Как проверить:* бой с парирующим стражем; считать срабатывания звука 11
|
||
на серии ударов и сравнить с эталонным прогоном SDLPoP.
|
||
|
||
### 19. Остальной бой сверен — ранг **Д**
|
||
|
||
Прочитаны целиком и совпадают:
|
||
|
||
* `swordfight` (seg005:998) — включая ветку кадра 161, `sword_strike`,
|
||
побочные эффекты уборки меча (`offguard`, `guard_refrac`,
|
||
`holding_sword`), разделение `seq_93`/`seq_92`/`seq_87` по `charid` и
|
||
хвост (`parry` / `forward_with_sword` / `back_with_sword`);
|
||
* `sword_strike` (seg005:1037) — список кадров, выбор `seq_75`/`seq_58`,
|
||
`seq_66` после парирования, сброс автоповтора;
|
||
* `check_sword_hurt` (seg002:971) — включая ПРИОРИТЕТ СТРАЖА при
|
||
одновременном ранении и сброс `Kid.action` в бег, а также
|
||
`refractimer` по навыку;
|
||
* `check_hurting` в основной части — гейты по мечу, ряду и кадрам, пороги
|
||
дистанции (29), `min_hurt_range` 8/12 по мечу соперника, ветка
|
||
парирования с `justblocked` и `seq_69`. Единственное расхождение —
|
||
звук, находка 18.
|
||
|
||
### 20. Стражи: «стена впереди» сужена до одного тайла — ранг **Б**
|
||
|
||
*Оригинал:* `guard_follows_kid_down` (seg002:811) и соседние ветки ИИ
|
||
спрашивают `wall_type(tile) != 0`. Эта функция (seg006:1626) считает
|
||
преградой ПЯТЬ видов тайлов: ворота, верх двери с полом, верх двери,
|
||
зеркало, чомпер и собственно стену — с разной стороной блокировки.
|
||
|
||
*У нас:* `guards.c:683` и `:685` сравнивают тайл напрямую с `TILE_WALL`
|
||
(тип 20). Ворота, верх двери, зеркало и чомпер преградой не считаются.
|
||
|
||
*Причина:* `wall_type` реализована у нас (`pop_map.c:504`, таблица на
|
||
`:498`), но НЕ экспортирована — в `pop_map.h` её нет, поэтому `guards.c`
|
||
до неё не дотягивается. То есть это не пробел в портировании логики, а
|
||
следствие границы модулей.
|
||
|
||
*Чем грозит:* страж, преследующий упавшего Кида, у нас шагнёт вперёд там,
|
||
где оригинал отступает — перед закрытыми воротами, верхом двери,
|
||
зеркалом и чомпером. Отсюда возможны и проход стража сквозь препятствие,
|
||
и падение туда, куда оригинал его не пускает.
|
||
|
||
*Как проверить:* уровень с воротами (например, 3-й) — заманить стража к
|
||
закрытым воротам после падения Кида и сравнить, отступает ли он.
|
||
|
||
*Замечание:* в `guards.c` таких мест ЧЕТЫРЕ (`:148`, `:683`, `:685`);
|
||
одно из них (`:148`) уже перечисляет три тайла вручную, то есть
|
||
расхождение частично компенсировано, но не везде одинаково.
|
||
|
||
---
|
||
|
||
## Область: столкновение со стенами и падение внутри стены
|
||
|
||
Заведена по наблюдению пользователя (2026-08-31): разбег, прыжок сделан
|
||
рано, Кид не долетел, врезался в стену и начал падать — но по X он
|
||
оказался ВНУТРИ стены и падал частично в ней.
|
||
|
||
### 21. Фикс «скольжения сквозь стену» — ПОРТИРОВАН 2026-08-31
|
||
|
||
> Решением пользователя исправление взято в ТЕКУЩИЙ билд: играбельность
|
||
> важнее буквальности. Реализация — `glide_through_wall_guard()` в
|
||
> `pop_map.c`, вызывается из `do_fall` в ветке «ещё летим». Цена: +57
|
||
> байт в банке 3, резидент и куча не изменились.
|
||
>
|
||
> Подтверждено host-тестом: в наборе `t_wall` число заходов в кладку
|
||
> упало с двух до нуля, остальные 15 наборов остались зелёными.
|
||
>
|
||
> Ниже — исходный разбор, по которому принималось решение.
|
||
|
||
### 21a. Исходный разбор: было соответствие ванили — ранг **Г**
|
||
|
||
*Оригинал:* в `do_fall` (seg005:37) есть блок `FIX_GLIDE_THROUGH_WALL` с
|
||
собственным комментарием SDLPoP: «Кид падает сквозь стены после разворота
|
||
в беге, особенно в невесомости». Блок опциональный — то есть в ВАНИЛЬНОЙ
|
||
игре этот баг ЕСТЬ, а SDLPoP его чинит по желанию. Рядом такие же
|
||
опциональные `FIX_JUMP_THROUGH_WALL_ABOVE_GATE` и `FIX_DROP_THROUGH_TAPESTRY`.
|
||
|
||
*У нас:* ни один из трёх не портирован — мы намеренно повторяем ваниль.
|
||
|
||
*Вывод по симптому:* «падение частично в стене» — с большой вероятностью
|
||
ОРИГИНАЛЬНОЕ поведение PoP, а не наша ошибка. Ранг Г означает: различия
|
||
с ванилью, скорее всего, нет. Но проверить стоит другое — не ХУЖЕ ли у
|
||
нас, чем в ванили (см. находки 22 и 15).
|
||
|
||
*Как проверить:* повторить сцену на живом SDLPoP с выключенными фиксами
|
||
(они выключаемы в его настройках) и сравнить глубину захода в стену.
|
||
|
||
### 22. `do_fall`: наш гард `curr_row <= 2` — ранг **В**
|
||
|
||
*Оригинал:* в `do_fall` ветка «достигли нового ряда» выполняется БЕЗ
|
||
условия на номер ряда: проверка тайла стены с вызовом выталкивания, затем
|
||
`land()` либо переход на ряд ниже.
|
||
|
||
*У нас:* `pop_map.c:909` — вся ветка обёрнута в `if (Char.curr_row <= 2)`.
|
||
Причина задокументирована (`:768`): наш `get_tile` за нижней кромкой
|
||
комнаты отдаёт СТЕНУ как сентинель, тогда как в оригинале там комната
|
||
снизу, и без гарда выталкивание срабатывало ложно, смещая падение на тайл.
|
||
|
||
*Чем грозит:* гард гасит не только ложные срабатывания. Если персонаж
|
||
достиг `curr_row == 3` легитимно (падение между комнатами по вертикали),
|
||
у нас не выполнится ни выталкивание из стены, ни `land()`, ни переход
|
||
ряда — всё это ляжет на следующий кадр и другую ветку. Именно такая
|
||
комбинация (падение у границы комнаты рядом со стеной) даёт кандидата в
|
||
причины наблюдения пользователя.
|
||
|
||
*Как проверить:* падение вдоль стены точно на стыке комнат по вертикали;
|
||
в отладчике смотреть `curr_row`, `Char.x` и факт вызова выталкивания.
|
||
|
||
### 23. `bumped_fall` — ранг **Д**
|
||
|
||
Сверен (seg004 против `pop_map.c`): откат X на 4 пикселя назад, обнуление
|
||
горизонтальной скорости в свободном падении, иначе `seq_45_bumpfall` с
|
||
проигрыванием, звук удара. Совпадает; у нас добавлен только флаг «стражи
|
||
услышали», который в оригинале ставится внутри звуковой функции.
|
||
|
||
**Замечание по области:** глубина отката при столкновении — ровно 4
|
||
пикселя в обоих движках. Если Кид вошёл в стену глубже (а при
|
||
недолёте с разбега скорость по X велика), одного отката не хватит ни там,
|
||
ни у нас — и дальше всё зависит от того, сработает ли выталкивание из
|
||
стены на следующем кадре. У нас его может съесть гард из находки 22.
|
||
Это главная зацепка по симптому.
|
||
|
||
### 24. `in_wall`: не перезагружается кадр — ранг **Б**, ОТЛОЖЕНА
|
||
|
||
> **Правка сделана и откачена 2026-08-31.** По букве оригинала находка
|
||
> верна, но практического эффекта показать не удалось: все 15 наборов
|
||
> host-тестов дали одинаковый результат до и после, включая специально
|
||
> написанный тест на заход в кладку (`t_wall`). При этом правка не
|
||
> бесплатна — добавляет маппинг окна и перезагрузку кадра на каждое
|
||
> выталкивание из стены. Платить за недоказанное не стали.
|
||
>
|
||
> Задача переехала в `docs/vanilla_vs_bugfixed.md`: вернуться к ней при
|
||
> работе над двумя режимами поведения, где появится сценарий, в котором
|
||
> кадр меняется перед выталкиванием.
|
||
|
||
|
||
*Оригинал:* `in_wall` (seg006) после выталкивания персонажа из стены
|
||
делает `load_fram_det_col()` — ЗАГРУЖАЕТ КАДР и следом определяет колонку,
|
||
затем перечитывает тайл.
|
||
|
||
*У нас:* `pop_map.c` (`in_wall`) вызывает только `determine_col()`.
|
||
Пороги (`>= 8`), формулы смещения (`6 - d` и `d + 4`), условие по тайлу
|
||
впереди и финальное чтение тайла совпадают — расходится только этот шаг.
|
||
|
||
*Чем грозит:* после выталкивания данные кадра (картинка, смещения, флаги
|
||
— включая «нужен пол» и «чётный пиксель») остаются от позиции ДО
|
||
коррекции, а ими пользуются проверки того же кадра: падение, клип,
|
||
коллизия. Это ровно область, где наблюдалось падение внутри стены
|
||
(находки 21, 22).
|
||
|
||
*Как проверить:* недолёт с разбега в стену; в отладчике сравнить `Char.x`,
|
||
колонку и поля текущего кадра сразу после выталкивания.
|
||
|
||
### 25. Таблицы кадров и seqtbl — ранг **Д**
|
||
|
||
`frame_table_kid`, `original_seqtbl` и таблица смещений извлекаются
|
||
АВТОМАТИЧЕСКИ из исходников SDLPoP (`tools/pop_extract_kid_data.py` →
|
||
`gen/kid_data.h`), поэтому расхождение в данных маловероятно по
|
||
построению. Применение тоже сверено: используются все четыре флага кадра
|
||
(«нужен пол» 0x40, вес по X 0x1F, «тонкий» 0x20, чётный пиксель 0x80), а
|
||
байт клинка маскируется как в оригинале (`& 0x3F`, `pop_kdraw.c:31`).
|
||
|
||
Не сверено: старшие два бита байта клинка (номер набора спрайтов) — у
|
||
Кида он нулевой, у прочих персонажей стоит проверить отдельно.
|
||
|
||
### 26. Полнота автоуправления — ранг **Д**
|
||
|
||
Из двенадцати функций `autocontrol_*` оригинала у нас есть одиннадцать.
|
||
Отсутствующая — тривиальная обёртка над общей логикой стража; у нас она
|
||
встроена в вызывающего. Расхождения нет.
|
||
|
||
Не сверены ПОСТРОЧНО тела: `autocontrol_guard_kid_armed`,
|
||
`autocontrol_guard_kid_far`, `autocontrol_shadow*`, `autocontrol_skeleton`,
|
||
`check_grab`, `check_bumped_look_left`, `back_with_sword`,
|
||
`forward_with_sword`.
|
||
|
||
---
|
||
|
||
## ИТОГ АУДИТА
|
||
|
||
Проверено 26 позиций за пять проходов.
|
||
|
||
| ранг | находки | суть |
|
||
|---|---|---|
|
||
| **А** | 1, 12, 13 | лишний пересчёт колонки в `land`; нет ветки «убит и сброшен с уступа»; безусловное прижатие к полу при смерти |
|
||
| **Б** | 2, 3, 7, 8, 14, 18, 20, 24 | `fall_x` и момент сброса; порядок «пики / коррекция X»; перо только для Кида; отложенные чомперы; нет `seq_64`; звук удара; «стена» сужена до одного тайла; нет перезагрузки кадра в `in_wall` |
|
||
| **В** | 4, 5, 10, 11, 15, 22 | флаг смерти вместо счётчика стадий; мёртвый доигрывает приземление; разнесённая цепочка отрисовки; порядок Кид/страж; гейт в `check_bumped_look_right`; гард `curr_row <= 2` в `do_fall` |
|
||
| **Г** | 21 | опциональные фиксы SDLPoP не портированы — соответствие ванили |
|
||
| **Д** | 6, 9, 16, 17, 19, 23, 25, 26 | сверено и совпадает |
|
||
|
||
### Три узла, вокруг которых группируются расхождения
|
||
|
||
1. **Смерть при активной физике** (1, 12, 13, 4, 5). Здесь все находки
|
||
ранга А. Общая причина: у нас смерть — это флаг, а физика продолжает
|
||
работать с персонажем как с живым.
|
||
2. **Границы модулей** (12, 20, 24). `pop_map` не отдаёт наружу то, что
|
||
нужно `guards.c` и работе с произвольным `Char`: тайловые запросы от
|
||
`Char`, `wall_type`, загрузку кадра. Ветки упрощались не по логике, а
|
||
по доступности функций.
|
||
3. **Момент побочных действий** (2, 8, 18, 24). Делаем то же самое, но
|
||
раньше или позже оригинала: сброс скорости, побудка чомперов, звук,
|
||
перезагрузка кадра. По отдельности мелочь, вместе — сдвиг состояния
|
||
на кадр.
|
||
|
||
### Что делать дальше
|
||
|
||
1. Проверить находки А и Б в MAME по сценариям из их описаний — начиная с
|
||
12/13 (смерть на краю) и 24 (выталкивание из стены).
|
||
2. Те же сцены прогнать на живом SDLPoP: часть наблюдений может оказаться
|
||
ванильным поведением (как находка 21).
|
||
3. Подтверждённые осознанные отличия записать в `docs/impl_diff.md` —
|
||
сейчас там нет ни одного из найденных, хотя правило проекта требует.
|
||
|
||
|
||
Порядок по ожидаемой отдаче:
|
||
|
||
1. **Бой** — ПРОЙДЕН. Находки: 12, 13 (ранг А), 18 (Б); совпадают
|
||
`control_with_sword`, `parry`, `swordfight`, `sword_strike`,
|
||
`check_sword_hurt`, `check_hurting` (кроме звука). Не сверены мелочи:
|
||
`back_with_sword`, `forward_with_sword`, `check_skel`.
|
||
2. **`play_seq` и опкоды** — самая опасная область: ошибка в одном опкоде
|
||
меняет все последовательности разом.
|
||
3. **Отрисовка** — `add_kid_to_objtable`/`add_guard_to_objtable`, порядок
|
||
слоёв, `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 (ранг А) — смерть на краю. ТОЛЬКО ВМЕСТЕ, НЕ ПООТДЕЛЬНОСТИ
|
||
|
||
> **Проверено на живой машине 2026-08-31 и провалилось.** Правка 13 была
|
||
> сделана в изоляции (прижатие к полу перенесено в ветки пережитого
|
||
> удара) — и сломала смерть: страж бьёт Кида, тот погибает, а вместо
|
||
> нормальной смерти идут вспышка, стопкадр и немедленный выход в
|
||
> заставку.
|
||
>
|
||
> Причина: у нас прижатие к полу работало КОМПЕНСАЦИЕЙ отсутствующей
|
||
> ветки «убит и сброшен с уступа» (находка 12). Убрав компенсацию и не
|
||
> добавив то, что она компенсировала, мы оставляем мёртвого персонажа с
|
||
> ненулевой `fall_y` и незакреплённой `Char.y` — физика продолжает вести
|
||
> его вниз, он проваливается за пределы уровня, и срабатывает аварийный
|
||
> путь.
|
||
>
|
||
> Вывод: обе находки — ОДНА правка. Оценка «чистое перемещение строк,
|
||
> риск низкий» была неверной; риск ВЫСОКИЙ, пока ветка 12 отсутствует.
|
||
> Порядок внутри правки: сперва добавить ветку `seq_81` (с экспортом
|
||
> тайловых запросов), убедиться, что смерть на краю отыгрывается ею, и
|
||
> только затем убирать безусловное прижатие.
|
||
|
||
|
||
*Место:* `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 | 24 | одна строка, риск низкий, готовая экспортированная функция |
|
||
| 2 | 12+13 ВМЕСТЕ | порознь ломают смерть (проверено); нужен экспорт двух функций |
|
||
| 3 | 18, 8 | обе УСКОРЯЮТ или бесплатны; 8 — по дешёвому варианту |
|
||
| 4 | 2, 3, 20 | перестановки и копия таблицы; нужны прогоны |
|
||
| 5 | 1, 7 | риск средний: обе могут компенсировать наши отличия в другом месте |
|
||
|
||
**Ни один фикс не требует переделки архитектуры и ни один не ложится на
|
||
горячий путь с накладными расходами** — при условии, что находка 20
|
||
делается копией таблицы, а находка 8 — битовой маской рядов.
|