- Une étrange anomalie a été découverte dans la scène d’ouverture de Half-Life 2 : une porte ne s’ouvre pas, ce qui bloque la progression
- La cause vient du bout du pied du garde, placé à l’intérieur de la porte, qui entre en collision avec sa trajectoire lors de l’ouverture, ce qui la fait se refermer et se verrouiller
- La même collision existait déjà dans le code d’origine, mais le résultat diffère à cause de la différence de précision entre les calculs en virgule flottante x87 de 2004 et les calculs SSE de 2013
- En environnement x87, une rotation minime repousse légèrement le pied et résout la collision, alors qu’en SSE la rotation est insuffisante, ce qui fait que la porte se referme
- Ce cas est un exemple emblématique de l’impact que peuvent avoir la précision en virgule flottante et les différences de compilateur sur le comportement réel d’un jeu
Bug découvert pendant le portage VR de Half-Life 2
- En 2013, lors d’une expérimentation chez Valve pour porter Team Fortress 2 en VR, Half-Life 2 et Portal 1, qui utilisent le même moteur, ont eux aussi pu fonctionner en VR
- Portal 1 provoquait des vertiges au point d’en devenir injouable en VR à cause de la distorsion du point de vue
- Half-Life 2 fonctionnait plutôt bien, avec une immersion particulièrement renforcée dans les scènes en bateau, l’empilement de caisses et les combats contre les manhacks
- Pendant les tests, un développeur qui parcourait le jeu entier en VR a découvert un blocage de progression dans la scène du début à la gare
Analyse de la cause de la porte qui ne s’ouvre pas
- Dans le scénario normal, le garde — en réalité Barney — frappe à la porte, dit au joueur d’entrer, puis le script suivant se déclenche une fois le joueur dans la pièce
- Mais dans le cas du bug, la porte tressaute, se verrouille puis se referme complètement ; le garde continue de la désigner et le joueur reste piégé
- Recompiler le code source original de Half-Life 2 produisait le même problème, ce qui a semé la confusion en donnant l’impression d’un bug apparu en remontant le temps
Cause profonde : l’évolution du mode de calcul en virgule flottante
- Au moment de sa sortie en 2004, Half-Life 2 utilisait le jeu d’instructions mathématiques x87, avec une structure mêlant des précisions 32, 64 et 80 bits
- Dans les builds postérieurs à 2013, le jeu d’instructions SSE est utilisé par défaut, avec une précision de calcul clairement limitée à 32 ou 64 bits
- Dans les deux environnements, la porte et le bout du pied du garde entrent en collision, mais de minuscules différences dans les calculs du moteur physique changent le résultat
- En x87, le garde pivote très légèrement au moment de la collision, ce qui dégage son pied et permet à la porte de s’ouvrir
- En SSE, l’angle de rotation est un peu plus faible, le pied touche encore la porte, qui se referme alors et se verrouille
Correctif et résolution
- Une fois le problème identifié, il a été résolu par une correction simple : reculer la position du garde d’environ 1 mm
- Le débogage a toutefois pris beaucoup de temps, notamment parce qu’il a fallu se réapproprier l’usage d’anciens outils
- Ce cas montre que les écarts de précision et les changements dans les réglages du compilateur peuvent modifier le comportement d’un ancien code
Réaction de la communauté
- Des développeurs ont réagi en disant qu’« au final, le problème vient toujours de la précision en virgule flottante »
- Certains ont évoqué des cas similaires, comme les changements dans le calcul des collisions dans les remakes de Sonic 1, 2 et 3
- Avec la leçon « le code est le même, mais le compilateur est différent », l’affaire est perçue comme un rappel des difficultés de maintenance du code legacy
- D’autres développeurs ont aussi relié ce cas à la raison pour laquelle, dans Fallout 4 par exemple, les PNJ sont conçus pour ne pas traverser certaines portes
- Dans l’ensemble, beaucoup y ont vu « l’une des histoires de bug les plus intéressantes »
1 commentaires
Commentaires sur Hacker News
À l’époque où je faisais du développement de jeux, je me souviens d’un échec d’assertion qui ne se produisait que sur certains PC à cause d’une erreur de calcul FPU
La cause était un logiciel de saisie manuscrite qui injectait une DLL dans tous les processus et réinitialisait le mode FPU à sa valeur par défaut
Au final, j’ai déplacé le code de configuration du FPU de l’étape d’initialisation vers la boucle d’événements, ce qui a permis d’éviter l’impact de DLL tierces
L’un de mes objectifs est de faire en sorte que Valve utilise Nix
Avec la prise en charge de Windows en cours, je pense que cela deviendra encore plus attractif
Cela permettrait de reproduire entièrement non seulement le code source d’origine, mais aussi la toolchain et les dépendances de l’époque, ce qui rendrait bien plus facile l’identification de la cause profonde de bugs comme celui-ci
Ça me rappelle le mème « DOOR STUCK »
Vidéo associée
Je me demande si la « bêta Half-Life 2 VR » est réellement jouable
Si oui, je me demande pourquoi je n’en avais jamais entendu parler, et sinon pourquoi ce n’est toujours pas disponible
J’aimerais aussi vraiment essayer Portal VR. On dit souvent que ça donne énormément la nausée, mais je suis immunisé contre le mal des transports en VR, donc ça vaudrait le coup de tenter
Il est suffisamment abouti pour m’avoir donné envie de refaire HL2 après tout ce temps
Il est courant que certains calculs cassent lors du passage de x87 à SSE
La même chose est arrivée dans TF2 : la build Linux utilisait SSE, ce qui rendait le calcul des munitions légèrement différent
Les petites boîtes donnaient +40 ou +41 selon le système d’exploitation du serveur
C’était amusant d’essayer de deviner quel OS était utilisé à chaque connexion à un nouveau serveur
Je trouve fascinant qu’un simple passage de x87 à SSE puisse casser un jeu, alors que la traduction x86→ARM fonctionne si bien
Il utilise des registres 80 bits, donc avec une précision plus élevée, mais adopte aussi des comportements étranges, comme la perte de précision lors d’un spill en mémoire
J’ai déjà rencontré un bug de précision similaire en déboguant un synthétiseur logiciel
Une simulation de circuit RC devait changer d’état lorsqu’elle s’approchait de 0, mais sur certains CPU, les valeurs denormal n’étaient pas flushées, donc la condition n’était jamais remplie
J’ai fini par régler le seuil de manière approximative à 0.7 et 0.01, et tout a bien fonctionné sur toutes les plateformes
En méta, je vais créer un clone de Twitter avec une fonctionnalité qui bannit immédiatement quiconque publie un billet en plusieurs paragraphes