AI 系统 · 对一个规则驾驶员在 1997 年游戏中的实证研究 · 研究原型(游戏)

只读说明书是不够的

一个规则驾驶员在 Fallout(1997)中。游戏说明书、攻略、存档进度与升级,对一个决策回路中没有神经网络的驾驶员分别改变了什么——依据驾驶员自己的记录测量。

下载 PDF(英文)↓ 失败优先循环 → VDSG:受指挥的运行时 → 实证研究 · 20 页 · 代码冻结于提交 6fc5c72(美国东部时间 2026 年 9 月 26 日 22:59)· 每个数字都出自我们自己;尚无第三方复现
决策回路中没有神经网络 每一项许可都附页码凭据 学习者写不到玩家的存档 每组一次运行,处处注明 负面结果一并发表
摘要

我们让一个决策回路中没有神经网络的规则驾驶员来玩 Fallout(1997)。游戏运行在开源的 Fallout Community Edition 引擎上,外加一个插桩补丁(状态输出、画面输出、动作输入)。每一次决策中,可容许的目标是:规则认定当前可行、且被带出处的卡片许可的那些目标,再由命令收窄;经验只能删除或重排它们。游戏的印刷版说明书被编译成 2,250 张附页码凭据的卡片;在全部 256 种事实组合中,它没有扣下任何一个可行目标,而在 2,344 次在线决策中,它改变所选目标的次数为 0:一份攻略列出的 22 个任务,它一个也没有提到;攻略的 12 个地点它只提到 2 个;除配音名单外,驾驶员遇到的人物它一个也没有提到。这份社区攻略提供了作为目标的任务以及该先找谁;经验再补上每句台词会通向哪里(按对话对象分别记住)。驾驶员像玩家一样保存进度,但只写入两个临时存档位;引擎拒绝其他任何存档位,所以学习者无法覆盖玩家的存档。在同一存档、同一随机种子下,每组各跑 12 分钟的 A/B 中,学习开启与关闭分别记录到 3 次与 16 次死亡,首次死亡分别在 8.6 分钟与 1.0 分钟,经验分别 +250 与 0;从空记忆开始,死亡 6 次对 16 次。每组只有一次运行。它通过插桩领取升级,在一次在线运行中死亡 10 次后升到 2 级,每一次死亡都回到了一个存档。我们报告途中发现的缺陷以及仍未解决的问题,其中包括毫无经验增长的三个小时。

说明书告诉你怎样操作这个游戏;攻略告诉你在游戏里做什么。在我们的记录中,只有后者改变过决策。

1 · 引言

1997 年一款角色扮演游戏的新玩家,打开盒子先读说明书。它讲解界面、角色属性表、以行动点计价的战斗,以及如何交谈、交易、治疗与旅行。它不告诉你去哪里、找谁谈、到了那里做什么;为此,玩家们写了攻略。本文要问的是:当玩家是一个只能追求“有出处的文档所许可的目标”的软件时,这两类文档各自贡献了什么。

我们的回答只针对一款游戏、一本说明书、一份攻略和一个驾驶员。游戏是 Fallout(Interplay,1997) [5],使用合法拥有的一份游戏数据,运行在其引擎的开源重新实现 Fallout Community Edition 上 [7],外加一个小型插桩补丁,把引擎的状态与画面写出、把动作读入。驾驶员是一个决策回路中没有神经网络的规则驾驶员,运行在 VDSG 运行时之中 [23]:每一次决策,可容许的目标都是“规则认定对引擎事实可行的目标”与“带出处的卡片所许可的目标”的交集,再由常设命令收窄;驾驶员的经验只能从这个集合中删除目标或对其重排,永远不能添加 [19]。游戏的印刷版说明书被编译成 2,250 张附页码凭据的带出处卡片;一份社区攻略又被编译成 2,289 张。

说明书许可了局面所提供的每一个目标,而按驾驶员自己的反事实计数器,它在 2,344 次在线决策中一次也没有改变所选的目标。改变行为的是攻略——它提到地点、任务与人物——再加上一款长篇游戏对任何玩家都有的两项要求:保住进度,以及领取角色的成长。我们依据驾驶员自己的记录测量了每一项,并把这些记录暴露出的缺陷作为方法的一部分一并报告。

1.1 · 贡献

1.2 · 我们主张什么,不主张什么

我们不主张驾驶员把 Fallout 玩得好:它只升到过一次 2 级;在那个阶段它唯一可做的任务在 1 级时击败了它,到 2 级又击败了它一次;之后三个小时它一无所获(§10)。我们也不主张攻略的效果经过了对照比较:A/B 改变的是学习,而不是书,两组都读两本书。每一项比较都只基于每组一次运行、一个存档、一个角色和一个引擎版本;每个数字都出自我们自己,尚无第三方复现。我们主张的更窄:在这款游戏和这个驾驶员上,一份只讲游戏如何操作的文档许可了一切却没有引导任何决策;一份讲游戏世界的文档改变了驾驶员的行为;而存档与升级可以加给一个学习者,却不必赋予它写入玩家存档的能力。

与先前论文的关系。运行时、它的命令以及有界的学习者,在 VDSG 论文中描述并证明 [23];学习循环、它的 Wilson 门槛以及它在这款游戏中需要的功劳分配修复,见失败优先论文 [19]。那里记录了一次在线 Fallout 运行在同一名守卫面前死了 49 次,原因是责任只追溯到每场战斗最后的片刻;把一场战斗记到引发它的那句台词上,并且只在 Wilson 下界超过基础率时才判定,这在脚本化场景中终结了这个循环。本文沿用这套机制,不重复其结果。关于坦克游戏 [21]和关于把事实依据训练进小模型 [22]的姊妹论文,以同样的风格报告了同一团队的其他测量。

2 · 环境:游戏、引擎与记录的内容

2.1 · 游戏与引擎

Fallout 是一款等距视角的回合制角色扮演游戏:玩家角色在城镇、避难所和荒漠遭遇的六角格地图上行走,通过编号台词菜单与人交谈,在按行动点计价的回合中战斗,并通过经验值与等级成长。驾驶员在 Fallout Community Edition [7] 上玩这款游戏——它是对原版可执行文件的源码级重新实现,固定在上游提交 0609bcf(2025 年 1 月),配合一份合法拥有的原版游戏数据。本文说“引擎做 X”时,指的是在该提交下阅读的这个重新实现;它力求复现原版游戏的行为,凡结论依赖于此处,我们都给出源文件。

驾驶员从不在玩家自己的安装上运行。一个暂存脚本为它准备一个私有的游戏目录:两个大型数据档案通过链接读取,地图、脚本、文本和存档都被复制,因此驾驶员做的任何事都写不到人玩的那些文件。驾驶员从一个既有存档开始:Shady Sands 镇里的一个 1 级角色。两个游戏副本从不共享同一目录;唯一一次共享,就是 §7 中被排除的那次运行。

2.2 · 插桩补丁

除此之外引擎未作修改。在提交 6fc5c72 处,补丁新增 2,211 行、删除 11 行,涉及 25 个文件;新增的行大多在三个新文件里(接口本身 1,456 行;升级命令 164 行;一个 JSON 写出器 48 行),其余是主循环、战斗、对话、存档与读档、角色界面、世界地图和任务列表中的挂钩。每隔一帧,它把引擎状态写成 JSON,并把当前画面写成位图;它从一个文本文件读取命令,并在游戏循环或战斗回合中本应处理一次点击的位置执行每条命令。状态包括:玩家的生命值、行动点、等级、经验、双手、物品栏和六个路径探针;地图上每个生物、物品、门和出口格及其六角格距离、路径长度,对生物还有引擎给出的命中率;战斗与回合状态;当前对话(说话者、回复、编号选项);任务列表;世界地图;以及引擎为存档、存档失败和驾驶员请求的重新载入所维护的计数器。命令包括:行走、离开、攻击、交谈、使用、拾取、装备、治疗、结束回合、旅行、进入、寻路查询、存档、选择死亡后回到哪个存档、重新载入、领取等级、提升技能、选择特长,以及一小组原始按键。

