PERSLIS DEFENSE · 演示 · VDSG · 受指挥的运行时
VDSG。看运行时驾驶。
下面是"地板"在 id Software 的 《DOOM》(1993,共享版 IWAD)与 《德军总部 3D》(1992)里掌舵,走的是与其他每一章相同的契约:引擎状态变成事实,规则从可容许集合中选出一个目标,证据记忆只在集合内部排序,执行器按下按键。回路里没有任何模型。页面上的每一个决定,都可以从旁边的态势报告中解释出来。
操作员用语言指挥它——别开火、只用霰弹枪、原地待命——而一条指令只能收窄驾驶员可做之事。它从不发明动作,它同时约束学习者与规则;当世界无法满足它时,每一次决策都会给出理由拒绝,直到可以满足为止。
你看到的是一次录制的运行,由回路当时发布的快照逐面板回放;实时控制台在操作员自己的机器上运行(自己运行)。这里没有任何东西是手工动画。
控制台
从上面的画面按颜色读出的 V-Card。眼睛在游戏进行中由引擎教导——地图说那个方位有一扇门,深度说它多远——然后自己给画面贴标签、测距离。agree 是它与地图一致的频率;卡片上的时间,是按里程计实测的步速走到那里所需的时间。
规则在此处可以追求的每一个目标。指令会从这个集合里移除目标;证据记忆只在剩下的范围内排序,所以学习永远不可能采取被禁止的动作。
等待录制…
这是全部输入,由引擎状态构成。唯一读取像素的是眼睛,而它的卡片只是第二证人,从来不是地图。
- 尚未有规则被拓宽——一条规则需要足够多的有害遭遇,才能明显高于基础率
● VDSG 原型 · ViZDoom 运行 DOOM1.WAD v1.8 · 打了探针的 ECWolf 运行 WL6 数据 · 研究赛道,未经任何鉴定
你在看什么
引擎状态 → 态势报告 → 规则(从可容许集合中选一个目标) → 指令 → 证据 → 执行器
▲ │
└──────── 目标、理由、回执、画面 ────────────────┘
七个目标——HEAL · DODGE · RETREAT · ATTACK · SEARCH · RESUPPLY · EXPLORE——按固定优先级排列,每一个只在其对象出现在雷达上时才可容许:看不见的东西不能 ATTACK,没有医疗包不能 HEAL。规则是约 300 行普通 Python,没有权重、没有状态;同一份报告每次给出同一个目标。证据记忆就是第二章里 arcade-floor 的机制,只在可容许集合内部排序,从不越界。
导航器在引擎自己的扇区几何上规划:一张占据栅格,带有门、升降台、单向台阶(Doom 能爬 24 单位、能从任何高度跳下)以及从 WAD 读出的带钥匙的门;出口开关来自地图自身的线特殊值,并与引擎的出生点核对过。页面上的每个决定都是一句话——去出口开关、在黄门前而没有黄钥匙、捡到了红钥匙。
指挥它
指令只收窄
别开火移除扳机;只管闪避移除 DODGE 之外的所有目标;只用霰弹枪锁定武器。解析器是一套固定短语词表加显式否定——不是模型——所以它不认识的文字会被拒绝,从不猜测。一条指令可以清除按键;它唯一会设置的,只有它点名的那把武器。
拒绝时给出理由
没有携带霰弹枪时的只用霰弹枪,会在上方的回执面板里每一次决策都显示已拒绝——未携带霰弹枪,并在捡起一把的那一刻生效。指令同样约束证据记忆:学习只能在指令留下的范围内排序,因此不可能采取你禁止的动作。在五个位点强制执行,各有一条回归测试。
在 DOOM 录制中,操作员下达了六条指令——别开火、恢复原状、只用霰弹枪、原地待命——页面按当时的情形显示每一条被接受、被拒绝或被撤回。在控制台里按下其中一条即可跳转到那一刻。
眼睛
能给画面贴标签并测距的最小视觉系统:把串流画面缩到 64×40,九位色彩偏向暗端(每通道两位会把真实 DOOM 80% 的像素塞进同一个桶),每个类别一张颜色直方图——天花板、地板、墙、门、敌人、拾取物——一列像素归于最能解释它的那个类别。没有任何训练:它是在游戏进行中由引擎教出来的。在 Doom 上,标签缓冲区说出精灵的名字,栅格说出门在哪里,深度缓冲区说出有多远;在德军总部上,每列一条射线穿过瓦片地图做同样的事。然后它自己给画面贴标签,页面显示直方图在填满、与教师的一致率以及测距误差。
距离按光线投射引擎的画法学习:屏幕高度 × 距离是一个常数,所以 距离 = k / 高度。在 120 秒的运行中实测:DOOM E1M1 一致率 78–92%;德军总部 MAP01 79%,k = 33,理论值 32,测距误差 ±7%。眼睛自己在 Doom 上的测距为 ±25–65%——暗色调色板让远处的地板和墙颜色相同——因此凡是引擎有深度缓冲区的地方,驾驶员以它为第一证人,眼睛的估计显示在旁边(眼睛估计 3.8 米)。深度缓冲区的尺度是实测而非假设:与垂直距离线性相关,墙每级 7.64 单位,精灵每级 7.35,基于 1,336 + 367 个可信样本,残差小于一个单位。
多远,多快
里程计
驾驶员实际移动与转向的速度,从每条命令产生的结果中读出——取干净的全速前进样本的第 80 百分位,所以撞墙不会拉低它。DOOM:每次决策 47 单位,12.8 米/秒,两秒内完成校准;德军总部:0.7 格。于是每张卡片都带上了时间:门 · 灰 · 右 12° · 4.2 米 · 0.4 秒可达。
卡死规则:已校准、已定尺
约束每一个会移动的目标的核心规则:没有向前移动 → 转身;同样的移动反复出现 → 卡死,脱出,绝不重新规划回同一个循环。没有移动如今以实测步速为准——不到命令本应走出距离的 30%。脱出动作朝眼睛看见的最深开阔处转身(45–150°,按引擎的转向速率折算成决策数),再以实测步速走出该深度的一半。前方开阔却走不动,说明有眼睛看不见的东西挡着——一道台阶、一具尸体——于是像以前一样左右交替。
校准以校准该有的方式找到了一个 bug:德军总部赛道一直在用 DOOM 的单位跑卡死规则——每次决策五格——1,014 次决策中 150 次卡死,每八次一次。换成格之后:9 次。
实测
| 项目 | 实测 | 出处 |
|---|---|---|
| 随机 / 规则 / 规则 + 记忆 | 32 个种子上回报 3.2 / 17.9 / 20.3 —— 持平而非胜出(配对 t = 0.78) | Freedoom 竞技场,第二章 |
| 击杀归因 | KILLCOUNT 是地图的计数器:被命令永不开火的驾驶员仍被记了 55 次击杀。造成的伤害才是诚实的数字。 | 缺陷 8,已公开 |
| E1M1(Hurt Me Plenty) | 通关:52 秒到达出口开关,4 次击杀 | 真实 DOOM1.WAD |
| E1M1(Nightmare) | 8 次尝试未通关,23–37 秒死亡 | 真实 DOOM1.WAD |
| E1M2(Hurt Me Plenty) | 10–20 秒拿到霰弹枪,持有红钥匙;167–207 秒死亡,17–18 次击杀。在有武器寻找之前,它曾从链枪 8.8 米、霰弹枪 16.7 米处走过,两者都从未进入视野,最后拿着手枪死去。 | 真实 DOOM1.WAD |
| 眼睛 | 一致率 78–92%(Doom)、79%(Wolf);测距 ±7%(Wolf)、±25–65%(Doom,自行测距) | 120 秒运行 |
| 里程计 | 每次决策 47 单位 = 12.8 米/秒(Doom),0.7 格(Wolf) | 约 2 秒内校准 |
| 单位错误的卡死规则 | 每 1,014 次决策 150 → 9 次卡死 | 德军总部 MAP01 |
| 德军总部的门 | 1,275 次决策中 583 次耗在门前;每 0.7 秒重按一次 USE 会让 ECWolf 正在打开的门反向——现在只在门关着时按一次 | MAP01 |
每个数字都在录制上面这次运行的同一台机器上自行测得;负面结果一并列出。这里没有任何东西经过鉴定。
自己运行
实时控制台从本地引擎提供同一个页面,并接受你随时输入的指令。它需要游戏数据:DOOM1.WAD 由 1995 年的共享版安装程序重建,德军总部的 WL6 文件来自 1992 年的发行版;两者都在档案库中,都不随代码仓库分发。
python -m doom_floor.console --port 8099 --game doom-e1m1 --skill 5 # DOOM,Nightmare 难度,78 个关卡可切换 python -m wolf_floor.console --port 8098 --level MAP01 --skill 3 # 德军总部 3D,60 层
然后打开 http://127.0.0.1:8099:在导航器里选游戏、关卡和难度,按下连续通关让它每次出口后前进、每次死亡后重试,再输入一条指令。
边界
原型。研究赛道,未经任何鉴定。驾驶员能在 Hurt Me Plenty 难度通过 E1M1,Nightmare 难度不能;它还不能通过 E1M2;它在 Doom 上的自行测距远差于德军总部,原因如上所述。回放是一段录制:显示的指令是在录制时下达的,页面不接受新的指令。眼睛只按颜色贴标签,并对此坦诚——面板上的一致率数字就是全部主张。
第六章的开放问题在这里同样成立:当风险与目标耦合时,仅靠规避失败学不会目标。Doom 正是测量这一点的地方。