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
276 lines
20 KiB
Markdown
276 lines
20 KiB
Markdown
# Два поведения: 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 из аудита — общая рамка «мы намеренно повторяем ваниль».
|
||
|
||
Не относятся сюда находки, где мы расходимся с оригиналом НЕ в его
|
||
пользу (ранги А и Б аудита): их надо чинить в обоих режимах, потому что
|
||
это не баги оригинала, а наши.
|