2.3 · 决策与记录

驾驶员每 0.5 秒决策一次,战斗中每 0.35 秒一次。每次控制台运行都会为每个决策向日志追加一行 JSON:地图与六角格、生命值、行动点、经验与等级、是否在战斗或对话中、可行的、可容许的、不可执行的和被暂停的目标、所选目标与发出的命令、驾驶员的状态行、当前任务、最近的敌对者及引擎给出的命中率、存档计数器,以及引擎对上一条命令的应答。日志始于 2026 年 9 月 26 日 17:38(当天下午才加入);在此之前,唯一的记录是驾驶员的计数器、控制台读数和提交历史。旧日志按其运行所显示的内容命名保存。驾驶员的记忆(每种局面与目标的死亡、每句台词引发了什么以及通向哪里、被搁置的任务、存档)是日志旁边的 JSON 文件。

数据截止。本文描述的代码是提交 6fc5c72(9 月 26 日美国东部时间 22:59)。所分析的决策日志截至 9 月 27 日 00:40;其中最后 101 分钟运行的正是该提交。截止之后该线仍在继续,又有六个提交,包括一个战役层(§10);这些都不在本文评估之内。所有分析都是只读的:读取日志、结果和记忆,固定版本的代码取自 git 对象,除了从导出副本运行该线自己的单元测试(不启动引擎)之外,没有运行该线的任何东西。附录 A 把每个数字对应到它的文件。

3 · 驾驶员

3.1 · 可容许集合

每次决策时,驾驶员把引擎状态转成一份情境报告,并像在 VDSG 运行时中一样计算 [23]:

\[ A_{\mathcal O}(s) \;=\; O\bigl(\mathrm{App}(s)\cap\mathrm{Lic}(s)\bigr), \]

其中 \(\mathrm{App}(s)\) 是规则认定可行的目标集合(FIGHT 需要一个敌对者,TALK 需要一个够得着的人,LOOT 需要一件物品,LEAVE 需要一个出口格,等等;WAIT 总是可行),\(\mathrm{Lic}(s)\) 是生效卡片所许可的目标集合,\(O\) 施加常设命令——每条命令只会删除目标(“别打”、“原地不动”),或把集合收窄到一类目标(“先交谈”)。有两个目标属于地板本身,书永远不能扣下:WAIT,以及退出一场无法继续的对话。剩下的目标再经过两道过滤:执行器是否还能完成它,以及卡住规则(一个目标被反复选择而局面毫无变化时,就被暂停一段成倍增长的时间);然后是经验,它可以删除一个在此处一再害死驾驶员的目标,并可以重排其余目标。一个固定优先级的规则策略在结果中做选择:先求生存(危急时治疗;战斗中装备、战斗或逃跑、结束回合),然后是一个可自由裁量的层级:LOOT、TALK、USE、LEAVE 与 EXPLORE。只有在这个层级中,书才可以按其投票重排目标。图 1 展示了这条链。命令只会收窄、经验不能扩大集合的证明见 [23, 19];该线的测试套件用其中报告的三个边界测试固定它们。

插桩的引擎Fallout CE加补丁:状态与画面输出、动作输入情境报告来自引擎自身数值的事实生效的卡片说明书与攻略,每张附页码凭据被许可的目标生效卡片所许可的可行的目标规则认定当前可做的∩ 许可,再经命令命令只会删去目标可执行、未卡住执行器能完成;看门狗经验死亡、台词、任务:只删除与重排,从不添加规则选择固定优先级执行器接口命令可容许集合 = 可行 ∩ 许可,再由命令收窄攻略:当前任务、先找谁交谈 · 经验:每句台词通向哪里(按对话对象分别记住;为任务而说,为搁置的任务而避开)仅在安静的时刻:领取已获得的等级并分配 · 把进度存入临时存档位 9 和 10(引擎拒绝其他任何存档位)一次决策:约每秒两次(战斗中每秒三次)
图 1. 一次决策。书从两处进入:生效卡片许可目标(上排);攻略还指出当前任务和先找谁(绿色条带)。经验只在可容许集合确定之后才进入,并且只删除和重排。升级与存档在安静的时刻进行,各占一次决策(琥珀色条带)。

3.2 · 生效的卡片

从报告中读出十一个局面事实:在战斗中、附近有敌对者、未持武器但携带了武器、受伤且携带兴奋剂、在对话中、有人可交谈、附近有物品、附近有门、附近有出口、在世界地图上、在地图上。每个事实用一组固定的词汇检索最匹配的卡片;一张卡片许可的,是它自己的文字所谈到的目标(谈到攻击的卡片许可 FIGHT;谈门的卡片许可 USE),每张卡片都附有页码凭据,控制台把它显示在它所许可的目标旁边。检索是基于词的索引,不是语言理解。加载攻略后,当前地图对应的攻略章节也会生效:章节的第一条指示,以及每一页提到在场某人的内容。这两份文档如何被编译成卡片不予公开(§10);此处要紧的是:一张卡片只能许可它自己的文字所谈到的东西。

3.3 · 经验

三种记忆在学习,每一种都只从游戏证实的失败中学习 [19]。证据记忆按局面与目标记录死亡,把一场战斗记到引发它的对话上,拿一个目标与同一局面中的其他目标比较,并且只按失败优先论文中的规则判定一个模式有害——该规则把死亡率的 Wilson 下界与基础率相比较。台词记忆记录说一句台词引发了什么(一场战斗、对话结束、生命值损失、一次死亡),并另外按对话对象记录这句台词把驾驶员带到了哪里:一个人的 “Yes.” 不等于另一个人的。任务记忆在任务文字所指的地点死亡三次之后,把该任务搁置,直到驾驶员的等级提升。学习关闭时,这三种记忆都既不被查询也不被写入。

3.4 · 谁在看,谁在决定

该线还运行着一只小“眼睛”,它根据颜色直方图给屏幕列打标签,由接口报告的矩形来教;它的标签画在控制台上,没有任何规则读取它们。这样一只眼睛作为引擎声明的见证者有多大价值,见一篇配套论文 [24]。决策链中没有神经网络,也没有任何其他经过训练的模型:规则是固定的,记忆是附有背后事件的计数,卡片是带凭据的文字。

4 · 说明书:什么也不扣下的许可,什么也不改变的建议

游戏的印刷版说明书(121 页) [6]被编译成 2,250 张卡片。在加入攻略之前,它是驾驶员唯一的书;我们在这套架构中一本书能扮演的两个角色上分别测试了它。

4.1 · 作为许可

只有当局面提供的某个目标没有被任何生效卡片许可时,一本书才会收窄驾驶员。我们枚举了描述地图的八个事实(在战斗中、附近有敌对者、未持武器但携带了武器、受伤且携带兴奋剂、有人可交谈、附近有物品、附近有门、附近有出口)的全部 \(2^8=256\) 种组合,为每种组合构造一份情境报告,并以说明书作为唯一的卡组,向固定版本的代码询问 \(\mathrm{App}(s)\setminus\mathrm{Lic}(s)\)。表 1 给出三个提交上的结果。在“按目标检索”之前,说明书恰好扣下一个目标 FLEE,发生在有敌对者在附近的 128 种组合中:它建议玩家逃跑的那一页在战斗相关页中排第 16,而每个事实只有六页生效。当每个事实还在其检索窗口内为书中谈到的其他每个目标各带入最佳的一页之后(提交 7acb344),说明书什么也不扣下;再把攻略一起加载,也没有任何变化。这次重测复现了该提交中记录的数字。这套机制是能够扣下目标的(该线的测试从一副合成卡组中撕掉页面,目标随之消失);只是这本书没有扣下。

