今天看到阮一峰大佬分享的一个最离奇的软件 Bug,看完后我大呼精彩,忍不住分享给大家,以下是第一人称的叙述:
🚪 我与《半条命 2》那扇该死的门
最近大家都在讨论游戏开发中“门”带来的各种灾难,这让我想起了我亲身经历的一个 Bug,它出现在一个你们可能听说过的游戏里——《半条命 2》。各位坐好了吗?那我就开始讲了。
💻 缘起:VR 移植的意外惊喜
那是 2013 年,我在 Valve 公司从事游戏开发,当时,第一代虚拟现实 VR 头盔 Oculus DK1 刚刚发售,我在 Valve 工作,和 Joe Ludwig 琢磨着怎么把 VR 这玩意儿用在一个真正的游戏里。我们挑了《军团要塞 2》来做实验,但因为引擎的关系,《半条命 2》(HL2)也顺道被我们移植了过来。

出乎意料的是,HL2 在 VR 里的体验棒极了!开快艇、用撬棍打飞天锯子(Manhack),都从原来的手忙脚乱变成了优雅的“一击本垒打”。我们觉得这可以发货了,于是把 VR 支持藏在了命令行里,标注为“Beta”,准备随一个 Source 引擎的更新一起推出。

🤯 撞鬼:开场即卡死
移植了一个片段后,我们发现实际效果很好,就决定移植整个游戏,并且发布了发售预告。
移植过程中,我试玩了很多片段,但没有从头到尾玩一次。
等到移植完成,就在发售前夕,我决定完整玩一次,如果发现有什么问题,就写在发布说明里面。
我心想,应该不会有大问题,毕竟这个游戏已经发售10年了,无数人玩过,反响良好。
但是,万万没有想到,我居然遇到了一个重大 Bug。

游戏的开头部分,玩家来到火车站,一个守卫让你进去一个房间。很奇怪,房间的门是关着的,你进不去,就…卡住了。
你没死,就是哪儿也去不了。前面的门关着,你进不去,也退不出去,身后的大门已经关上了。你被困在一个走廊里,旁边有个守卫,无路可走。真是奇怪。
游戏的剧情是,你必须进入这个房间,才能往下玩。你又去找守卫,他指着锁着的门,仅此而已,你被困住了。
我上网查了视频,心想自己是不是记错了。没错,门应该是自动打开的,你走进去就行了,但是…现在这扇门却关了!
然后,我就卡住了。
门“嘎吱”了一下,但纹丝不动,然后又自动锁上了。卫兵就这么指着锁死的门,永远等着我。我哪儿也去不了,彻底被困在了 HL2 的第一分钟。这太荒谬了!

🔍 追溯:穿越时空的 Bug
我赶忙叫上其他人,包括一些十年前参与这个游戏开发的人。他们测试后,都说确实有问题,而且在非 VR 模式下也一样,门也是关着的,所以肯定不是我移植弄坏的。但没人知道原因,因为代码根本没改过。
更疯狂的是,我们尝试回溯到 HL2 2004 年发售的原始版本,重新编译运行,结果发现,那个原始版本也坏了,门也是关着的。——Bug 竟然也在!
这简直是撞鬼了!一个 Bug 居然可以穿越时空,感染了它的原始版本?如果它一直都在,那当年数百万玩家是怎么玩到后面的?
🧑🔬 谜底:浮点数的毫厘之差
经过一天一夜的调试,重新使用当年的调试和回放工具之后,一位聪明的同事终于找到了问题:
那个卫兵! 门内站着一个守卫,他站得离门太近了,他的脚尖(更准确地说是他的碰撞体边缘)轻微地挡住了门的开启路径。当门开始打开时,它撞到了卫兵的脚,反弹回去,然后自动关闭锁死。

但这只解释了 Bug 的原因,没有解释它为什么现在出现。
一旦弄明白怎么回事,解决方法就很简单。我们把守卫往后移大约一毫米,门就很顺利自动打开了。
现在我们可以发布游戏了。但是,问题还是没有彻底解决。为什么这个游戏当初没有出现这个 Bug,原版里守卫的脚趾也挡着门啊?为什么十年后重新编译,Bug 就出现呢?或者说,Bug 其实一直都在,为什么十年前这扇门没有关上呢?
于是,一场旷日持久的漏洞搜寻就此展开。
我们终于发现了答案,关键就在于浮点数精度。
-
2004 年的 《半条命2》:当时我们使用的是 x87 数学指令集,它有一种“野路子”的浮点运算,精度是混杂的(32、64、甚至 80 位)。在这种计算下,门撞到卫兵脚时产生的物理冲量,恰好足以克服卫兵鞋底的摩擦力,让卫兵往旁边微转了一点点。这一点点位移,就让他的脚尖脱离了门的路径,门顺利打开。一个 Bug 救了另一个 Bug。
-
2013 年的新编译:十年后,编译器默认使用的是更标准的 SSE 指令集(标准的 32 或 64 位)。在 SSE 更加“可预测”的计算下,虽然门还是撞了卫兵,但最终算出来的摩擦力、动量和旋转角度,让卫兵微转的角度少了一点点。
-
真相就是:十年前编译用了32位精度,现在用了64位,小数点的差异造成了几毫米的误差,让守卫的脚趾碰到了门。
这毫厘之差,就是生与死的区别。 那一点点没挪开的脚尖,成了游戏世界里不可逾越的障碍。
好了,现在玩家终于可以走进大门,继续玩下去了。

🛠️ 结局:一毫米的胜利
我们费了九牛二虎之力才发现这个深藏在编译器和 CPU 指令集里的“幽灵”。
最终的解决方案?简单到令人心碎:我们把那个卫兵的位置往后挪了大约一毫米。
游戏恢复正常,我们松了一口气。但这件事让我永远记住了:即使代码一行没动,编译器和硬件环境的改变,也能让当年一个被好运气和 80 位精度“保护”起来的 Bug,突然在十年后跑出来,把玩家锁在游戏的开场。
这就是我对“游戏开发中的门”的恐怖回忆。

被折叠的 条评论
为什么被折叠?



