第 5 章
第5章 卡马克的虚空(约 1992 年)
当楼下的庆祝声传来时,约翰·卡马克坐在房间里,开始输入关于多边形裁剪算法的下一行代码。键盘的敲击声稳定、均匀,像一台精密仪器在黑暗中独自运转。显示器发出的冷白色光打在他脸上,将那些从《德军总部3D》的代码库中继承下来的、已经不再够用的假设,一行一行地抹去。墙壁只能以九十度角排列,天花板高度必须恒定,光照衰减是线性的——这些在光线投射引擎中被硬编码为真理的限制,此刻正在他的屏幕上被一个更抽象的数据结构取代。他正在定义的是一个多边形的边可以以任意斜率存在的世界,一个天花板可以倾斜、地板可以起伏、墙壁可以围成任何角度的空间,而楼下传来的笑声正在穿透地板,罗梅罗的笑声,那种带着穿透力的、表演性的、仿佛要将整栋房子都变成舞台的声音,它在宣告《德军总部3D》的版税支票已经到账,宣告id Software正在征服整个共享软件市场,宣告1992年5月的这个深夜属于那些愿意庆祝胜利的人。卡马克没有抬头。
他正在处理一个关于视锥体裁剪边界条件的逻辑错误,这个错误会在某些角度下导致一堵本应可见的墙突然消失,而解决它需要重新组织整个多边形裁剪管线的执行顺序,而不是下楼去喝一杯啤酒。这不是一个突然降临的分叉。它在《德军总部3D》开发的最后几周就已经开始萌发,当卡马克在测试中反复跑过那些正交排列的走廊,看着那些被限制在九十度角内的墙壁,意识到这些限制不是硬件强加的——386处理器可以在理论上渲染任意角度的墙壁,真正限制它的是他六个月前写下的那个光线投射算法,那个曾经让整个团队惊叹的技术突破,现在变成了一个需要被拆除的脚手架。罗梅罗看到的是地图编辑器里可以无穷排列的纳粹旗帜和希特勒肖像,是玩家肾上腺素飙升时按下的射击键,是媒体采访中那些让他可以讲述自己从街机少年到明星制作人上升弧线的叙事素材。卡马克看到的是一面墙,它只能以直角存在,而现实世界中的墙不是这样的。
现实世界中的墙可以是任何角度,现实世界中的地板可以倾斜,现实世界中的光照可以从一个角落蔓延到另一个角落,在这个过程中衰减、变色、在粗糙的表面上留下微小的阴影颗粒,而他的引擎,那个让id Software从密西西比河边的车库里搬进什里夫波特正规办公室的引擎,一个也做不到。光标在代码编辑器中闪烁。卡马克输入了那行关于多边形裁剪的代码,然后停了下来,盯着屏幕上那个正在成形的数据结构。他正在构建的不仅是一个渲染系统,而是一个关于空间的声明。在接下来的几个月里,这个声明将会膨胀、分叉、自我复制,最终变成一台机器,那台机器将定义id Software未来五年的技术霸权,也将把卡马克与他的合作者之间最后一点不需要通过屏幕共享就能完成的交流,研成粉末。隔离是逐步实现的,但它遵循着一种近乎数学必然性的推进方式。首先,卡马克需要更长的连续时间块来处理那些不能在团队讨论的碎片化时段中解决的算法问题。
这种对现实世界物理属性的抽象渴望,并非始于《德军总部3D》。早在1990年9月,在路易斯安那州什里夫波特的Softdisk公司,当卡马克借助迈克尔·亚伯拉什的“强力图形程序”副本,开创了切片适配更新技术,让个人电脑也能实现多方向流畅滚动时,他就已经开始了这种实践。他与汤姆·霍尔连夜制作的《危险的戴夫之侵犯版权》演示,将《超级马力欧兄弟3》的玩法搬上电脑,其核心正是对硬件限制的突破和对新空间逻辑的构建。罗梅罗当时就认为卡马克的代码成就巨大,并敏锐地察觉到这种潜力在Softdisk公司纯属“浪费”。
BSP树的分割平面选择、光照衰减函数的参数调校、网络数据包的重传超时阈值——这些问题的上下文如此庞大,以至于每次被打断,他都需要十五分钟才能将整个问题重新加载到工作记忆中,而如果一天被打断四次,那就是一个小时的计算能力被浪费在上下文切换上。这个计算是精确的,不可辩驳的,而一旦他完成了这个计算,减少打断就不再是偏好问题,而是效率命令。然后,他开始避开白天的办公时间。办公室的日光灯颜色不对,Windows的反射角度会随着太阳移动而变化,这些无法被精确控制的变量干扰了他对屏幕上像素亮度的判断,而像素亮度是光照模型的核心输出。于是他在离开办公室后继续工作,起初是几个小时,然后延长到整个晚上,最后他的作息彻底倒转:下午四点起床,晚上八点开始工作,一直持续到第二天清晨,当什里夫波特的天空开始泛白时,他拉上窗帘,在黑暗中入睡。最后,他搬到了那栋独立的房子。
不是因为任何关于孤独的抽象渴望,而是因为共享空间意味着共享时间表,共享时间表意味着妥协,妥协意味着系统效率的衰减,而系统效率的衰减在逻辑上等价于一个错误。在id Software的办公室里,汤姆·霍尔试图用一种更像外交辞令的方式表达团队的担忧——卡马克不再出现在日常讨论中,不再参与那些决定游戏应该长什么样的会议,不再与同事一起吃午饭。卡马克的回应不是解释,而是行动:他将自己的代码更新推送到共享服务器上,并在一个名为.plan的文件中记录了每一次变更的技术理由。他不再解释这些事情,他只是在做这些事情,而当他不再解释的时候,他也就不再需要解释。
解释是一种为了降低他人理解成本而进行的计算资源支出,而如果他人不能直接从代码中读取意图,那说明他人不具备理解这个系统所需的基础知识,而这恰恰证明了面对面讨论的低效——你将时间花在将已经编码进逻辑树的信息翻译成自然语言,而自然语言是一种精度极低的传输协议,它丢失信息,引入歧义,产生需要进一步解释的误解,而每一次解释都产生更多的上下文切换,更多的效率损失,更多的错误。.plan文件中的条目简洁到近乎冷漠。关于二元空间分割的实现,日志只记录了技术事实:BSP树的递归划分完成了,每个节点存储一个分割平面,前向子树包含位于平面前方的几何体,后向子树包含位于平面后方的几何体,叶节点代表凸子空间,渲染时从观察者所在节点开始递归遍历,保证从前往后的绘制顺序,同时消除深度排序的开销。没有感叹号,没有“我花了三天终于搞定了”,没有任何向阅读者传递情感信息的意图。这甚至不是写给团队看的,这是卡马克写给系统本身的文档,而团队碰巧可以访问。
但BSP不是一个小功能。它是Doom引擎的核心架构决策,它将三维空间表示为一棵二叉树,允许渲染器以从前往后的顺序遍历所有可见多边形,而不必对每个像素进行深度测试。这意味着在386处理器上,在没有GPU、没有硬件加速的情况下,一台售价两千美元的个人电脑可以实时渲染一个拥有任意角度墙壁、可变高度楼层、动态光照和斜面地板的复杂三维场景。在此之前,没有人相信这是可能的,甚至没有人认真尝试过,因为整个产业的共识是,实时第一人称视角的复杂三维场景需要专门的图形工作站,需要价值数万美元的硬件,需要那些只存在于大学实验室和军用仿真系统中的渲染管线。卡马克看着这个共识,然后忽略了它,因为共识不是物理定律,共识只是许多人同时相信的同一个错误,而物理定律不关心有多少人相信它。但BSP的意义不仅在于它解决了渲染顺序问题。它更深刻地改变了id Software内部的工作方式。
一旦BSP成为引擎的核心,地图设计就不再是直接在网格上放置墙壁,而是先构建一个空间结构,然后让BSP编译器将其转化为二叉树。这意味着地图设计师需要理解空间划分的概念,需要知道为什么某些几何体排列会导致BSP树不平衡,需要学习一种不再是直接摆积木的创作方式。罗梅罗可以学会这些,他从来不是愚蠢的程序员——事实上,他曾经以惊人的速度编写过游戏代码,在Softdisk的那些深夜,他的编程速度甚至超过了卡马克在某些任务上的效率。但问题不在于他能不能学会,而在于学会之后,地图编辑就从一种直觉驱动的创作行为,变成了一种必须服从引擎底层逻辑的工程实践。而罗梅罗的整个创作本能都在抗拒这种服从。他想要在地图里放一个隐藏的火箭发射器,因为那会让玩家尖叫,因为那会在玩家社区中产生持续数周的秘密谣言,因为那是一个不需要被任何BSP树解释的、纯粹的、肉体性的快乐。
而卡马克的引擎说,隐藏区域需要被BSP树正确地分割成凸子空间,否则渲染器会在某些视角下漏掉多边形,而在那些漏掉多边形的裂缝里,玩家会看到空白的虚空,而不是恐惧。这是卡马克的本体论实践第一次开始吞噬合作者的自主性。它不会以冲突的形式爆发,至少不会立刻爆发,因为罗梅罗还在为《德军总部3D》的成功而眩晕,因为团队还在享受版税支票带来的自由,因为下一款游戏还没有被正式命名,因为愤怒还没有找到它的词汇。但当卡马克坐在那栋拉上窗帘的房子里,在凌晨三点输入关于光照衰减模型的代码时,他正在铺设的不仅是Doom引擎的技术基础,也是一种将在此后几年里持续侵蚀id Software内部关系的逻辑:真正重要的是系统,而不是任何单一用例;是引擎,而不是任何一款游戏;是那个可以让无限游戏得以生成的元层,而不是任何具体的、血肉丰满的、可以让玩家尖叫的隐藏火箭发射器。
而如果你不能理解这个逻辑,如果你认为那个隐藏火箭发射器比它所依赖的渲染管线更值得被讨论,那么你就是系统的一个组件,一个需要被精确调度、被清晰约束、被周期性地检查输出的资源,而不是系统的共同设计者。光照模型是另一个例证。在《德军总部3D》中,所有表面都接收相同的环境光,亮度只根据距离衰减,而且衰减是线性的。这意味着一个房间看起来像一个均匀照明的盒子,没有阴影,没有角落的黑暗,没有那种让空间变得危险、神秘、值得探索的视觉纵深。卡马克知道这一点,但他也知道,真正的光照模拟——那种追踪每一束光线从光源到表面再到眼睛的路径的算法——在386上是不可能实时运行的,它需要浮点运算单元,需要并行处理架构,需要那些尚不存在于消费级硬件中的计算资源。他不是在追求真实感,他在追求一种在计算约束下有效的近似。
他实现的衰减模型不是物理世界的模型,而是一个经过精心调校以产生特定视觉效果的数学函数,它让靠近光源的墙壁更亮,让远处的角落更暗,让玩家在进入一个房间时本能地知道危险可能藏在哪里——不是因为那是一束真实的光,而是因为那是一束服膺于游戏设计逻辑的、被精确计算过的光,它的每一个光子都存在于一个被约翰·卡马克定义过的、自洽的、不依赖任何外部物理法则的宇宙中。但这同样意味着,关卡设计师现在必须理解光照衰减的数学特性。放一盏灯意味着设置一个衰减半径,而衰减半径决定了玩家在多远的地方能看到那盏灯,而在那盏灯照不到的边缘,什么怪物可能正在等待。罗梅罗想要的是一盏可以照亮整个竞技场的镁光灯,因为那会让战斗更壮丽,因为那会让玩家看清每一个正在逼近的敌人的像素细节,因为那是一种不需要任何光照衰减公式就能被理解的、直接的美学判断。但卡马克的引擎说,不,全局光照在当前的硬件上不可行,你需要在这个半径内工作,这是系统的边界,你不能越过它。
卡马克并没有坐着说出这句话。他甚至没有想过要禁止什么。他只是构建了一个系统,这个系统有自己的物理法则,而这些法则不像日常法律那样可以被辩论、被妥协、被通过人情关系软化。代码不关心你是罗梅罗还是卡马克,代码只执行它的逻辑。在一个由系统决定可行域的世界里,说服作者修改系统是唯一有效的行动,而说服需要理解系统,理解系统需要时间,时间是一种稀缺资源,而卡马克不想将他的时间花在将系统翻译成罗梅罗的语言上。他已经将翻译任务外包给了.plan文件,用那些干巴巴的技术日志,而罗梅罗从未真正学会阅读它们。不是因为他不能,而是因为他不想。他想要的是效果,是冲击力,是玩家脸上那种混合着恐惧与狂喜的表情,而不是一个关于光照衰减的微分方程。1992年夏天,当卡马克开始实现网络同步协议时,这种分裂变得更加不可逆。
局域网对战——允许四台电脑通过IPX协议连接,让四个玩家在同一个三维空间里互相射击——这个想法在当时的游戏产业中几乎不存在,因为它听起来像是技术自杀。实时同步四个视角的三维渲染,在没有任何专用服务器的情况下,通过1992年的家庭网络传输玩家位置、射击方向、拾取物品的状态,同时保持帧率稳定在每秒至少三十帧——这需要解决一系列此前从未被认真对待过的分布式计算问题。卡马克解决它们的方式是设计一个客户端-服务器架构,其中一台玩家的机器同时充当服务器,其余三台作为客户端连接。服务器维护游戏世界的权威状态,客户端发送玩家输入,接收状态更新,并在本地进行预测渲染以掩盖网络延迟。这不是一个完美的解决方案——完美的解决方案需要专用服务器硬件、需要更好的网络协议、需要这个产业在五年后才会具备的基础设施——但它是一个在1992年硬件条件下可以有效运行的解决方案,而“有效”是卡马克唯一认可的赞美词。网络协议的实现对团队的影响是双重的。
一方面,它让Doom的多人模式成为可能,而多人模式将成为Doom超越《德军总部3D》的关键差异化特征,成为那个让最终产品在历史上刻下名字的杀手级功能。但另一方面,它进一步将卡马克推向了那个只有他能完全理解的抽象层。现在,引擎不仅要管理本地CPU的周期和内存页,还要管理网络上四个节点之间的状态同步、丢包重传、延迟补偿。这个系统的复杂性已经到了一个临界点,超过这个临界点,团队中除了卡马克之外没有人能完整地解释它为什么能工作,而只能知道它确实能工作,因为卡马克说它能工作,因为屏幕上那些像素在以流畅的帧率移动,因为四个玩家确实可以在同一个地下室里互相射击而不至于崩溃。这种知识垄断不是卡马克有意为之的阴谋。它只是他认知风格的自然产物。他解决问题的方式是独自深入问题的核心,将所有变量加载到自己的思维中,进行数小时甚至数天的连续计算,然后输出一个解决方案。
这个过程中没有注释,没有文档,没有教学意图,因为注释和文档需要他将已经内化的概念重新外化,而这种外化会打断他的计算流。当他完成了光照模型,他把它推送到服务器;当他完成了BSP,他把它推送到服务器;当他完成了网络协议,他把它推送到服务器。推送本身即是沟通的全部。如果你不能从推送中理解他做了什么,那你需要提高自己的理解能力,而不是要求他降低他的工作效率。而这种逻辑,当它从技术领域蔓延到组织领域时,就开始吞噬卡马克与罗梅罗之间那已经脆弱不堪的合作关系。在卡马克看来,罗梅罗是一个需要被精确调度的资源。他是一个优秀的关卡设计师,一个能够激发玩家肾上腺素的天才,一个能用地图编辑器创造出令人难忘的空间体验的内容生产者。但他也是一个需要被管理的变量——他的创作冲动如果不被引擎的边界约束,就会产生无法被BSP树有效处理的几何体,或者依赖那些在当前帧率预算下无法实现的效果。
因此,卡马克开始将罗梅罗视为系统的一个组件,这个组件有特定的输入输出规格,有已知的可靠性和已知的误差范围,而管理这个组件的方式是向它提供清晰的技术约束,然后检查它产出的内容是否符合这些约束。这不是蔑视。这是工程思维。但罗梅罗不是工程思维的产物,他是一种完全不同的认知风格的产物——那种风格相信直觉,信任肾上腺素,认为游戏设计不是关于数据结构和光照衰减,而是关于一个玩家在凌晨三点,独自坐在黑暗的房间里,突然被一个从未见过的怪物吓得从椅子上跳起来的那个瞬间。而如果实现那个瞬间需要在引擎中开一个特例,需要在BSP树中插入一个逻辑上不优雅但体验上完美的修补,罗梅罗会毫不犹豫地这么做。卡马克不会。对他来说,特例是系统债务,修补是架构缺陷,而如果一款游戏需要靠破坏引擎的纯净性来获得冲击力,那说明引擎还不够通用,还需要进一步的抽象,进一步的解耦,而不是说明创作应该凌驾于架构之上。这就是两个约翰之间开始生长的裂隙。
它不是关于任何具体的技术决策,不是关于BSP的实现方式或光照衰减系数的选择,不是关于谁更聪明或谁更努力。它是关于一个更根本的问题:游戏应该是什么?对卡马克而言,游戏是引擎的用例,是证明系统有效性的测试数据,是那个可以让无限游戏得以生成的元层的一次具体实例化。对罗梅罗而言,游戏就是一切——它是体验,是冲击,是那个让玩家尖叫的瞬间,而引擎只是实现它的手段,是需要被驯服、被突破、被绕过的东西,如果它挡在玩家和那个尖叫之间。1992年秋天,当Doom引擎的技术演示第一次在内部运行时,这个裂隙被短暂地掩盖了。屏幕上的像素组成了一个粗糙的走廊,走廊的墙壁不再以直角排列,而是呈现出一个锐利的斜角,在动态光照的投射下,那个斜面的一侧明亮,另一侧隐入黑暗,而在黑暗的边缘,一个像素画的恶魔怪物正在移动。
它的移动速度是恒定的,不是加速的,因为卡马克判断,在预期的帧率下——大约每秒三十五帧——玩家无法感知速度的渐变,而恒定速度所需的计算量远小于加速模型,因为加速模型需要每帧更新速度变量,需要额外的浮点运算,需要在网络同步协议中传输更多的状态信息。这不是真实感,这是效率,而效率创造出的效果是惊人的。走廊的尽头,一面墙上的纹理在光照下呈现出以前从未在个人电脑上出现过的深度感,而天花板的高度变化让空间不再是一个方盒子,而是一个有起伏、有压迫感、有隐藏危险的建筑结构。团队聚集在屏幕前。罗梅罗看见了那个斜面上的光影,看见了那个从黑暗中浮现的恶魔,看见了那个可以在此后几个月里被他塞满隐藏房间、秘密武器和致命陷阱的三维空间,他发出了一声嘶吼,那种声音像是从胸腔深处被挤压出来,包含着狂喜、征服欲、以及一种只有在他看见一个新世界等待被填充时才会出现的震颤。
汤姆·霍尔看见了那个恶魔,开始构思一个关于它起源的叙事,一个可以填满多页设计文档的科幻背景故事,包括它的生物学分类、它所属的恶魔军团结构、以及主角在击败它之后将会解锁的剧情分支。阿德里安·卡马克看见了那个纹理,开始计算他需要绘制多少张恶魔像素画才能让整个怪物图鉴看起来足够暴烈,足够让那些在深夜独自玩游戏的玩家感到一种想要回头检查自己房间角落的冲动。凯文·克劳德听见了扬声器里传出的音效,开始想象哪种采样能配得上那种从黑暗中浮现的恐怖感,哪种低频可以震动玩家的胸腔,哪种混响可以让一个三米高的恶魔听起来像是从地狱深处爬出来的。约翰·卡马克看见了一个系统,这个系统证明了BSP树的递归遍历、光照衰减模型、网络同步协议和WAD文件格式的架构决策是正确的,因为它们共同产生了一个可运行的、可扩展的、不依赖任何单一用例的引擎。他看见的不是一个游戏,而是一个证明。
这个证明告诉他,他可以在接下来的几个月里继续完善这个系统——优化渲染管线,减少内存占用,提高网络吞吐量,增加对更多声音通道的支持——而罗梅罗和其他人可以将这个系统填充为任何他们想要的东西。他们的工作不冲突,因为他们工作在不同的层上,而卡马克的层是底层的、决定性的、不可被推翻的。这本身就是一个论断,它不仅关于技术架构,更关于权力结构:谁控制元层,谁就控制所有可能的游戏。那个技术演示运行结束时,卡马克没有说太多话。他记下了几个需要优化的渲染路径——某个特定的光照条件下,衰减函数在远距离处产生了浮点舍入误差,导致一个本应完全黑暗的角落出现了一个像素宽的灰色条纹——然后回到他的房子里,拉上窗帘,打开编辑器,开始输入下一行代码。楼下的庆祝声再次传来,但这一次,声音更远,更模糊,像是从另一个世界渗透进来的残余信号,那个世界里有啤酒、有笑声、有罗梅罗正在讲述的关于恶魔怪物设计灵感的故事,而卡马克的屏幕上,光标在关于WAD文件格式的文档中闪烁。
WAD————是一种将游戏的所有资源,纹理、音效、关卡地图、怪物行为脚本,打包进一个统一文件格式的架构决策。它的设计初衷是让引擎核心与资源完全解耦,这意味着任何人只要理解了WAD格式,就可以创建自己的关卡、自己的纹理、自己的怪物,而不需要修改引擎的任何一行代码。引擎启动时加载一个目录表,目录表中的每个条目指向一个资源在磁盘上的位置、类型和大小,运行时按需读取,这使得模组支持成为可能,而不需要暴露引擎源码。模组支持。这是一个在1992年几乎没有人理解其后果的决定。卡马克不是因为它多么具有商业远见而做出这个决定的——他做出这个决定是因为它符合系统的纯净性。引擎应该是一个封闭的、自洽的、物理法则统一的宇宙,而资源是这个宇宙中可以替换的原子。让原子可以被替换,意味着宇宙本身不需要被修改。
当卡马克读到这份文档时,他的反应被日后在场的人回忆为一个简洁的问题:“故事需要引擎做什么?”答案是:什么也不需要。引擎不关心故事。引擎管理多边形、光照、碰撞检测和网络同步,而主角的动机和对白不属于任何这些范畴。因此,在卡马克的逻辑中,故事是纯粹的装饰,是资源层的一个可选组件,它的存在或缺失不会影响系统的有效性。罗梅罗可能在乎故事,但罗梅罗在乎很多卡马克不在乎的东西,而卡马克的引擎正在逐渐成为这样一个仲裁者:它不会说“你不能有故事”,但它会问“故事需要引擎做什么?”而如果答案是“不需要”,那么故事就失去了在技术讨论中的发言权。这不是冲突。这是排挤。一种认知风格通过构建一个越来越复杂、越来越自足、越来越不需要外部参照的系统,将另一种认知风格边缘化。卡马克不需要打败罗梅罗,他只需要继续完善Doom引擎,而随着引擎的完善,罗梅罗的创作空间将越来越被限制在资源层,而资源层的一切最终都可以被替换。换一个关卡设计师,游戏仍然可以运行,因为引擎没有改变。换一个纹理艺术家,游戏仍然可以运行,因为WAD格式没有改变。
引擎不关心故事。引擎管理多边形、光照、碰撞检测和网络同步,而主角的动机和对白不属于任何这些范畴。因此,在卡马克的逻辑中,故事是纯粹的装饰,是资源层的一个可选组件,它的存在或缺失不会影响系统的有效性。罗梅罗可能在乎故事,但罗梅罗在乎很多卡马克不在乎的东西,而卡马克的引擎正在逐渐成为这样一个仲裁者:它不会说“你不能有故事”,但它会问“故事需要引擎做什么?”而如果答案是“不需要”,那么故事就失去了在技术讨论中的发言权。这不是冲突。这是排挤。一种认知风格通过构建一个越来越复杂、越来越自足、越来越不需要外部参照的系统,将另一种认知风格边缘化。卡马克不需要打败罗梅罗,他只需要继续完善Doom引擎,而随着引擎的完善,罗梅罗的创作空间将越来越被限制在资源层,而资源层的一切最终都可以被替换。换一个关卡设计师,游戏仍然可以运行,因为引擎没有改变。换一个纹理艺术家,游戏仍然可以运行,因为WAD格式没有改变。
换掉整个故事,换掉所有对话,换掉那个让罗梅罗在凌晨三点独自傻笑的隐藏火箭发射器,游戏仍然可以运行——因为引擎是永恒的,而引擎是卡马克的。1992年12月,id Software原本计划发行的另一款游戏正式取消。这个决定几乎没有引起任何争议,因为《德军总部3D》的成功已经让整个团队的注意力转向了下一件大事,而下一件大事就是Doom引擎。那个被取消的项目——汤姆·霍尔曾经为之投入了大量创作热情的设计,那些关于外星世界、时间旅行和哲学悖论的叙事野心——变成了团队历史中的一个注脚,一个被更大引擎的引力场吞噬的小天体。霍尔开始意识到,在这个由卡马克的认知风格主导的团队中,他的叙事野心没有容身之地。
而在那栋拉上窗帘的房子里,卡马克正在实现一个关于怪物移动速度的优化。他的决定是让怪物的移动速度恒定,而不是加速。在物理世界中,物体从静止到运动需要加速,从运动到静止需要减速,但实现加速需要额外的计算周期来更新每帧的速度变量,而卡马克判断,在Doom预期的帧率下,玩家无法感知速度的渐变。这个判断可能是正确的,但它的正确性不是重点。重点是,这是一个关于真实感的取舍,而取舍的标准不是“真实世界怎么样”,而是“系统效率怎么样”。如果真实感与效率冲突,效率胜出。如果罗梅罗说他想要一个怪物在冲刺时逐渐加速,因为那会让追逐更紧张,因为那会让玩家在逃跑时感到一种递增的恐惧,因为那是一种他在街机厅里学到的基本节奏——卡马克会回答说,引擎不提供这个功能,而实现这个功能需要修改移动系统,移动系统是与网络同步协议耦合的,修改它会增加延迟,而增加延迟会影响多人模式的体验,而多人模式是引擎的核心功能之一。这个因果链是完美的,不可辩驳的,因为它不是意见,而是系统分析。而罗梅罗,不管他多么想要那个加速冲刺的怪物,他无法反驳系统分析,因为他从未彻底理解过网络同步协议。他只能接受,或者找一个绕开问题的办法——比如,在关卡设计中放置一个触发点,当玩家经过时,怪物突然从隐藏的壁龛中生成,制造一种“它冲过来了”的错觉。这不是一个加速的怪物,这是一个在触发器上生成的怪物,但它可以产生类似的效果,而卡马克不会反对,因为它不需要修改引擎。
如果罗梅罗说他想要一个怪物在冲刺时逐渐加速,因为那会让追逐更紧张,因为那会让玩家在逃跑时感到一种递增的恐惧,因为那是一种他在街机厅里学到的基本节奏——卡马克会回答说,引擎不提供这个功能,而实现这个功能需要修改移动系统,移动系统是与网络同步协议耦合的,修改它会增加延迟,而增加延迟会影响多人模式的体验,而多人模式是引擎的核心功能之一。这个因果链是完美的,不可辩驳的,因为它不是意见,而是系统分析。而罗梅罗,不管他多么想要那个加速冲刺的怪物,他无法反驳系统分析,因为他从未彻底理解过网络同步协议。他只能接受,或者找一个绕开问题的办法——比如,在关卡设计中放置一个触发点,当玩家经过时,怪物突然从隐藏的壁龛中生成,制造一种“它冲过来了”的错觉。这不是一个加速的怪物,这是一个在触发器上生成的怪物,但它可以产生类似的效果,而卡马克不会反对,因为它不需要修改引擎。
这就是他们开始合作的方式:不是通过讨论,而是通过罗梅罗在资源层的边界内寻找变通方案,而卡马克在引擎层继续构建一个越来越不需要变通方案的系统。如果变通方案足够多,足够复杂,足够令人印象深刻,那说明引擎还不够通用,还需要进一步的抽象,还需要更多的解耦。而每一次这样的抽象,都将卡马克推得更远,将罗梅罗限制得更窄,直到那个不可避免的时刻到来——当引擎足够完善,完善到任何人的创作都可以被兼容,而任何人的重要性都可以被降低。1993年1月,Doom引擎的第一次完整内部演示在id Software的办公室运行。四台386电脑通过局域网连接,四个玩家在同一个虚拟空间里移动、射击、死亡、重生。动态光照在斜面上滑动,恶魔的像素画第一次在这个新世界里被渲染出来,网络同步延迟低到几乎不可感知——卡马克在控制台日志中看到的时间戳显示,平均往返时间稳定在十四毫秒以内,这意味着玩家A扣下扳机的那一刻,玩家B的屏幕上几乎同时出现了枪口闪光。团队沉默了。
不是那种被震撼得说不出话的沉默,而是一种在巨大成就面前的、带有敬畏感的沉默,一种在意识到自己正在见证某个尚未被正式命名的东西诞生时的本能反应。罗梅罗看着屏幕,看见了他将要填满的无限空间——那些可以被他扭曲、堆叠、装饰、塞满秘密的走廊和房间,那些可以让玩家们互相厮杀的竞技场,那些可以让他的肾上腺素美学达到巅峰的关卡架构。他深吸一口气,然后开始咆哮,那种标志性的、可以穿透墙壁的、将整个办公室变成舞台的咆哮,它包含了所有他无法用语言表达的狂喜,所有他将在接下来的几个月里倾注进那些关卡的血肉和神经。卡马克站在人群边缘,看着同一个屏幕,但他看见的不是罗梅罗看见的东西。他看见的是BSP树的遍历路径在控制台输出中滚动,是光照衰减函数在每一帧被调用时产生的精确数值,是网络数据包在服务器和客户端之间往返的时间戳,是那个被他从零开始建造的、在每一行代码中都服从于他意志的虚空,正在以每秒三十五帧的速度证明自己的有效性。
他看见的是一个系统,一个被证明有效的系统,一个可以不依赖任何特定游戏而存在的系统,一个即使将今天屏幕上所有的恶魔、所有的纹理、所有的关卡地图全部替换为完全不同的东西,仍然可以运行、仍然可以渲染、仍然可以同步四个玩家在互联网上互相射击的系统。他看见的是他的虚空,那个他自从写下第一行多边形裁剪算法代码时就开始构建的、自洽的、物理法则统一的、不需要任何外部参照的封闭世界,而此刻,这个世界正在屏幕上运行,帧率稳定,光照正确,网络同步,一切都按照他设计的方式工作。这个成就的全部重量,只有卡马克自己能完全承受。因为只有他完全理解,为了让那束光照在那个斜面上,为了让那个数据包在十四毫秒内从服务器到达客户端,为了让那个恶魔在BSP树的叶节点中被正确渲染,他付出了什么样的代价。他付出了与人连接的能力,付出了通过共享空间而不是共享代码来维持友谊的能力,付出了将合作者视为合作者而不是资源的能力。
这个成就的全部重量,只有卡马克自己能完全承受。因为只有他完全理解,为了让那束光照在那个斜面上,为了让那个数据包在十四毫秒内从服务器到达客户端,为了让那个恶魔在BSP树的叶节点中被正确渲染,他付出了什么样的代价。他付出了与人连接的能力,付出了通过共享空间而不是共享代码来维持友谊的能力,付出了将合作者视为合作者而不是资源的能力。这些代价没有被写进任何.plan文件,因为它们不是技术问题,而技术问题是他唯一被训练来解决的问题。它们没有被写进任何变更日志,因为它们是副作用,是副现象,是在系统优化过程中被牺牲掉的、不产生任何效率增益的冗余功能,而冗余功能在工程学上等同于浪费。他转身离开人群,回到他的房子里,回到那扇拉上窗帘的窗户后面,回到光标闪烁的编辑器前。在屏幕上,Doom引擎的代码库已经膨胀到超过十万行,每行代码都服从于他的意志,每行代码都执行着精确的逻辑,每行代码都不会在凌晨三点突然需要被解释、被安抚、被妥协。这是一个完美的协作者,一个永远不会提出异议的系统,一个可以被他无限完善而永远不会要求他停止优化的宇宙。
他输入了下一行代码,关于碰撞检测的一个边界情况——当一个玩家以特定角度冲向一面墙时,浮点舍入误差可能导致他穿过墙体,落入那个由未定义内存构成的、纯黑的虚空——然后他的手指在键盘上停顿了一秒,在那短暂的一秒里,他听见了远处传来的笑声,罗梅罗的笑声,团队的庆祝声,那些他不再参与的声音,那些从另一个世界渗透进来的、已经变得模糊的、像是被厚厚的墙壁和拉紧的窗帘过滤过的残余信号。然后他继续输入,而那些声音渐渐消失,被键盘的敲击声、显示器的嗡鸣声和他自己构建的虚空所吸收。那个虚空是他建造过的最完美的引擎,它不需要任何人,也不需要他的任何部分,除了那些可以被转化为代码的部分。