表 1. 作为许可的说明书:在八个地图事实的 256 种组合上被扣下的可行目标(analysis/manual_licence.py)。
代码卡组扣下某个目标的组合数被扣下的目标
90308cd,按目标检索之前说明书256 中的 128FLEE
7acb344,按目标检索说明书256 中的 0无
6fc5c72,本文说明书256 中的 0无
6fc5c72,本文两本书256 中的 0无

4.2 · 作为建议

在可自由裁量的层级中,生效卡片还会投票:每张卡片为它谈到的目标投票,权重取决于它与当前局面匹配得有多好;规则先取票数最高的目标,平票时按固定顺序。为了知道这是否有作用,驾驶员把每个决策都算两遍——有投票和没有投票——并统计两次选择不同的决策数。在 Shady Sands 的一次在线运行中,以说明书为唯一的书,计数器显示 2,344 中的 0:说明书的投票从未改变所选的目标。该读数(“decisions 2344 | steered 0”)于 9 月 26 日美国东部时间 15:46 取自控制台,记录在该线的开发日志中,也记录在加入攻略的那个提交(27fc090)里。逐决策日志在大约两小时后才开始,因此这个计数无法从日志中重新推导;计数器的代码在固定的历史中(73c9bd4,读数之前的最后一个提交)。

4.3 · 原因

两个结果都源于这本书讲的是什么。表 2 和图 2 对每本书统计:提到攻略十二个地点章节中每一个的卡片、提到攻略 22 个任务中每一个的卡片,以及提到驾驶员在有日志的运行中发出过交谈命令的 17 个单词名字中每一个的卡片(这些是引擎自己的名字,取自日志)。说明书提到两个地点:主角的避难所(出现在开篇设定和提示中),以及第一个任务的目的地 Vault 15(出现在开篇设定和一个世界地图示例中)。它没有提到任何任务。那 17 个人中,它一个也没提到,只有两个出现在配音名单页、写在配音演员旁边。(它的开篇设定和提示确实提到一个角色:主角所在避难所的监督者,而在有日志的运行中驾驶员从未向他发出交谈命令。)在读出 2,344 中的 0 的那一刻,说明书贡献的“Shady Sands”卡片是两条关于城镇地图按钮的指示。一张卡片只能许可或投票给它的文字谈到的东西,而说明书泛泛地谈到每一类动作,却不具体谈任何地点、任务或人物;所以它什么也不扣下,也什么都不偏好。

表 2. 两本书各自提到了什么(analysis/books.py,只读地作用于两副卡组)。至少有一张卡片提到某地点就计入;对说明书,出现在名单页(第 117–118 页)的提及单独计数。
说明书攻略
卡片2,2502,289
提到的攻略章节地点(共 12 个)2(主角避难所:5 张卡片;Vault 15:2 张)12
提到的攻略任务(共 22 个)022
提到的驾驶员交谈对象(共 17 个名字)0(另有 2 个只在名单页)11
游戏说明书(2,250 张卡片)攻略(2,289 张卡片)0%25%50%75%100%攻略设有章节的地点(12)2 / 1212 / 12攻略中的任务(22)0 / 2222 / 22驾驶员交谈过的人物(17 个名字)0 / 1711 / 17说明书:另有 2 个只出现在配音名单中
图 2. 两本书对游戏世界的覆盖。攻略的任务是它自己列出的,因此它按构造提到全部 22 个;要紧的比较是说明书的零。

这是关于这本说明书和这套架构的陈述,而不是关于说明书这一类东西的。《文明 II》的说明书给出了战略建议,一个学习型智能体可以借此赢得更多对局 [2];Fallout 的说明书讲的是界面、角色系统和战斗规则,把世界留给玩家自己。

5 · 攻略:任务、人物,以及一句台词通向哪里

这份攻略是 The Nearly Ultimate Fallout Guide 1.1 版,一份 72 页的社区攻略 [8]。它以与说明书相同的方式被编译成 2,289 张附页码凭据的卡片。它有十二个区域章节,每个地点一章,并在其中八个章节里列出 22 个任务;它的角色设计部分建议提升哪些技能、提升到多少,并把每个特长分入五个等级——驾驶员升级时会用到(§8)。攻略的卡片与说明书的卡片一样按其文字许可目标,而表 1 表明加载它们不会额外扣下任何东西。它的作用来自另外三条渠道。

当前任务。在每个区域中,驾驶员专注于一个任务:游戏自己的任务列表中显示为已接受且未完成的第一个任务;若没有,则是攻略顺序中尚未接受的第一个任务;被搁置的任务跳过。当前任务的用词——去掉任何属于另一个任务名称的词——标记出属于它的台词。

先找谁。区域章节中点名的在场人物最先被接近,按攻略的顺序,当前任务涉及的人排在其他人之前。只有专有名词才算:攻略把城镇卫兵、孩子这类角色用小写书写,而章节开头的题词是引文,不是建议。攻略点名的人值得走任意远的路;其他人只在路径 18 格以内才会被接近。

一句台词通向哪里。一句台词通向哪里是学来的,不是读来的:台词记忆按对话对象记录一句台词把驾驶员带到了哪张地图。有任务在进行时,已知通向该任务地点的台词会被说出;已知通向某个被搁置任务的地点的台词则不说——只要还有别的安全台词。

5.1 · 洞穴:作为目标的任务,以及一句总是把它带回去的台词

Shady Sands 的第二个任务要玩家清理一个满是辐射蝎的洞穴,攻略点名了带你去那里的人(第 19 页)。任务成为当前任务后,驾驶员去了;在 1 级时,它在连续三次控制台运行中死在洞穴里:在任务记忆存在之前,13.4 分钟内 4 次、12.9 分钟内 8 次;在有任务记忆的第一次运行中,16.5 分钟内 10 次(每一次都死在洞穴;analysis/defects.py)。任务记忆确实在三次死亡后搁置了该任务,但驾驶员仍不断回去:Shady Sands 另一个任务的文字里提到了辐射蝎,于是把驾驶员带去洞穴的那句台词被当成了那个任务的台词,而“跟进任务”就意味着说出它。修复(49fb85b)就是上面所说的按对话对象记住目的地,外加一条规则:属于另一个任务名称的词归那个任务所有;该提交记录了修复之前、搁置之后又在洞穴里死了五次。接下来的两次运行——因为驾驶员当时还不会保存进度,所以都从同一个存档开始——分别在 8.8 分钟和 3.6 分钟内零死亡,并各获得 250 经验。这些是在不断变化的代码上的单次短运行;它们表明机制在起作用,而不是其大小。为本文读取时,台词记忆里仍保留着这句台词:对那个对话对象说 “Yes.”,有 32 次通向了洞穴。

5.2 · 升到 2 级的那次对话

在升到 2 级的那次在线运行中(§6,图 3),20:39:54 时驾驶员正在探索 Shady Sands 东半部,够得着的范围里没有新的人可以交谈:可选的目标是离开、探索和等待。当一个 23 格之外的人进入它的报告时,它停止探索,走了过去。对于书中没有点名的人,规则只在路径 18 格以内才提供交谈;而这个人在攻略的 Shady Sands 章节中被点了名(第 18 页)。七秒之后,对话结束时,经验从 700 升到 1,200,角色越过了引擎 2 级所需的 1,000 点门槛。没有攻略,同样的代码会继续探索;它之后是否会遇到他、那又值多少,单凭一次运行我们无法判断。

5.3 · 攻略引导了多少交谈

