- In einer frühen Szene von Half-Life 2 wurde ein seltsamer Bug entdeckt, bei dem sich eine Tür nicht öffnet und der Fortschritt stehen bleibt
- Die Ursache ist, dass beim Öffnen der Tür die Zehenspitze des im Türbereich stehenden Wächters mit dem Türpfad kollidiert, wodurch sich die Tür wieder schließt und verriegelt
- Derselbe Konflikt existierte auch im Originalcode, doch das Ergebnis fiel wegen des Präzisionsunterschieds zwischen den x87-Gleitkommaberechnungen von 2004 und den SSE-Berechnungen von 2013 anders aus
- In der x87-Umgebung wird der Fuß durch eine minimale Rotation leicht weggeschoben, sodass die Kollision aufgelöst wird, während bei SSE die Rotation nicht ausreicht und die Tür sich schließt
- Dieser Fall ist ein typisches Beispiel dafür, wie sich Gleitkommapräzision und Compiler-Unterschiede auf das tatsächliche Verhalten eines Spiels auswirken
Ein Bug, entdeckt beim VR-Port von Half-Life 2
- Bei einem Experiment von Valve im Jahr 2013, Team Fortress 2 auf VR zu portieren, liefen auch Half-Life 2 und Portal 1, die dieselbe Engine verwenden, in VR
- Portal 1 verursachte durch die Perspektivverzerrung so starke Übelkeit, dass es in VR praktisch unspielbar war
- Half-Life 2 funktionierte vergleichsweise gut, und Szenen wie die Bootspassage, das Stapeln von Kisten und Kämpfe mit Manhacks profitierten von der für VR typischen Immersion
- Während der Tests spielte ein Entwickler das Spiel von Anfang bis Ende in VR durch und entdeckte gleich in der frühen Bahnhofsszene einen Zustand, in dem es nicht weitergeht
Analyse der Ursache, warum sich die Tür nicht öffnet
- Im ursprünglichen Szenario klopft der Wächter (eigentlich Barney) an die Tür und sagt „Geh rein“, danach geht es weiter zum nächsten Skript, sobald der Spieler den Raum betritt
- Im Bug-Fall ruckelt die Tür jedoch, verriegelt sich und schließt sich vollständig, während der Wächter weiter auf die Tür zeigt und der Spieler feststeckt
- Selbst beim erneuten Bauen des ursprünglichen Half-Life 2-Quellcodes trat dasselbe Problem auf, was für Verwirrung sorgte, weil es wie ein rückwirkend entstandener Bug wirkte
Die eigentliche Ursache: Veränderungen bei der Gleitkommaberechnung
- Zum Release 2004 verwendete Half-Life 2 den x87-Befehlssatz mit einer Struktur, in der sich 32-, 64- und 80-Bit-Präzision mischten
- In Builds ab 2013 wurde standardmäßig der SSE-Befehlssatz verwendet, wodurch die Rechengenauigkeit klar auf 32 oder 64 Bit begrenzt war
- In beiden Umgebungen kollidieren die Tür und die Zehenspitze des Wächters, aber durch feine Unterschiede in den Berechnungen der Physik-Engine fällt das Ergebnis anders aus
- Unter x87 dreht sich der Wächter bei der Kollision ganz leicht, sodass der Fuß aus dem Türbereich herauskommt und die Tür aufgeht
- Unter SSE ist die Rotation minimal geringer, sodass der Fuß weiterhin anliegt und die Tür sich wieder schließt und verriegelt
Behebung und Lösung
- Nachdem das Problem verstanden war, ließ es sich mit einer einfachen Änderung beheben, indem die Position des Wächters um etwa 1 mm nach hinten verschoben wurde
- Das Debugging nahm viel Zeit in Anspruch, unter anderem weil erst der Umgang mit alten Tools wieder aufgefrischt werden musste
- Dieser Fall zeigt, dass Präzisionsunterschiede und geänderte Compiler-Einstellungen das Verhalten alter Codebasen verändern können
Reaktionen aus der Community
- Entwickler zeigten Verständnis und meinten, „am Ende ist immer die Gleitkommapräzision schuld“
- Einige verwiesen auf ähnliche Fälle bei veränderten Kollisionsberechnungen in Remakes von Sonic 1, 2 und 3
- Mit der Lehre „Der Code ist derselbe, aber der Compiler ist ein anderer“ wurde der Fall als Beispiel für die Schwierigkeit der Pflege von Legacy-Code bewertet
- Andere Entwickler brachten das mit dem Grund in Verbindung, warum in Spielen wie Fallout 4 NPCs so entworfen wurden, dass sie nicht durch Türen gehen
- Insgesamt überwog die Reaktion, dies sei „eine der interessantesten Bug-Geschichten überhaupt“
1 Kommentare
Hacker-News-Kommentare
Ich erinnere mich, dass mir früher in der Spieleentwicklung auf bestimmten PCs Assertion-Fehler nur wegen FPU-Berechnungsfehlern begegnet sind
Die Ursache war, dass eine Software für Handschrifteingabe DLLs in alle Prozesse injizierte und dabei den FPU-Modus auf den Standardwert zurücksetzte
Am Ende haben wir den Code für die FPU-Konfiguration aus der Initialisierungsphase in die Event-Loop verschoben, um den Einfluss von Third-Party-DLLs zu umgehen
Eines meiner Ziele ist, Valve zur Nutzung von Nix zu bringen
Mit der laufenden Unterstützung für Windows dürfte das noch attraktiver wirken
Damit ließen sich nicht nur die Originalquellen, sondern auch die damalige Toolchain und die Abhängigkeiten vollständig reproduzieren, was die Suche nach der eigentlichen Ursache solcher Bugs deutlich erleichtern würde
Das erinnert mich an das Meme „DOOR STUCK“
Passendes Video
Ich frage mich, ob die „Half-Life 2 VR Beta“ tatsächlich spielbar ist
Falls ja, frage ich mich, warum ich nichts davon wusste, und falls nein, warum das noch nicht der Fall ist
Portal VR würde ich auch unbedingt gern ausprobieren. Viele sagen, dass einem davon stark übel wird, aber ich bin gegen VR-Übelkeit immun, also wäre es einen Versuch wert
Sie ist so gut umgesetzt, dass ich HL2 deshalb nach langer Zeit wieder gespielt habe
Beim Wechsel von x87 zu SSE gehen manche Berechnungen oft kaputt
Dasselbe ist auch in TF2 passiert: Im Linux-Build wurde SSE verwendet, wodurch die Munitionsberechnung leicht anders ausfiel
Eine kleine Kiste gab +40 oder +41, je nach Server-OS
Es machte Spaß, beim Verbinden mit einem neuen Server herauszufinden, welches OS dort lief
Wenn schon der bloße Wechsel von x87 zu SSE ein Spiel kaputtmachen kann, ist es erstaunlich, wie gut x86→ARM-Übersetzungen funktionieren
Sie verwendet 80-Bit-Register und bietet dadurch höhere Präzision, zeigt aber auch seltsames Verhalten, etwa Präzisionsverlust beim Spill in den Speicher
Ich hatte einmal einen ähnlichen Präzisions-Bug, als ich einen Software-Synthesizer debuggt habe
Eine RC-Schaltungssimulation sollte den Zustand wechseln, wenn sie sich 0 annäherte, aber auf bestimmten CPUs wurden Denormal-Werte nicht auf 0 geflusht, sodass die Bedingung nie erfüllt wurde
Am Ende haben wir den Schwellwert grob auf etwa 0,7 und 0,01 gesetzt, und dann funktionierte es auf allen Plattformen gut
Als Meta-Kommentar: Ich würde einen Twitter-Klon bauen und eine Funktion einführen, die Leute sofort bannt, wenn sie in mehreren Absätzen bloggen