# Два поведения: VANILLA и BUGFIXED > Заведено 2026-08-31. Задача поставлена, работа НЕ начата. ## Зачем У оригинального PoP есть баги, которые игроки знают наизусть и на которых построены известные трюки. SDLPoP чинит их не молча, а ОПЦИОНАЛЬНО: каждое исправление отдельным переключателем, по умолчанию часть включена, часть нет. Мы до сих пор повторяли ваниль — сознательно, чтобы порт вёл себя как оригинал. Задача: дать ДВА поведения на выбор, не размазывая условия по всему коду: * **VANILLA** — как в оригинале 1989 года, со всеми его багами; * **BUGFIXED** — с портированными исправлениями SDLPoP. ## Что уже известно (из аудита) Разбор расхождений — `docs/sdlpop_audit.md`. Оттуда прямо в эту задачу переезжает следующее. ### Опциональные фиксы SDLPoP, которых у нас НЕТ Все три относятся к прохождению сквозь препятствия и живут в `do_fall` (seg005) и рядом: | фикс SDLPoP | что чинит | |---|---| | «скольжение сквозь стену» | Кид падает сквозь стены после разворота в беге, особенно под зельем медленного падения | | «прыжок сквозь стену над воротами» | пролёт в тайл над воротами | | «проваливание сквозь гобелен» | падение сквозь тайл гобелена | Ни один не портирован — это и есть наше нынешнее VANILLA. ### Находка 24 — отложена сюда `in_wall` у нас пересчитывает только колонку, а оригинал перезагружает ещё и данные кадра (`load_fram_det_col`, seg006). Правка сделана и ОТКАЧЕНА 2026-08-31 по такой причине: * по букве оригинала находка верна; * но практического эффекта показать НЕ УДАЛОСЬ — все 15 наборов host-тестов дают одинаковый результат до и после, включая специально написанный тест на заход в кладку; * при этом правка не бесплатна: добавляет маппинг окна и перезагрузку кадра на каждое выталкивание из стены. Платить за недоказанное не стали. Вернуться к ней имеет смысл именно здесь: при работе над BUGFIXED появится сценарий, где кадр меняется перед выталкиванием, и тогда эффект станет наблюдаемым. ### Готовый детектор поведения `tests/host/t_wall.c` расширен с одной проверки до трёх. Ключевая — `wall_stops_jump_from_left_side`: она НЕ требует нуля заходов в кладку, а сторожит их ЧИСЛО (сейчас ровно два случая из четырнадцати стартовых позиций). Это и есть переключатель ожиданий между режимами: * больше двух — правка сделала нас хуже ванили, регресс; * ровно два — ведём себя как оригинал (режим VANILLA); * меньше двух — кто-то портировал фикс; в режиме BUGFIXED тест должен ждать нуля. То есть когда появится BUGFIXED, этому тесту понадобится ожидание, зависящее от режима, — и он готов стать первым таким. ## Переключатель уже есть — второго не нужно Уточнено 2026-08-31: в настройках игры ПЕРЕКЛЮЧАТЕЛЬ VANILLA/ENHANCED СУЩЕСТВУЕТ (`docs/menu_settings_plan.md`), просто сейчас он жёстко зафиксирован в положении VANILLA. Отдельную сущность заводить не надо — эта задача про то, чтобы наполнить смыслом уже имеющееся положение ENHANCED. **Наш «ванильный» билд уже не чистая ваниль.** Часть ванильных багов у нас пофикшена по ходу портирования. Значит: * нельзя считать текущее поведение эталоном ванили — оно смешанное; * при разделении режимов придётся пройтись по уже сделанным фиксам и решить по каждому, остаётся он в VANILLA или уезжает в ENHANCED; * и наоборот: отдельные исправления (например, падение сквозь стену) вполне могут быть сделаны прямо в нынешнем «ванильном» билде, если сочтём, что играбельность важнее буквальности. ## Что предстоит решить 1. **Что считать ванилью на практике.** Составить список уже сделанных отступлений от оригинала и распределить их между режимами. 2. **Цена рантайм-проверки.** Условия попадают в физику и `play_seq`, то есть в горячий путь. Если переключать в рантайме, проверка должна быть дешевле самого фикса: флаг в резиденте, а не вызов через банк. 3. **Что считать умолчанием.** Оригинальное поведение честнее для порта, но часть фиксов SDLPoP включает по умолчанию. 4. **Как тестировать оба режима.** Host-тесты гоняются одним прогоном; для двух режимов нужен либо параметр сборки тестов, либо ожидания, зависящие от флага. ## Инвентаризация: что уже решено по каждому фиксу SDLPoP Составлено 2026-08-31 обходом кода. В движке эти решения УЖЕ приняты и задокументированы прямо в комментариях — таблица лишь сводит их в одно место, чтобы при разделении режимов не перечитывать исходники. | фикс SDLPoP | где у нас | что взято | |---|---|---| | `fix_feather_fall_affects_guards` | `pop_map.c:941` | **ФИКС** — перо действует только на Кида | | `fix_exit_door` | `pop_map.c:1212` | **ФИКС** — ветка фикса вместо ванильного глобала | | `FIX_GATE_SOUNDS` | `pop_trob.c:579` | **ФИКС** — условия через ИЛИ | | `fix_sound_priorities` | `pop_sfx.c:196` | **ФИКС** — в SDLPoP включён безусловно, сравниваемся с исправленным | | `FIX_STAND_ON_THIN_AIR` | `pop_map.c:1499` | **ВАНИЛЬ** — взяты 2 части из 3, третья ждёт патча `seqtbl` | | `fix_painless_fall_on_guard` | `pop_map.c:1611` | **ВАНИЛЬ** — намеренно | | `fix_jumping_over_guard` | `pop_map.c:1612` | **ВАНИЛЬ** — намеренно | | `FIX_RETREAT_WITHOUT_LEAVING_ROOM` | `pop_map.c:3036` | **ВАНИЛЬ** — в SDLPoP выключен по умолчанию; трюк 35 сохраняем | | `fix_skeleton_chomper_blood` | `pop_map.c:3177` | **ВАНИЛЬ** — кровь скелета в ванили есть | | потеря HP скелетом при падении с двух этажей | `pop_map.c:676` | **ВАНИЛЬ** — баг оригинала, сохраняем сознательно | ### Фиксы, которым нужна правка байткода `FIX_STAND_ON_THIN_AIR` не взят НЕ потому, что мы выбрали ваниль, а потому что его нельзя применить наполовину: он состоит из трёх частей, и третья — правка самого байткода `seqtbl` (смещения в последовательности вставания). Взяв только первые две, мы получим вставание, уносящее весовую точку в стену, то есть ХУЖЕ ванили. **Это выполнимо, и раньше здесь стояла неверная оценка** (уточнено 2026-08-31): байткод можно менять и у нас. Способов три: 1. **Патч в рантайме** — после загрузки `kid.ani` в EMM-страницу пропатчить нужные байты прямо там. Речь о единицах байт, страница уже наша, и патч обратим — то есть режим переключается без пересборки ресурсов. Это и делает фикс пригодным для VANILLA/ENHANCED. 2. **Патч в упаковщике** — готовить два варианта `kid.ani`. Привязывает режим к файлам на диске, поэтому хуже: переключатель в меню перестаёт быть чисто кодовым. 3. **Две копии в одной странице** — и это, пожалуй, лучший вариант (решено 2026-08-31). `kid.ani` целиком около 4 КБ, а страница EMM — 16 КБ, то есть обе версии байткода спокойно помещаются рядом в уже выделенной странице. Переключение режима сводится к смене базового смещения, патчить ничего не нужно, откат мгновенный. Для сравнения: у SDLPoP рабочая таблица и неизменная копия оригинала существуют раздельно (`seqtbl` и `original_seqtbl`), причём вторая нужна для сверки — то есть сама идея «оригинальный байткод отдельно, рабочий отдельно» там уже заложена. Общее правило для BUGFIXED: фиксы, требующие правки `seqtbl`, доступны через рантайм-патч страницы; закладывать это стоит сразу, чтобы не упереться при первом же таком фиксе. ### Вывод для разделения режимов Четыре фикса уже взяты, шесть позиций оставлены ванильными. Значит нынешний билд — это не VANILLA, а «ваниль плюс четыре исправления». При разделении: * взятые четыре надо либо оставить в обоих режимах (если считаем их безусловными улучшениями), либо увести в ENHANCED и вернуть ванильное поведение в VANILLA — второе честнее, но потребует обратной работы; * ванильные шесть — кандидаты в ENHANCED; `FIX_STAND_ON_THIN_AIR` тоже, но ему дополнительно нужен рантайм-патч байткода. ## ВСЕ исправления SDLPoP и их статус у нас Полный перечень опциональных исправлений оригинала, какие есть в SDLPoP (43 позиции), со статусом в нашем порте. Названия — идентификаторы опций SDLPoP, описание — своими словами. Статусы: **ВЗЯТ** — портирован; **ВАНИЛЬ** — сознательно не берём, держим поведение оригинала; **НЕТ** — не реализован, кандидат в ENHANCED; **НЕДОСТУПЕН** — требует правки байткода `seqtbl` (см. ограничение выше); **В РАБОТЕ** — решено делать сейчас. ### Стены и препятствия | опция | что чинит | статус | |---|---|---| | `fix_glide_through_wall` | проход сквозь стену при падении после разворота в беге | **ВЗЯТ** 2026-08-31 — `glide_through_wall_guard()` в `pop_map.c`, точка отвязки для VANILLA | | `fix_jump_through_wall_above_gate` | прыжок в тайл над воротами | НЕТ | | `fix_drop_through_tapestry` | проваливание сквозь гобелен | НЕТ | | `fix_running_jump_through_tapestry` | прыжок с разбега сквозь гобелен | НЕТ | | `fix_turn_running_near_wall` | разворот в беге вплотную к стене | НЕТ | | `fix_wall_bump_triggers_tile_below` | удар о стену срабатывает на тайл ниже | НЕТ | | `fix_bigpillar_climb` | подъём на большую колонну | НЕТ | | `fix_land_against_gate_or_tapestry` | приземление вплотную к воротам или гобелену | НЕТ | | `fix_caped_prince_sliding_through_gate` | проскальзывание сквозь ворота | НЕТ | ### Падение, прыжки, зацепы | опция | что чинит | статус | |---|---|---| | `fix_stand_on_thin_air` | стояние на воздухе после отмены падения | НЕТ — нужен рантайм-патч `seqtbl` (см. выше) | | `fix_jump_distance_at_edge` | дальность прыжка у самой кромки | НЕТ | | `fix_edge_distance_check_when_climbing` | проверка расстояния до кромки при подъёме | НЕТ | | `fix_grab_falling_speed` | зацеп на слишком большой скорости падения | НЕТ | | `fix_drop_2_rooms_climbing_loose_tile` | провал через две комнаты при подъёме на шаткой плите | НЕТ | | `fix_infinite_down_bug` | бесконечное падение вниз | НЕТ | | `fix_falling_through_floor_during_sword_strike` | провал сквозь пол во время удара мечом | НЕТ | | `fix_safe_landing_on_spikes` | безопасное приземление на невыдвинутые пики | НЕТ | | `fix_dead_floating_in_air` | мёртвый зависает в воздухе | НЕТ | ### Бой и стражи | опция | что чинит | статус | |---|---|---| | `fix_painless_fall_on_guard` | падение на стража с высоты без урона | **ВАНИЛЬ** | | `fix_jumping_over_guard` | перепрыгивание через стража | **ВАНИЛЬ** | | `fix_skeleton_chomper_blood` | кровь скелета в челюстях | **ВАНИЛЬ** | | `fix_push_guard_into_wall` | вталкивание стража в стену | НЕТ | | `fix_guard_following_through_closed_gates` | страж идёт сквозь закрытые ворота | НЕТ | | `fix_doortop_disabling_guard` | верх двери отключает стража | НЕТ | | `fix_offscreen_guards_disappearing` | стражи пропадают за краем экрана | НЕТ | | `fix_unintended_sword_strike` | непреднамеренный удар мечом | НЕТ | | `fix_two_coll_bug` | двойная проверка столкновения | НЕТ | | `fix_move_after_sheathe` | движение сразу после убирания меча | НЕТ | ### Ворота, двери, плиты | опция | что чинит | статус | |---|---|---| | `fix_exit_door` | дверь выхода с уровня | **ВЗЯТ** | | `fix_gate_sounds` | звуки ворот | **ВЗЯТ** | | `fix_gate_drawing_bug` | отрисовка ворот | НЕТ | | `fix_press_through_closed_gates` | нажатие плиты сквозь закрытые ворота | НЕТ | | `fix_chompers_not_starting` | челюсти не заводятся | НЕТ | | `fix_loose_left_of_potion` | шаткая плита слева от зелья | НЕТ | | `fix_hidden_floors_during_flashing` | скрытые полы во время вспышки | НЕТ | | `fix_retreat_without_leaving_room` | отступление без смены комнаты (трюк 35) | **ВАНИЛЬ** | ### Зелья, перо, спецэффекты | опция | что чинит | статус | |---|---|---| | `fix_feather_fall_affects_guards` | перо действует и на стражей | **ВЗЯТ** | | `fix_feather_interrupted_by_leveldoor` | перо прерывается дверью уровня | НЕТ | | `fix_move_after_drink` | движение сразу после питья | НЕТ | | `fix_quicksave_during_feather` | быстрое сохранение под пером | НЕТ | | `fix_hang_on_teleport` | зависание при телепорте | НЕТ (телепортов у нас нет) | ### Интерфейс и ввод | опция | что чинит | статус | |---|---|---| | `fix_one_hp_stops_blinking` | индикатор перестаёт мигать на одном HP | НЕТ | | `fix_register_quick_input` | учёт быстрого ввода | НЕТ | ### Отдельно: приоритеты звуков `fix_sound_priorities` в SDLPoP включён БЕЗУСЛОВНО (не опция), и мы сравниваемся с исправленным вариантом — `pop_sfx.c:196`. Статус: **ВЗЯТ**, вернуть ванильное поведение отдельным режимом было бы дополнительной работой. ### Сводка | статус | сколько | |---|---:| | ВЗЯТ | 5 | | ВАНИЛЬ (сознательно) | 4 | | требует патча `seqtbl` (выполнимо) | 1 | | НЕТ (кандидаты в ENHANCED) | 32 | ## Список кандидатов на BUGFIXED Пополняется по мере аудита. Пока: * три опциональных фикса SDLPoP выше; * находка 24 (перезагрузка кадра в `in_wall`); * находка 21 из аудита — общая рамка «мы намеренно повторяем ваниль». Не относятся сюда находки, где мы расходимся с оригиналом НЕ в его пользу (ранги А и Б аудита): их надо чинить в обоих режимах, потому что это не баги оригинала, а наши.