在归档的决策日志中(从 9 月 26 日 17:38 到 9 月 27 日 00:40 共 50,376 次决策,全部加载了攻略),驾驶员向 26 个对话对象发出了 1,035 条交谈命令。其中 207 条(20%)发给了攻略点名的 11 个人;730 条(71%)发给了引擎只以身份称呼的人,例如镇上的孩子、卫兵、居民和农民;其余 98 条发给了攻略没有用到的单名角色。攻略的顺序是偏好,不是许可:当够得着的范围里没有它点名的人时,驾驶员就和附近的任何人交谈。第 10 节展示了这在三个小时里付出的代价。

没有测量的部分。没有移除攻略的 A/B。驾驶员在控制台上统计过攻略改变了的决策(换了一个目标或换了一个人,并附上起作用的那一页),但这个计数器没有写入磁盘,所以我们只能通过上面的例子和交谈命令来报告它。

6 · 像玩家一样保存进度,却不碰玩家的存档

在 9 月 26 日晚间之前,每一次死亡都会重新载入驾驶员起始时的那个存档位,于是一条命里获得的经验、任务和地图都随之丢失;一个“搁置到驾驶员更强时再说”的任务永远不会回来:驾驶员从来没有变强。保存进度解决了这一点,也使得必须准确说清:一个会存档的学习者可以写什么。

6.1 · 规则

何时
只在安静的时刻(在地图上;没有战斗、对话、行走、视野内的敌对者、伤势、打开的界面或在操控的人),只在稳定时(引擎的路径探针从这里能到达某处,且没有移动命令在这一格失败过),只在自上次存档以来有进展时(获得了经验、到了上次存档没有的地图、任务列表变化、花掉了一次升级,或距上次存档已有 600 次决策,约五分钟),并且距上次存档至少 90 次决策(约 45 秒)。只有当引擎自己的存档计数器增加时,一次存档才算数。
存到哪里
驾驶员的临时存档位 9 和 10,轮流使用,被放弃的存档位优先,这样唯一完好的存档永远不会被覆盖。
死亡之后
引擎自动载入最新的存档。回来后 80 次决策之内的死亡记为该存档的一次“速死”;两次之后,该存档被放弃,改用它之前的那个,再往前就是游戏开始时的存档位。
卡住
试图移动却 150 次决策都没有移动,驾驶员会主动载入上一个存档;引擎会统计被请求的重新载入,所以主动的重新载入永远不会被当成一次死亡。如果从载入某个存档的那一刻起就卡住,那个存档本身就是陷阱,会被放弃。
重启
控制台从同一局游戏最新的完好存档继续。

6.2 · 性质,以及固定它的证据

命题 1(玩家的存档在学习者够不到的地方)。 对任意记忆状态和任意决策序列,驾驶员发出的任何命令都不会使引擎把存档写入 1–8 号存档位,并且驾驶员的引擎从不写入玩家的游戏目录。
论证。(i)引擎运行在游戏目录的暂存副本上(§2)。(ii)驾驶员唯一的存档命令,除非存档位是 9 或 10,否则在引擎中被拒绝,而且拒绝两次:由命令处理器,以及由它调用的存档函数;处理器还会在战斗中、对话中、移动中以及没有存活玩家时拒绝。(iii)游戏的快速存档键会写入最近一次载入或保存的存档位;接口在它每次载入和存档之后都会忘掉这个存档位,于是这个键改为打开存档界面。(iv)驾驶员不发送任何点击,它唯一使用的按键是对话数字键、一个离开交易界面的键,以及 Escape;它的按键表里没有任何能让游戏存档的键(打开存档界面的 F4 或 Ctrl+S、快速存档的 F6、选项菜单中的 S),接口的按键命令也不带修饰键;它的重新载入命令只选择死亡后载入哪个存档。(ii)–(iv)中的每一条都由一项检查固定:该检查读取已提交的补丁和固定版本的驾驶员代码(analysis/pins.py,检查 S1–S6);该线自己的测试固定驾驶员这一侧(任何命令都不会指向玩家的存档位;暂存脚本从不删除驾驶员的存档位)。

这正是 Saltzer 与 Schroeder 意义上的最小权限 [15]:那个从自身死亡中学习的组件,只持有它需要的那一项写权限,别无其他。有两点保留让这一结论保持诚实。第一,游戏自带的存档界面不受接口保护。它距选项菜单只有一个菜单项,而驾驶员确实打开过一次那个菜单——一次 Escape 在对话已经关闭之后才落下(图 5);驾驶员能表达的任何输入都无法从那里走到存档(论证(iv)),但这是驾驶员代码的性质,而不是引擎中的一道关卡。第二,这一性质并非一直成立。在提交 fc2ef32 之前,接口启动时的载入让快速存档位保持在待用状态,因此如果有人在驾驶员游玩时按下快速存档键,就会覆盖被载入的存档位——也就是玩家存档的暂存副本。这是一次自动代码审查发现的(§9)。

6.3 · 在线:十次死亡,每次都回到一个存档

升到 2 级的那次控制台运行持续 55.4 分钟(20:09:50 至 21:05:14,6,924 次决策),并从驾驶员自己的存档继续。它存档 10 次,存入 9 号和 10 号存档位,除一次之外都是轮流:那一次,放弃某个存档之后的下一次存档按规则写入了被放弃的存档位;每一次存档都由引擎的计数器确认。它死亡 10 次,全部发生在升到 2 级之前,时间在第 10.4 到第 26.2 分钟之间。表 3 列出这些死亡:每一次死亡都载入了当时尚未被放弃的最新存档。有一个存档按规则被放弃。第 23.2 分钟,驾驶员到达一个荒漠遭遇时存了档(“一张新地图”);就在下一个决策,一场与两只辐射蝎的战斗开始了。这个存档害死了它两次,分别在上一次载入之后的第 67 和第 69 次决策,并在第 24.0 分钟被放弃;同一张地图上的下一次死亡回到了它之前的那个存档——在山区存的。因此,在进入危险地图时存档并不是免费的:这一次在规则发现之前付出了两次死亡,并在同一张地图上又付出了第三次(§10)。

表 3. 升到 2 级那次运行中的十次死亡(analysis/journey.py,作用于驾驶员的决策日志)。地图名是引擎的:MOUNTN 与 DESERT 是随机遭遇地图,RAIDERS 是掠夺者营地。“最新完好”指当时尚未被放弃的最新存档;在 DESERT1 到达时存入 9 号位的存档,在第 6 和第 7 次死亡之后被放弃。
死亡分钟死于回到存档位最新完好
110.4MOUNTN2DESERT29是
212.2MOUNTN2DESERT29是
314.5MOUNTN2DESERT29是
420.9MOUNTN2MOUNTN29是
523.1MOUNTN1MOUNTN210是
623.5DESERT1DESERT19是
724.0DESERT1DESERT19是
824.5DESERT1MOUNTN210是
925.1DESERT2MOUNTN210是
1026.2RAIDERSMOUNTN210是
升到 2 级后:在 Shady Sands 两半之间切换 491 次40060080010001200010203040501,000 经验升 2 级(stat.cc)第 30.2 分钟升到 2 级两次速死后放弃该存档死亡10 次死亡,每次都载入最新的未被放弃的存档控制台运行开始后的分钟数(20:09:50 开始)经验值经验存档(9 或 10 号位)死亡
图 3. 升到 2 级的那次在线运行(一次运行)。开始时经验 550;在一次山区遭遇的战斗后到 675 和 700;第 30.2 分钟,在一次因攻略而值得走过去的对话结束时到 1,200(§5)。十次死亡(竖线),每一次都回到尚未被放弃的最新存档;方块是存档;叉号是两次速死之后被放弃的存档。升到 2 级之后的阴影区间是 §9 中的缺陷:23 分钟内在小镇两半之间切换 491 次。

7 · 学习开启对比学习关闭

7.1 · 设计

