第 8 章
黄金时代的团队编译器
第8章 黄金时代的团队编译器
岩田聪站在白板前,手里捏着马克笔,却没有写字。面前坐着七个人——程序员、美术、音效师,都是HAL研究所这一年多来陆续加入的新面孔。桌上摊着几张打印纸,上面密密麻麻列着内存地址分配表。空气里有种微妙的沉默,所有人都在等他把事情说清楚。他要说的这件事,他自己也是刚刚想明白。那是1986年初的札幌。HAL研究所接到的开发委托比两年前翻了三倍。任天堂传来的不仅是合同,还有越来越紧的交付期限。红白机在市场上已经卖疯了,1985年全年日本国内出货量接近四百万台,第三方开发商像闻到血腥味的鱼群一样涌进来。每一家都想在卡带上分一杯羹,但真正能把游戏按时做出来、做完还能跑得稳的公司,屈指可数。HAL是其中之一。因为岩田聪此前在《弹珠台》和《高尔夫》里展现出的技术功底,任天堂把越来越多的项目优先交给他们。问题也出在这里。岩田聪发现自己正被一种奇怪的焦虑啃噬。
他仍然每天写代码,仍然能在深夜的测试机上把一段循环优化到比别人少用十几个字节,但他清楚地意识到一件事:他不可能同时出现在三个项目的代码审查现场。上一周,《气球大战》的一个物理碰撞模块出了bug,负责的程序员花了两天没找到问题,最后是他坐下来,花了四十分钟从汇编指令的堆栈操作里揪出一个指针偏移错误。问题解决了,他回到办公室后坐在椅子上想了很久。不是因为那个bug难找。而是因为那个bug的类型,他在三个月前的《高尔夫》项目里亲手修过一次。信息没有传递出去。知识停在了他自己的脑子里。这就是此刻他站在白板前的原因。他要做的不是再修一个bug,而是让这个房间里的人以后不需要他也能修。岩田聪开口了,声音不大但很稳。他把马克笔点在白板上,画了一个方框。《气球大战》的程序部分拆成六个模块。每个模块的入口地址、出口参数、占用的零页内存区域和堆栈深度,今天必须定下来。他说的是汇编语言开发中一个极其具体的技术约束。
FC的CPU只有2KB主内存,其中零页地址——6502处理器里能用单字节寻址、速度最快的那二百五十六个字节——是所有程序员争夺的黄金地段。变量放在零页还是放在普通内存,执行效率能差出好几个指令周期。在每秒要处理六十帧画面的游戏里,这种差异累积起来就是掉帧和不掉帧的区别。岩田聪在白板上写下一串十六进制地址。物理碰撞模块占零页$00到$1F,三十二个字节。超出范围的数据必须走栈传递,用完立刻弹出。角色状态模块用$20到$3F。音效驱动模块只给$80到$9F。房间里一个年轻程序员举手。十六个字节做音效驱动是不是太紧了?音序数据至少需要三个通道的状态机。岩田聪的回答基于精确的计算。FC的APU只有五个通道——两个脉冲方波、一个三角波、一个噪声、一个DPCM。每个通道的状态变量用三个字节就能装下:一个音高计数器、一个包络相位、一个持续时间指针。五个通道十五个字节,剩一个做全局音量标志位。他停了一下,补了一句:《高尔夫》里就是这么做的。
这不是炫耀。这是把经验变成接口规范。那天下午的会议持续了将近三个小时。岩田聪带着所有人把《气球大战》的全部内存映射表过了一遍,精确到每一个字节的归属。他没有说“大家加油”或者任何感性的动员。他说的是:角色精灵的OAM DMA传送必须在VBlank期间完成,主循环里不允许直接写$2004端口。这个规则写进开发规范,以后所有项目都照此执行。OAM是FC图形处理器里的精灵属性内存,存放着屏幕上所有活动角色的位置、朝向和瓦片索引。这块内存只能在电子束回扫的极短时间内通过DMA方式批量写入,如果在渲染期间直接操作,画面就会出现撕裂。这个知识点任何一个合格的FC程序员都懂。但岩田聪要做的不是让人“懂”,而是让人“遵守”。他把这个要求从个人经验变成了团队规则。会议结束后,他回到工位,打开一个活页笔记本,在当天日期的页面上写了一行字:接口定义完成,物理模块零页占用压缩至三十一字节。下面又加了一行:下次审查,角色状态机状态转换表。
这个笔记本后来被HAL的老员工称为“岩田编译器手册”。它不是正式文档,也没有装订成册,但里面记录的内容逐渐演变成了HAL研究所第一套成文的代码规范。这些后来被称为“编码标准”的实践,在1986年的日本游戏开发现场还极为罕见。那个年代的FC开发更像手工作坊:每个程序员有自己的习惯,代码能跑就行,注释可有可无,变量名经常是随手打的缩写。两个不同程序员写的模块要想对接,往往需要第三个人花几天时间读懂两边代码再手工粘合。岩田聪经历过太多次这种“粘合”的痛苦。他在1985年同时参与三个项目时发现,自己至少有三分之一的时间不是在写新功能,而是在理解别人写的代码里某个变量到底是什么意思。
有一次他为了查一个画面闪烁的bug,逆向追踪了另一位程序员的代码逻辑,最后发现对方把帧计数器和动画相位计数器存进了同一个内存地址的不同位段,用位掩码分别读写——很巧妙,但没有任何注释。岩田聪花了整整一个下午才搞明白。他没有发火。他只是在那天晚上把那个位段操作的逻辑画成了流程图,第二天贴在开发室的墙上,然后对所有人宣布了一条规则:以后用位段拆分变量的地方,必须在旁边写清楚掩码值和位宽。不写注释的代码不要提交审查。这不是管理学的教条。这是一个程序员对混乱的本能厌恶。到1986年夏天,HAL研究所的开发室墙上已经贴了十几张这样的流程图和规范说明。新加入的程序员会被要求先花三天时间读完这些材料才能开始写代码。岩田聪亲自做入职培训——不是讲公司文化,而是讲FC硬件的内存映射、HAL内部约定的子程序调用规范、以及代码审查流程中必须检查的五个关键点:堆栈平衡、零页占用、中断响应时间、VBlank期间的CPU负载、跨模块数据接口的类型一致性。
最后一个词——“类型一致性”——在汇编语言开发中其实是个很奢侈的概念。汇编没有编译器帮你检查数据类型,一切数据本质上都是字节序列。一个模块把某个地址的值当成有符号偏移量来用,另一个模块却把它当成无符号索引来读,结果是画面上的角色偶尔会瞬移到一个诡异的位置。这类bug极难追踪,因为它们在大多数时候不会触发,只在特定条件下才会发作——比如当偏移量恰好超过一百二十七的时候。岩田聪的解决方案很朴素:他要求每个模块的接口文档必须明确标注每个传入传出参数的数据类型和取值范围。“有符号八位整数”、“无符号八位整数”、“BCD码”、“位标志”——这些标签必须写在注释头里。调用方和被调用方的程序员都要在接口文档上签字确认。签字确认。在1986年的日本游戏行业,这个做法几乎闻所未闻。但效果是立竿见影的。《气球大战》的开发周期比最初预估缩短了将近四分之一。不是因为程序员写得快了,而是因为花在调试跨模块bug上的时间大幅减少。
岩田聪在项目总结会上没有讲任何感性的话,他只是把bug追踪表投影在墙上——那个表格记录了从项目开始到结束每一个bug的类型、来源和修复时间——然后指着其中一列数据说:接口不一致导致的bug占总数的百分之三。上一个项目是百分之十七。这组数字就是最有效的动员令。同一时期,外面的世界却是另一番景象。1986年到1987年间,红白机游戏市场进入了疯狂的膨胀期。大量第三方开发商涌入赛道,其中不乏在计算机软件领域颇有积累的公司,也有许多是从街机行业转过来的。但FC开发的特殊性——那种在2KB内存里做文章的极限编程——让很多习惯了宽松硬件环境的团队措手不及。项目延期是常态。更糟糕的是延期之后拿出来的产品仍然充满bug。有些公司勉强发行了游戏,结果因为严重的画面闪烁或崩溃问题遭到玩家投诉和退货,零售商反过来向发行商施压。到了1987年,已经有至少十几家规模较小的第三方开发商悄然退出了FC市场。
这些公司的死因各有不同,但有一个共同点:它们没有建立起有效的开发管理机制。程序员各自为战,美术资源与程序对接靠口头沟通,版本控制依赖软盘拷贝,代码冲突靠人肉合并。当项目规模从两三个人扩展到十几个人时,这套手工作坊式的流程就崩溃了。其中一个典型案例发生在一家从个人电脑游戏转战FC的中型开发商身上。这家公司花了八个月开发一款横版动作游戏,投入了十二名开发人员——在当时算是相当大的团队配置。问题出在两个核心程序员身上:他们各自写了一套完全不同的碰撞检测算法。一个人采用基于瓦片地图的边界判定法,另一个人写的是基于角色坐标的距离计算法。两个人按照各自的理解完成了自己负责的关卡代码。等到集成测试的时候才发现,角色在某些地形组合下会直接穿过墙壁,而在另一些地方又会被空气墙挡住无法移动。修复这个问题需要重写将近一半的核心代码。但交付期限只剩六周。
项目最终被取消,前期投入全部沉没。岩田聪听说了这些事。他没有评论别人的失败,但他对HAL内部的人说了一句话:不靠天才解决问题,靠规则。这句话后来被HAL的一些老员工记了很多年。不是因为它在修辞上有多漂亮——恰恰相反,它太朴素了——而是因为它准确地描述了岩田聪正在做的事情:把解决问题的能力从个人身上剥离出来,沉淀为团队可以共享的规则和流程。1987年初,《越野机车》项目启动。这是任天堂第一方发行的游戏,由宫本茂担任制作人,HAL研究所负责主要开发工作。宫本茂的设计理念一如既往地强调直觉和乐趣:玩家驾驶摩托车在野外赛道上竞速,可以做出跳跃、加速和倾斜车身等动作;赛道设计要有起伏和障碍;操作手感必须轻盈而有弹性。“轻盈而有弹性”是一个典型的宫本茂式描述。它不涉及任何技术参数,但它对程序员提出了极高的要求。
车辆物理模型必须在每一帧内完成加速度计算、摩擦力衰减、跳跃抛物线和落地缓冲等多个步骤的运算,而且所有这些运算必须在2KB内存的约束下与其他游戏系统共存。岩田聪拿到设计文档后的第一反应不是写代码,而是画表。他把《越野机车》的程序系统拆成了八个模块:物理引擎、输入处理、地形碰撞、精灵管理、音效驱动、计时与排名、AI对手逻辑、以及一个负责协调所有模块的主控循环。每个模块都被分配了明确的内存预算和CPU时间预算——后者尤其关键。FC的CPU主频只有1.79MHz。每条指令的执行时间以微秒计。主循环必须在一帧的16.7毫秒内跑完所有逻辑并完成画面更新数据的准备,否则就会掉帧。岩田聪做了一件在当时很少人做的事情:他把每个模块在最坏情况下的指令周期数估算出来,然后加总验证是否超出帧时间预算。物理引擎最坏情况下需要多少条指令?输入处理呢?AI对手的路径计算呢?他把这些数字列成表格,然后对团队宣布了硬约束:总预算是一帧两万九千个指令周期。
物理模块不能超过八千个周期。AI模块不能超过五千个周期。超出预算的部分必须在审查阶段优化掉。这不是建议。负责AI模块的程序员后来回忆说,他最初觉得五千个周期做对手AI简直是开玩笑——光是计算对手摩托车与玩家位置的相对距离就要用到乘除法运算,而6502处理器没有硬件乘除法指令,全靠软件移位实现。但岩田聪帮他拆解了问题:距离比较不需要精确的欧几里得距离公式;用曼哈顿距离——横纵坐标差值的绝对值之和——就足够判断对手的相对位置了;而绝对值的计算可以通过查表法用一个很小的查找表实现,比条件分支快得多。这就是岩田聪式的指导:不告诉你“想办法优化”,而是告诉你“这个算法可以用查表法替代”。他把自己的技术直觉拆解成可操作的步骤交给别人。《越野机车》最终在1987年顺利发行并取得了百万级销量。游戏的手感确实做到了宫本茂要求的“轻盈而有弹性”:摩托车在空中的抛物线轨迹流畅自然;落地时的悬挂压缩和回弹有肉眼可辨的细微变化;
转弯时的车身倾斜角度与速度之间的对应关系让人感到一种直觉性的满足。这些细节的实现,靠的不是某个天才程序员的灵光一现,而是岩田聪制定的物理模块接口规范——负责手感的程序员知道自己要在哪个函数里修改哪个参数来调整弹性系数,因为接口文档上写得清清楚楚。但岩田聪在这一时期最核心的贡献并不在于某款具体游戏的成功。《气球大战》和《越野机车》都是好游戏,它们的销量证明了开发质量;但岩田聪真正在做的事情比这更大。他在建立一套组织的操作系统。
他甚至建立了一套类似于“中断处理”的优先级机制。当某个项目出现紧急bug需要跨组支援时,响应程序员的调度方式不是靠行政命令或者人情请托,而是靠一套预先约定的优先级表:涉及数据丢失或画面崩溃的bug为最高优先级,可以打断当前所有非交付节点的工作;视觉效果瑕疵为中等优先级;优化建议为最低优先级。这套制度在今天的软件开发行业看来或许稀松平常——敏捷开发、Scrum、看板管理这些概念早已深入人心——但在1987年的日本游戏行业,它是开创性的。那个年代的游戏开发管理几乎完全依赖项目经理的个人经验和威望;一旦项目经理判断失误或者离职,整个团队就陷入混乱。岩田聪试图建立的是一套不依赖特定个人的系统:规则写在纸上而不是记在某人脑子里;流程可以被新员工学习而不需要老员工口传心授;技术决策的依据是可验证的数据而不是谁的嗓门大。他仍然写代码——每天晚上九点以后,当会议和审查都结束了,他会回到自己的终端前写上一两个小时。
他仍然写代码——每天晚上九点以后,当会议和审查都结束了,他会回到自己的终端前写上一两个小时。这段时间他写的大多是底层工具:内存转储分析器、性能剖析脚本、自动化的接口一致性检查程序。他把这些工具交给团队使用,然后说:让机器检查机器能检查的东西。这句话浓缩了他的管理哲学的核心:把人从机械性的验证工作中解放出来,把创造力留给真正需要人的部分。到1988年初,HAL研究所的程序员团队已经从两年前的五个人扩展到了将近二十人。项目数量稳定在每年三到四个中等规模作品外加若干小型委托开发。代码复用率——这是一个岩田聪很看重的指标——从几乎为零提升到了大约百分之三十:新项目可以直接引用旧项目中经过验证的模块库而不需要从头编写。《越野机车》的物理引擎核心代码后来被复用到另外两款赛车类游戏中,修改量不到百分之十。任天堂方面注意到了HAL的稳定产出能力。
山内溥本人并没有直接过问开发管理的细节——那不是他的风格——但任天堂的制作人们开始形成一种默契:技术难度高、工期紧张的项目优先找HAL。因为他们能按时交付能跑得动的产品。这不是一句轻飘飘的赞美。在那个年代,“能跑得动”本身就是一道极高的门槛。然而门槛的另一侧站着另一种挑战。1988年春天的一个傍晚,岩田聪坐在办公室里翻看一份新的项目提案。提案来自一位年轻的游戏设计师——不是宫本茂那种级别的制作人,而是一个刚入行不久但想法天马行空的策划。提案文档有二十几页,里面充满了各种奇思妙想:一个发生在现代美国小镇上的冒险故事;主角是一群孩子;战斗系统不是传统的回合制或动作制,而是某种模糊的情感对抗;画面风格不走可爱路线而要偏向写实与怪诞的结合;音乐要像电影配乐一样有完整的叙事结构。文档的最后写着项目的暂定名:《地球冒险》。岩田聪放下文档,沉默了很久。
他不是在评估这个项目的可行性——以他的技术直觉,他一眼就能看出这份提案里有多少东西在当前硬件条件下几乎不可能实现。他在想的是另一件事:他用了三年时间建立起来的那套基于技术理性的管理方法——那些清晰的内存分配表、严格的接口规范、精确到指令周期的性能预算——在面对这样一个拒绝被拆解成清晰模块的创意时,还能不能运转得起来。《地球冒险》的设计思路本质上是对“编译器思维”的反叛。它不是从技术约束出发推导可行方案,而是先构想出一个完整的幻想世界,再试图把这个幻想世界塞进硬件里。这种创作方式产生的需求往往是模糊的、变动的、难以预先定义的——而这恰恰是岩田聪那套管理方法最不适应的场景。他把文档合上,放回桌上,然后拿起电话打给了任天堂方面的对接人。看完了,他说,语气一如既往地平静。这个项目需要的时间会比预估长很多。对方在电话那头说了些什么。岩田聪听着,手指无意识地敲着桌面。不是技术问题,是方法问题。
挂掉电话后,他在笔记本上写了一行字,没有日期,没有标题,只是孤零零的一句话:规则能解决复杂,但能解决混乱吗?窗外札幌的夜色正在变深,远处有几盏路灯亮起来,光线穿过玻璃落在桌上那二十几页提案纸上。《地球冒险》的封面画着一个戴棒球帽的男孩站在星空下,脚边是一只奇怪的生物,背后是连绵的山脉轮廓。岩田聪盯着那幅画看了几秒钟,然后把笔记本合上,起身离开了办公室。
走廊里的灯光把他的影子拉得很长,投在贴着代码规范和流程图的墙壁上——那些他花了三年时间一笔一画建立起来的规则,此刻静静地覆盖在白板表面,像一层坚固而沉默的保护壳。保护壳里面是秩序,保护壳外面,是即将撞上来的混乱。1988年,HAL研究所全年交付了四款游戏,全部在预定工期内完成,代码缺陷率较行业平均水平低了近六成。同期进入FC开发领域的第三方开发商中,约有四分之一在两年内退出市场,原因包括项目延期导致的违约金、产品质量问题引发的退货潮、以及团队内部管理失控造成的核心人员流失。《越野机车》的物理引擎模块被成功复用至后续两个项目,修改量均未超过原始代码的百分之十五。《气球大战》中定义的OAM操作规范成为HAL所有FC项目的标准模板,此后三年内未再出现一例VBlank期间画面撕裂的bug报告。这些数字并未被公开统计,但它们反映了红白机时代开发者的高淘汰率。岩田聪在这一时期建立的管理体系,让HAL研究所稳稳地站在了存活者的一侧。但那个戴棒球帽的男孩还站在星空下,脚边跟着一只奇怪的生物,背后是连绵的山脉轮廓。《地球冒险》的提案纸静静躺在办公桌上,带着所有无法被表格和流程图驯服的野性创意,等待回应。