Недавняя дискуссия об опасности дверей в геймдеве навеяла воспоминания о баге с дверью из знаменитой Half-Life 2. Приготовьтесь к истории: она захватывает не меньше, чем сюжет игры.

В 2013 году, когда только появился Oculus DK1, я с Джо Людвигом из Valve решили проверить возможности VR на реальной игре. Взяв за основу Team Fortress 2 (история выбора заслуживает отдельного рассказа), мы заметили, что движок Source 1 позволяет оживить и другие проекты Valve — Half-Life 2 и Portal 1 — в виртуальной реальности.
Portal 1 хоть и запускалась, но из-за трюков с перспективой порталов плохо вызывала укачивание. А вот Half-Life 2 в VR пошла почти без нареканий. Джо уделил особое внимание уровням с лодкой, добившись плавного ощущения погружения.
Сборка ящиков в начале игры, обычно превращавшаяся в хаос на плоском экране, в VR получалась едва ли не медитативной: коробки аккуратно укладывались друг на друга. И битвы с мэнхэками, где на мониторе вымахиваешь монтировкой, в виртуальной реальности превращались в изящные, точные удары.

Когда настало время готовить релиз, мы включили поддержку VR в командную строку HL2, объявили экспериментальную бету и пересобрали всю игру. Долго не проходили её целиком — лишь тестировали ключевые главы. Я же решил перепройти от начала до конца, чтобы зафиксировать все возможные проблемы в примечаниях к выпуску.
Но на самой первой локации я упёрся в загвоздку: в коридоре охранник требовал поднять банку и велел зайти в комнату — а дверь отказывалась открываться. Я застрял между стен и скриптовых событий. Судя по роликам в сети, дверь должна была распахнуться сама и впустить меня внутрь.
https://embedd.srv.habr.com/iframe/692af4bebef06f642020e2c5" width="560" height="315" loading="lazy
Однако дверь лишь шумела, открываясь на секунду и тут же закрываясь — а за ней нависали массивные ворота, блокирующие обратный путь.

Мы собрали команду, в том числе авторов оригинального HL2, и выяснили, что баг существует даже вне VR. Скомпилировали исходники, идентичные тем, что вышли в 2004 году — и баг продолжал проявляться. Казалось, он проник в прошлое и заразил оригинал.
День ушёл на знакомство с отладчиком, и наконец кто-то заметил: при открытии двери второй охранник стоит слишком близко к её траектории. Столкнувшись с ботинком, дверь отскакивает, закрывается и больше не пытается открыться, а скрипт застревает в ожидании игрока.
Решение оказалось тривиальным: сдвинуть NPC на миллиметр назад. Но чтобы до этого дойти, пришлось заново разбираться в инструментах отладки.

Оставался вопрос: почему баг не вылезал в оригинале? Ответ скрывался в вычислениях с плавающей точкой. В 2004 году компилировали под x87 с нестабильной точностью 80 бит; спустя годы компиляторы перешли на SSE — точность 32/64 бита стала жёстко определённой. В обеих системах дверь касалась ботинка охранника, но в x87 тончайшее отклонение избавляло от столкновения, а в SSE сдвиг был недостаточен.
Таким образом, из-за небольших различий в погрешностях движущихся объектов Скрипт двери пытался открыться лишь единожды и застревал навсегда, если столкновение не решалось автоматически.