该线的 A/B 工具让两组从同一个存档开始,引擎的骰子在每次载入时固定(种子 1234),每组在一个隐藏的引擎上跑 12 分钟挂钟时间:一组学习开启,一组学习关闭(证据、台词与任务记忆既不被查询也不被写入)。每组使用游戏目录的私有副本,并在自己的记忆副本上工作:要么是在线驾驶员至今所学的快照(“在线记忆”),要么什么也没有(“空记忆”)。两组都保存进度;两组都读两本书。一次运行的两组在同一个进程中先后进行。工具从驾驶员的会话中统计:决策数、死亡数、首次死亡的分钟数、进入的地图、获得的经验、存档数以及所选目标。我们有两次有记录的运行,每种各一次(表 4,图 4)。

表 4. 两次有记录的 A/B 运行,每组一次运行(工具的结果文件 maps/measure-20260926-194427.json 与 -202342.json,以及各组自己的记录)。每小时死亡数按工具的计算给出(空记忆文件中为 29.99 和 79.99,此处四舍五入)。死亡数为记录值,是下界(§7.3)。
在线记忆 · 开启在线记忆 · 关闭空记忆 · 开启空记忆 · 关闭
组开始时间(9 月 26 日,美国东部时间)19:20:1319:32:1719:59:3320:11:38
分钟12.012.012.012.0
决策数1,6191,2751,7741,726
死亡316616
每小时死亡15.080.030.080.0
首次死亡(分钟)8.630.960.750.84
获得的经验+250000
进入的地图3121
存档2122
洞穴任务被搁置开始时即是(快照)不查询运行中学会否
学习开启学习关闭每组一次 12 分钟运行 · 同一存档 · 种子 123412 分钟内死亡次数051015316在线记忆616空记忆首次死亡(分钟)02468108.630.96在线记忆0.750.84空记忆获得的经验01002003002500在线记忆00空记忆进入的地图数0123431在线记忆21空记忆两组都会存档;每组使用私有的游戏副本;测试工具未记录代码版本
图 4. 表 4 的 A/B。每组一次 12 分钟运行,同一存档,种子 1234;两组都保存进度,各自使用一份私有的游戏副本。死亡数为记录值,是下界(§7.3)。

7.2 · 结果

使用在线记忆时,学习开启的一组死亡 3 次,另一组 16 次;它首次死亡在 8.6 分钟而不是 1.0 分钟,获得 250 经验而另一组为零,并进入了三张地图(小镇、掠夺者营地和一次荒漠遭遇),另一组则从未离开它的第一张地图。它在开始时带着在线驾驶员的记忆:洞穴任务在 1 级时已被搁置,之前说过的 1,783 句台词的结果记录,以及已知对某个对话对象说的某一句台词会通向洞穴。从空记忆开始时,两组的首次死亡几乎同时到来——0.75 对 0.84 分钟——在还什么都没学会时理应如此。在 12 分钟里,学习开启的一组死亡 6 次,另一组 16 次:它说了 26 句台词,学会其中一句通向洞穴,有三次死亡被记到洞穴任务上,并在运行中搁置了这个任务(它结束时的任务记录如此写明),它花在逃跑上的决策比花在任何其他目标上的都多(1,774 中的 679)。它没有获得经验。两次运行中,学习关闭的一组都停留在第一张地图上,把时间花在战斗和对话里:除等待之外,它最常见的目标是装备武器、结束回合、战斗、治疗和回复。

7.3 · 这说明了什么,没说明什么

两次运行的方向相同,死亡数相差 2.7 到 5.3 倍,但每组只有一次运行。两组在一台共享的机器上相隔十二分钟运行;骰子在每次载入时固定,但驾驶员的时机并不固定,所以两组在最初几秒内就会分叉,而且在同样的挂钟时间里做出了不同数量的决策(1,619 对 1,275)。工具没有记录代码版本;按开始时间,两次运行使用的是提交 2baf007 与 946fd2b 时的代码树。学习关闭仍保留规则、可执行性检查、卡住规则和存档,所以这项比较隔离的是那三种记忆,而不是任何更广义的“学习”。工具还报告了已知六角格数,但它在两种模式之间不可比,因为在线记忆组是从驾驶员地图记忆的副本开始的;我们把它排除在表 4 之外,列在附录中。

后来发现的两个测试工具缺陷。两次运行都早于两处修复。在 4188965(19:39)之前,驾驶员在彻底卡住时请求的重新载入如果没有被引擎执行,下一次死亡就会被当成那次重新载入而不被计数;空记忆那次运行包含这一修复,但两次运行都早于 fc2ef32——在那之后,由引擎自己统计驾驶员请求的重新载入。测试工具没有记录载入计数器,因此表 4 中的死亡数是下界:死亡可能被漏计,但不会被多计。漏计本身也有上界:卡住规则只允许在 150 次决策之后进行第一次主动重新载入,此后每 300 次决策最多一次,而每一次最多隐藏一次死亡;因此学习开启的组最多死亡 8 次(在线记忆)和 12 次(空记忆),而每个学习关闭的组至少 16 次(analysis/accounting.py)。相比之下,在每一份归档的决策日志中,引擎的载入次数都等于死亡次数加上主动重新载入次数,所以本文中其他所有死亡计数都是精确的。第二个缺陷:同一个临时目录如果第二次交给测试工具,各组不会重新开始(在 fc2ef32 中修复,a31fe6e 又加了一道防护);两次有记录的运行各自使用在其开始时新建的临时目录,都没有复用。

被排除的运行。第一次 A/B 于当天 18:40 开始,当时各组还没有私有的游戏副本,它与在线控制台并排运行。它学习开启的那一组与在线驾驶员共享游戏的世界文件,而在线驾驶员正在同一目录中游玩;同一目录上的两个引擎会互相覆盖世界状态。它没有写出任何结果(日志中只有那一组的开始),而引入私有副本的提交(dee8169)宣布这次运行无效。本文将其排除,不使用其中任何数字。

8 · 成长:像角色界面那样领取一个等级

8.1 · 一个等级的成长在哪里被授予

达到经验门槛会立即提升角色的等级,但在这个引擎中,随等级而来的技能点和特长只在打开角色界面时才被授予:结算在 UpdateLevel() 中进行(editor.cc,固定提交中的第 5199 行),角色界面在非创建角色模式下启动时调用它。每获得一级,它增加 \(5 + 2\cdot\mathrm{IN} + 2\cdot\mathrm{Educated}\) 个未分配技能点(有 Gifted 特质时减 5,最低为 0,最多持有 99),并且每三级(有 Skilled 特质时每四级)在持有特长少于七个时给出一个待选特长。驾驶员从未打开过这个界面,所以到了 2 级,它的等级指示灯一直亮着,而这一级只给它带来了生命值,别无其他。

8.2 · 接口的升级命令

补丁把这段结算移入角色界面与接口共用的两个函数中(一个领取所获得的等级,一个施加特长的附带效果),因此两者不可能授予不同的数量。三条接口命令经由它们执行:领取所获得的等级、把一项技能提升一点、选择一个特长;需要在界面上做第二次选择的两个特长会被拒绝。一个读数报告:指示灯、已领取的等级、未分配点数、每项技能、标记技能、手中武器对应的技能、已有的特长,以及有待选特长时可选的那些。在一份私有的游戏副本上,加入这些命令的提交(ad8d6ea)验证了:一次领取授予的与界面授予的相同;在接口领取之后再打开真实的界面,不会重复授予任何东西;无效的技能和特长会被拒绝。

8.3 · 驾驶员的选择

在一个安静的时刻,驾驶员领取所获得的等级;在随后的一个安静时刻把它花掉。技能点花在它实际使用的东西上:先是手中武器的技能,然后是标记技能(最低的优先),直到攻略给出的标准(“早期提升到 100% 左右”,然后武器技能到 150%,第 7 页)。急救一点也不给:驾驶员用兴奋剂治疗,从不使用这项技能,而攻略把它列为不值得投点的技能(第 7 页)。特长选择在攻略五个等级中排名最高的可选项(第 8–9 页)。没有加载攻略时不选特长,它保持待选。只有当引擎的读数显示出来,一步才算数;花掉一次升级,算作下一次存档的进展。

8.4 · 在线

9 月 26 日 22:24:57,在一次以升级代码(ad8d6ea)启动的控制台运行的第一秒,驾驶员领取了 2 级,并把点数花在近战武器上,从 52% 到 72%(手中的武器是一把刀);22:24:58,它以“花掉了一次升级”为理由存入 9 号位——驾驶员只在引擎显示出这一步之后才会给出这个理由。2 级时没有待选特长。

8.5 · 审查发现的一个缺陷:引擎永远不会报告的上限

升级代码的第一版把引擎的技能上限抄成了 300,并引用了一个头文件。引擎的上限是 200(SKILL_LEVEL_MAX,skill.cc 第 31 行):技能被截断在它,而处于上限时加点会被拒绝。在 200 时,驾驶员的计划会一直存在,每一点都会被拒绝,同样的一批命令每 20 次左右决策就会被重新发送,无休无止。测试抓不到它,因为它们用 300 作为“处于上限”的值,而引擎永远不可能报告这个值。对该提交的另一次自动审查在驾驶员提升的任何技能接近上限之前就发现了它(当时正在提升的那项技能是 72%)。修复(6fc5c72)把上限设为 200,在情况没有变化时不再重发引擎拒绝过的步骤,并加入一个测试,从 C++ 源码中读回 SKILL_LEVEL_MAX、两个徒手/近战技能的索引以及两个被拒绝特长的索引,于是一个抄错的字面量会让测试套件失败。我们在固定提交上重跑了该测试套件,并在引擎的固定版本上导出了引擎源码:461 通过,1 个严格预期失败(§9)。

9 · 方法:记录暴露出的缺陷

上面的每一个结果都是经由缺陷得到的,而这些缺陷是方法的一部分:每一个都是在驾驶员自己的记录中、通过穷举检查或通过自动代码审查发现的,并在一个说明错在哪里的提交中修复,几乎总附有一个固定修复的测试。表 5 列出与本文结论相关的缺陷,以及暴露每个缺陷的计数——凡有归档日志之处都已重新计算。有两个日志计数比提交信息中说的更大,因为那两条信息是在运行仍在进行时写的;以日志为准,附录 A 注明了两者。

表 5. 已发现并修复的缺陷(该线的提交信息;计数由 analysis/defects.py、analysis/journey.py 与 analysis/stall.py 只读地从归档决策日志重新计算)。
缺陷发现途径记录修复
说明书从未许可在战斗中逃跑:它关于逃跑的那一页排第 16,只有六页生效穷举检查在 256 种事实组合中的 128 种里扣下了 FLEE7acb344
一句台词的结果被早读了一个 tick;责任只落在一场战斗最后的六个决策上驾驶员的记录在同一名守卫面前死亡 49 次(见 [19])7d52323,73c9bd4
一个被搁置的任务又经由一句被当作另一个任务台词的话重新进入驾驶员的记录16.5 分钟内在洞穴死亡 10 次(该提交记录搁置之后又死了五次)49fb85b
同一个游戏目录上的两个引擎第一次 A/B该次运行无效(§7)dee8169
引擎没有执行的主动重新载入,会让下一次死亡不被计数编写代码的会话自查A/B 的死亡计数因此只能给出界限(§7.3)4188965,fc2ef32
卡在地图边缘;在那里存的档成了回归点驾驶员的记录两次“彻底卡住”重新载入,都回到同一个存档450b325,2baf007
一次没走到目的地的远距离行走,把那个出口永久封死驾驶员的记录在一张地图上 2,736 次决策,经验不变(28.2 分钟)946fd2b
驾驶员自己迟到的 Escape 打开了选项菜单,被当成有人在操控驾驶员的记录连续 1,642 次决策处于等待(图 5)7056bda
在一个已走遍的小镇的两半之间来回往返驾驶员的记录升到 2 级后 23 分钟内切换 491 次;接下来 3.08 小时内 54 次31dad05,bb3abb5
接口载入之后快速存档位保持待用,以及另外 10 项自动审查12 项发现:修复 11 项,1 项有争议fc2ef32
技能上限被抄成 300;引擎的是 200自动审查测试使用了一个不可能出现的值6fc5c72
上游引擎:战斗 AI 中一处未初始化的读取,被优化器变成了空指针解引用一次崩溃由该线的补丁测试固定在补丁中

审查。该线的代码是在 AI 编程助手的协助下编写的,其历史中记录的每一次审查都是自动的、只读的审查;历史中没有记录任何人工代码审查。一个独立的 AI 编程工具审查了三个范围:9949c65..73c9bd4(七项发现;六项在 401b168 中修复,一项按设计不改)、对这些修复及其后提交的第二轮审查(又发现一项,在 2c5e584 中修复),以及 2c5e584..41919c8(12 项发现;11 项在 fc2ef32 中修复,1 项有争议:台词记忆有意按名字比较攻击者,因为威胁镇上的一名卫兵会让所有卫兵翻脸,现在有一个测试固定了这一选择)。最后这次审查修复的发现包括 §6 中处于待用状态的快速存档位、一次失败的存档打开了引擎的模态错误框(驾驶员根本看不到它)、一次真实的死亡可能被当成驾驶员请求的重新载入(现在由引擎统计这些请求),以及一个已不存在、却仍被认为是最新的存档文件。在该工具达到使用上限期间,编写代码的那个助手的一个独立审查代理审查了升级提交,发现了 §8 中的技能上限问题;编写代码的会话还两次对自己最近的工作做了自查(4188965、a31fe6e),其中发现了 §7.3 中的死亡计数问题,以及 A/B 测试工具中一处不安全的清理。外部工具对之后那些修复提交的审查在截止之前启动,但因使用上限立即失败;这次审查仍然欠着。这些修复都附有测试和变异检查,记录在各自的提交信息中(fc2ef32 中 14 个变异体全部被捕获,6fc5c72 中 47 个全部被捕获)。

重跑测试套件。我们从导出的副本在四个提交上重跑了该线自己的测试套件,不启动引擎,并在引擎的固定版本上导出常量测试要读取的引擎源码(表 6)。每次运行都与其提交信息中给出的数字一致(凡给出之处)。那一个预期失败是有意保留的:在只经历过徒手时的死亡之后,最宽的模式会判定“持武器战斗”有害,而这从未尝试过;补救办法——在从未尝试过的情境中探索被判定有害的目标——尚未构建 [19]。

表 6. 从导出副本重跑的该线测试套件(analysis/run_suite.sh,9 月 27 日)。
提交加入了什么通过严格预期失败
fc2ef32外部审查的修复4131
bb3abb5不再在已走遍的地图之间来回4221
ad8d6ea经由角色界面结算的升级4561
6fc5c72引擎真实的技能上限;本文的代码4611

上游引擎中的一个缺陷。在固定版本的上游引擎中,一个战斗 AI 辅助函数清空三个输出指针,但对第三个是“透过它读取”来检查的,而它唯一的调用者传入的是一个未初始化局部数组中的一个元素。这次读取是未定义行为。该线补丁中的注释记录了编译器如何处理它:辅助函数被内联后,优化器删掉了一处空指针检查,于是任何在被击中之前加入战斗的生物都会解引用空指针,引擎随之崩溃。补丁改为检查指针本身——就像它上面两行已经做的那样——并有一个测试固定全部三处。我们确认了上游源码确实透过指针读取、并传入了未初始化的元素(analysis/pins.py,检查 E1);我们没有重新构建引擎来确认机器码。

游戏的一帧画面:稀疏灌木的荒漠,右侧站着几名武装卫兵,中央打开着游戏的选项菜单,按钮为 Save Game、Load Game、Preferences、Exit Game 和 Done;底部界面栏的消息记录报告遇到了一名旅行商人和卫兵。
图 5. 游戏的研究截图:插桩引擎于 9 月 26 日 20:00 写出的一帧 640×480 画面。在一次荒漠遭遇中,驾驶员自己本想用于一段已经结束的对话的 Escape,打开了游戏的选项菜单;驾驶员把打开的界面当成有人在操控,于是等待了 1,642 次决策(表 5)。菜单的第一项就是游戏自带的存档界面,接口并不保护它(§6)。游戏内容 © 其权利人;为研究目的复制。

10 · 未解决的问题与局限

状态。研究原型,在一款游戏中。该线把自己标注为原型:除了一台机器之外没有部署路径,也没有告警。这里没有任何东西是经过认证的安全系统,而一款死亡后可以重新载入的游戏,比任何物理系统都宽容得多。每个数字都出自我们自己、来自驾驶员自己的记录,尚无第三方复现。

10.1 · 毫无经验增长的三个小时

升到 2 级之后,控制台运行了 3.08 小时(21:35 至 00:40,21,833 次决策),分为三次控制台运行,分别基于提交 bb3abb5、ad8d6ea 和 6fc5c72;最后 100.6 分钟运行的正是本文的代码。第一次决策与最后一次决策时经验都是 1,200(图 6)。90.7% 的决策发生在 Shady Sands 的三张地图上。驾驶员因“彻底卡住”而载入上一个存档 31 次(三次运行中分别为 10、7 和 14 次)。它死亡 7 次,全部在最后一次运行中:在 2 级时死在洞穴里五次——洞穴任务在三次死亡后再次被搁置,之后又在那里死了两次,因为它最近的两个存档都是在洞穴里存的,每次死亡都把它带回其中一个;另外两次死在一次荒漠遭遇中。引擎拒绝了 572 条行走命令(因为有出口格挡路)和 319 条“从脚下这一格离开”的命令。69.6% 的决策是 WAIT:8,383 次在等一次行走结束,6,761 次无事可做。它没有携带兴奋剂,手里始终是一把刀。

3.08 小时内 21,833 次决策;首次与最后一次决策时经验都是 1,200按目标等待行走完成8,383 (38%)等待6,761 (31%)探索3,764 (17%)按地图Shady Sands 东区11,936 (55%)Shady Sands 西区7,059 (32%)其他地图2,038 (9%)交谈: 1,415; 离开、旅行: 773; 其他: 737洞穴: 800驾驶员自己的决策日志,21:35–00:40:三次控制台运行,提交 bb3abb5、ad8d6ea 与 6fc5c72
图 6. 升到 2 级之后的三个小时(一次控制台会话,三次运行)。上:按目标划分的决策。下:按地图划分的决策。经验没有变化。

该线自己的诊断在这次运行之后写成,并随其后的工作一起提交(41d3dcc),指出了三个原因。驾驶员被困住了:踏上出口格就会离开地图,所以第一步会跨过出口格的行走都被拒绝,而“离开”这个目标选中的是它脚下的那一格。它没有计划:攻略是一叠生效的页面,它们许可、投票并点名视野里的人,但没有任何东西把一个步骤——例如“去和这个人谈,他会透露下一批城镇”——一直保持到游戏表明它已完成;于是驾驶员和附近的任何人交谈(在所有有日志的运行中,71% 的交谈命令发给了只以身份称呼的人;§5)。而且它的战斗很弱。随后的工作——一个把攻略的指示作为步骤保持、直到游戏自身的事实将其关闭的战役层,绕开出口格的行走,以及按引擎命中数值选择武器——是在本文截止之后提交的,不在本文评估之内。结果就是它本来的样子:一页一页读的攻略,还不是一个计划。

10.2 · 未解决的问题

武器与角色配置
驾驶员用刀战斗,并把 2 级的点数花在近战武器上,而角色被标记的战斗技能是徒手;该线后来的诊断测得,对同一个目标,引擎给出的命中率用刀是 42%,用物品栏里带着的指虎是 72%。按引擎自己的数值选择武器是截止之后的事。
战斗战术
洞穴任务在 1 级时击败了驾驶员(三次运行中在洞穴死亡 22 次),在 2 级时又一次(五次)。用刀时,日志记录的引擎对辐射蝎的命中率在 30% 到 62% 之间。规则按行动点算术战斗,每回合一次攻击,血少时用一支兴奋剂;它们不会走位、不会退到掩体后,也不会利用同伴。
一到达就存档
在一场战斗即将开始的地图上一进入就存档,会让这个存档成为回到那场战斗的入口:在升到 2 级的那次运行中,规则放弃它之前付出了两次死亡(§6);在那三个小时里,它在洞穴中存了两次档(一次在到达时,一次在那里第一次死亡两秒之后),并在洞穴里死了五次。
容器与交易
接口没有搜索容器的命令,驾驶员也会离开每一个交易界面;两者都未被驱动。
一次未解释的崩溃
该线的笔记把一次新游戏中战斗时的崩溃列为未解释。没有保留它的崩溃报告,而本文不运行引擎,无法检查它。
过度泛化
§9 中的严格预期失败会一直留在测试套件中,直到对被判定有害的目标的探索被构建出来。
游戏自带的存档界面
它不受接口保护(§6);在那里加一道关卡,或在驾驶员的副本中把玩家的存档位设为只读,就能补上命题 1 的剩余缺口。
攻略效果的大小
没有移除攻略的 A/B,它的变化计数器也没有被保留。

10.3 · 效度威胁

一个存档、一个角色、一个引擎版本;结果可能取决于角色配置和存档所处的位置。A/B 每组只有一次运行,按挂钟时间计,代码版本由时间戳推断,其死亡计数是下界(§7.3)。归档的运行不是一个受控序列:大多数之间隔着一个提交,每一个描述的是它那一刻的代码。构建驾驶员、运行它并撰写本文的是同一个团队,记录是驾驶员自己的,由本身还在变化的代码写出;提交信息中有两个计数比日志显示的小,我们已更正。代码是在 AI 编程助手的协助下编写的,只经过自动审查(§9)。2,344 中的 0 这个计数只以控制台读数的形式存在于开发日志和一条提交信息中。所有这些都发生在一款游戏里。

10.4 · 本文不公开的内容

这两份文档如何被编译成卡片、如何检查来源之间的矛盾、出处如何存储,以及准入门在内部检查什么,均不予公开。本文陈述的是它们的接口与测得的行为:附页码凭据的卡片、按卡片自身文字的许可,以及上面报告的计数。

12 · 结论

一本游戏说明书,被编译成带出处的卡片并成为规则驾驶员唯一的许可来源,它许可了局面所提供的每一个目标,却没有改变 2,344 次在线决策中的任何一次,因为它讲的是游戏如何操作,而不是游戏里的任何东西。一份攻略改变了驾驶员的行为:追求哪个任务、走向谁,以及——结合驾驶员自己对每句台词通向哪里的记忆——说什么。把角色带到 2 级的那次对话,只有攻略让它值得走过去。像玩家一样存档让驾驶员保住了一条命所获得的东西,而规则只给学习者两个临时存档位,其余一概由引擎拒绝;在同一存档、同一骰子下,学习开启对关闭记录到的死亡为 3 对 16、6 对 16(最坏情况为 8 和 12 对至少 16),每组一次运行。成长经由角色界面自身的结算而来,一个抄错的技能上限被自动审查发现,并对照引擎源码固定下来。随后,停在同一经验值上的三个小时显示出下一个局限:生效的页面还不是计划。这些结果每一个都很小,每一个都能追溯到一个文件。

参考文献

  1. M. Alshiekh, R. Bloem, R. Ehlers, B. Könighofer, S. Niekum, U. Topcu. Safe reinforcement learning via shielding. In Proc. AAAI Conference on Artificial Intelligence, 2018.
  2. S. R. K. Branavan, D. Silver, R. Barzilay. Learning to win by reading manuals in a Monte-Carlo framework. Journal of Artificial Intelligence Research 43:661–704, 2012. doi:10.1613/jair.3484.
  3. M. Chen, Y. Li, Y. Yang, S. Yu, B. Lin, X. He. AutoManual: constructing instruction manuals by LLM agents via interactive environmental learning. In Advances in Neural Information Processing Systems (NeurIPS), 2024. arXiv:2405.16247.
  4. A. Ecoffet, J. Huizinga, J. Lehman, K. O. Stanley, J. Clune. First return, then explore. Nature 590(7847):580–586, 2021.
  5. Interplay Productions. Fallout: A Post Nuclear Role Playing Game. Computer game, 1997.
  6. Interplay Productions. Fallout game manual, printed with the game, 1997 (121 pages in the scanned edition used here).
  7. Fallout Community Edition contributors. fallout1-ce: a re-implementation of the Fallout engine. github.com/alexbatalov/fallout1-ce; used at commit 0609bcf (15 January 2025) with an instrumentation patch.
  8. P. Jorner. The Nearly Ultimate Fallout Guide, version 1.1. Community walkthrough, 72 pages (PDF edition, 2017).
  9. H. Küttler, N. Nardelli, A. H. Miller, R. Raileanu, M. Selvatici, E. Grefenstette, T. Rocktäschel. The NetHack Learning Environment. In Advances in Neural Information Processing Systems (NeurIPS), 2020. arXiv:2006.13760.
  10. J. E. Laird, A. Newell, P. S. Rosenbloom. SOAR: an architecture for general intelligence. Artificial Intelligence 33(1):1–64, 1987.
  11. D. S. Nau, T. C. Au, O. Ilghami, U. Kuter, J. W. Murdock, D. Wu, F. Yaman. SHOP2: an HTN planning system. Journal of Artificial Intelligence Research 20:379–404, 2003.
  12. J. Orkin. Three states and a plan: the A.I. of F.E.A.R. Talk, Game Developers Conference, 2006.
  13. D. Paglieri et al. BALROG: benchmarking agentic LLM and VLM reasoning on games. In International Conference on Learning Representations (ICLR), 2025. arXiv:2411.13543.
  14. A. S. Rao, M. P. Georgeff. BDI agents: from theory to practice. In Proc. First International Conference on Multi-Agent Systems (ICMAS-95), AAAI Press, 1995.
  15. J. H. Saltzer, M. D. Schroeder. The protection of information in computer systems. Proceedings of the IEEE 63(9):1278–1308, 1975.
  16. G. Wang, Y. Xie, Y. Jiang, A. Mandlekar, C. Xiao, Y. Zhu, L. Fan, A. Anandkumar. Voyager: an open-ended embodied agent with large language models. arXiv:2305.16291, 2023.
  17. Y. Wu, Y. Fan, P. P. Liang, A. Azaria, Y. Li, T. M. Mitchell. Read and reap the rewards: learning to play Atari with the help of instruction manuals. arXiv:2302.04449, 2023.
  18. Y. Wu, S. Prabhumoye, S. Y. Min, Y. Bisk, R. Salakhutdinov, A. Azaria, T. Mitchell, Y. Li. SPRING: studying the paper and reasoning to play games. arXiv:2305.15486, 2023.
  19. Perslis Research. Fail-First Models: failure becomes structure, structure changes the next attempt. 2026. research.perslis.com/fail-first
  20. Perslis Research. Inference Placement: where learned inference earns authority in a symbolic system. 2026. research.perslis.com/inference-placement
  21. Perslis Research. Rules at the Wheel: tanks, language models and an honest loss. 2026. research.perslis.com/tank-arena
  22. Perslis Research. We Tried to Train It In: negative results on teaching small models to ground facts. 2026. research.perslis.com/training-grounding
  23. Perslis Research. VDSG: a commanded admission-control runtime for autonomous agents. 2026. research.perslis.com/vdsg
  24. Perslis Research. A Witness, Not a Detector: checking what a game engine claims against the pixels. 2026. research.perslis.com/witness-eye

附录 A · 每个数字的来源

本文的每个数字都读自该线自己的文件,或由本文 analysis/ 目录中的只读脚本从这些文件重新计算;CLAIMS.md 把每一项结论对应到它的文件和行。除标注 analysis/ 者外,路径均相对于该线(fallout-floor/)。

结果来源
2,250 与 2,289 张卡片;提到的地点、任务与人物;交谈命令两副卡组,以只读方式打开;固定版本的卡组代码;归档的决策日志(analysis/books.py)
256 种事实组合上的许可测试90308cd、7acb344、6fc5c72 处的固定代码(analysis/manual_licence.py)
2,344 次决策中被引导 0 次9 月 26 日美国东部时间 15:46 的控制台读数,见该线的开发日志(复制到 analysis/evidence/);提交 27fc090;计数器代码见 73c9bd4
洞穴运行;选项菜单等待;困在小镇的运行;恢复陷阱maps/decisions.*.jsonl(analysis/defects.py)
升到 2 级的运行:经验、存档、死亡、被放弃的存档、切换maps/decisions.journey1.jsonl(analysis/journey.py)
升到 2 级之后的三个小时;在线的升级领取maps/decisions.jsonl.1,快照在 analysis/evidence/(analysis/stall.py)
A/B 结果maps/measure-20260926-194427.json、-202342.json;各组记录复制到 analysis/evidence/ab-arms/(analysis/ab.py)
存档拒绝、快速存档位、驾驶员输入、等级结算、技能上限、上游的读取6fc5c72 处已提交的补丁与驾驶员代码;上游 0609bcf(analysis/pins.py,全部检查通过)
每份归档日志中的死亡计数核对;A/B 的最坏情况日志中的载入与死亡计数器;2baf007 与 946fd2b 处卡住规则的常量(analysis/accounting.py)
四个提交上的测试计数analysis/run_suite.sh,作用于导出副本
审查发现、变异计数、日期该线的提交信息(fc2ef32、6fc5c72,以及表 5 中每一项修复)

被日志更正的两个计数。修复出口封死问题的提交(946fd2b)说驾驶员在一张地图上花了 2,297 次决策;那次运行的归档日志中有 2,736 次,全部在那张地图上。修复选项菜单等待问题的提交(7056bda)说驾驶员等待了 300 次决策;日志中有连续 1,642 次等待决策。两条信息都是在运行仍在进行时写的;本文采用日志。

表 4 未列出的 A/B 细节。各组结束时已知的六角格数(驾驶员的地图记忆;在线记忆组从它的副本开始):在线记忆,学习开启 26,674,关闭 19,619;空记忆,6,825 与 2,121。最常选择的目标:在线记忆开启组,探索 582、结束回合 358、等待 207、战斗 164;关闭组,装备 328、结束回合 248、回复 207、等待 204;空记忆开启组,逃跑 679、结束回合 315、探索 305、等待 175;关闭组,结束回合 391、装备 389、等待 246、战斗 244。

图。图 1–4 与图 6 由同一个脚本从本文的分析输出绘制,PDF 与本页共用。图 5 是插桩引擎在它所说明的那次运行中写出的一帧画面,作为研究截图复制。

如何引用

Perslis Research. Reading the Manual Is Not Enough: A Rule Pilot in Fallout (1997). Research prototype, September 2026. https://research.perslis.com/fallout-manual

@techreport{perslis2026falloutmanual,
  title       = {Reading the Manual Is Not Enough: A Rule Pilot in Fallout (1997)},
  author      = {{Perslis Research}},
  institution = {Perslis Research},
  year        = {2026},
  month       = {9},
  note        = {Research prototype; games; not a certified safety system.},
  url         = {https://research.perslis.com/fallout-manual}
}