论文解读·

GameLogicBench:如何逐帧验证 Coding Agent 写对了游戏逻辑

从 Godot 的场景树与物理帧讲起,拆解 GameLogicBench 如何用 72 个任务、403 个手工场景、1,451 个测试用例和逐帧断言评测游戏逻辑。

页数
26
形式
交互图解
更新
2026.09.22
文章目录

Xinyu Che, Yunfei Ge, Shihao Li, Yanchen Liu, Hang Yan, Xinping Lei et al. · arXiv:2609.21562v2 · 2026-09-21

INTERACTIVE PAPER DECK26 页交互图解:从 Godot 的物理帧到 mutant 校准 展开阅读

使用按钮或 ← → 翻页,F 进入或退出全屏手机端建议横屏阅读

一局游戏结束时看起来正确,不代表它在中途没有违规则。GameLogicBench 把这个差别变成了可重复执行的测试:Agent 在 Godot 项目里补上玩法逻辑;隐藏的判分程序(judge)选择不同场景和随机种子,在每一个 simulation tick(仿真更新步)检查位置、时间、资源和事件顺序是否始终满足规则。

72Godot gameplay-logic 任务
403人工设计的测试场景(scenario)
1,451scenario 经 seed 取值后得到的 test case
52.78%论文中最佳单次 solve rate
PART 0 · READING PRIMER

先认识 Godot:为什么这篇论文总在谈 Scene、Node 和 tick

Godot 用 scene(场景)和 node tree(节点树)组织游戏。角色可以是一个 CharacterBody2D 节点,下面挂着碰撞体、贴图和相机;关卡 scene 再把这棵角色树实例化进来。脚本通常附着在某个 node 上,通过引擎回调推进状态,再用 signal(信号)通知其他 node。Godot 官方文档把 node 称为游戏的基本构件,把 scene 描述为可保存、可重复实例化的 node tree。

Scene / Node tree场景是节点树;节点负责渲染、物理、声音、输入等具体工作。换一个节点或连线,就可能改变整条运行路径。
GDScriptGodot 自带的脚本语言。论文只评测 Godot 4.4 与 GDScript,不覆盖 C#、C++ 或其他引擎。
Simulation tick引擎推进一次仿真的离散步骤。物理逻辑通常写在 _physics_process(delta) 中;论文的 judge 以固定 timestep 运行并逐 tick 断言。
Signal节点发出的事件消息。例如角色受伤后发出 health_changed,UI 订阅后更新血条,不需要把 UI 写死进角色脚本。

下面是一段可以挂到 CharacterBody2D 的 Godot 4.x 脚本。它故意很小,却包含了这篇论文关心的三类东西:每个物理帧读取输入、更新速度和位置;生命值变化时发出事件;外部代码能从公开状态读取行为结果。

player.gd · Godot 4.x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extends CharacterBody2D

signal health_changed(value: int)

@export var speed := 220.0
var health := 3

func _physics_process(_delta: float) -> void:
var direction := Input.get_vector("left", "right", "up", "down")
velocity = direction * speed
move_and_slide()

func take_damage(amount: int) -> void:
health = max(health - amount, 0)
health_changed.emit(health)

func snapshot() -> Dictionary:
return {
"position": global_position,
"velocity": velocity,
"health": health
}

如果只在最后读取 health == 3,就看不出它是否曾被错误地扣到 2、又被另一段代码补回 3。逐 tick 评测会检查完整轨迹:每一步发生了什么、何时发生、不同系统是否同时遵守约束。这正是 GameLogicBench 选择游戏逻辑作为研究对象的原因。

背景资料来自 Godot 4.4 官方文档的 Nodes and ScenesUsing signals;上面的示例是为本文写的最小示意,不是论文任务代码。

Q1: 这篇论文试图解决什么问题?

很多游戏规则约束的不是终点,而是到达终点的过程。设想一个资源系统:第 30 帧扣掉 10 单位资源,第 31 帧又错误地多加 10;结束时余额与正确实现相同。如果评测只看最后一帧,它会把这段错误轨迹当成正确。类似问题也会出现在 cooldown(冷却时间)、移动碰撞、跳跃落地、状态机切换和同一帧内的多个请求中。

现有游戏开发 benchmark 常见三种判法:复播一条固定示例、看最终视频或截图、让另一个模型操作并打分。它们各有用处,但没有同时提供下面三项能力:

  • judge 自己选择多个合法场景,而不是只看 Agent 已见过的 preview;
  • 在运行过程的多个 tick 读取状态和事件历史,而不是只核对最后结果;
  • 判分路径中没有语言模型,使同一提交、场景与 seed 可以得到完全一致的 verdict(判定)。

GameLogicBench 不评价“这款游戏好不好玩”,也不评价美术和关卡内容。它只检查一件事:Agent 补上的 gameplay logic,能否在多种合法运行条件下始终遵守预先写好的规则。

GameLogicBench 的任务层级、求解与评测流程以及 Claude Code 下的模型结果
原论文 Figure 2。左侧把任务分成 Atom、Combo、Repo;中间是隔离的 SOLVE 与 JUDGE;右侧是 Claude Code 下各模型的单次 solve rate。点击查看原尺寸。

Q2: 有哪些相关研究?

先区分两类任务。GameCraft-Bench、WebGameBench 等评测完整游戏能否生成、能否玩、视觉是否符合要求;GameLogicBench 给 Agent 一个可运行的 Godot 项目,只让它补上指定机制,再用 engine assertion(引擎内断言)验收。GameLogicBench 的覆盖面较窄,但每个 verdict 都能复现,也能定位运行途中发生的违规。

Benchmark产物 / 任务判定方式确定性跨 tick每题 scenario
SWE-bench软件仓库 patchUnit tests不适用不适用
Terminal-Bench 2.0终端任务,生成或 patchUnit tests不适用不适用
WebCompassChromium / JS,生成或 patchAgent judge不适用1
WebGameBench浏览器游戏,从头生成Agent judge1
GameDevBenchGodot / GDScript,真实项目 patchTest scripts部分任务1
JAMERGodot / GDScript,生成或 patchEngine verify1
GameCraft-Bench完整 Godot 游戏,从头生成VLM judge1
GameEngineBenchUE5 / C++,真实项目 patchTests + LLM judge部分任务1
AutoUEUE5 / C++,从头生成LLM judge1
GameLogicBenchGodot / GDScript,生成项目或真实项目 patchEngine assertions全部任务2–12
原论文 Table 1:运行中产物 benchmark 的评测设计对比
原论文 Table 1。“Deterministic”“Tick-level”“Scenarios”是本文用来定位相关工作的三条主轴。表中的圆圈表示只有部分任务满足,不是完全支持。

这张比较表需要保留一个边界:它比较的是作者选取的、会直接判断运行中产物的 benchmark,不是所有代码 benchmark 的完整综述;“本文唯一同时具备三项性质”也只在这组对照里成立。

Q3: 论文如何解决这个问题?

三个 tier 描述集成范围

  • Atom:在为 benchmark 新做的最小游戏里隔离一个机制,例如敌人寻路;21 题。
  • Combo:仍是最小游戏,但几个机制会在时间、空间或并发调用中互相影响,例如巡逻、视线、跳跃和追逐同时成立;28 题。
  • Repo:在真实开源 Godot 项目中补回一个组件,需要读懂现有调用方、动画、相机、数据资源和共享状态;23 题。

三类任务不是难度标签,而是 integration scope(集成范围)。论文的数据确实显示 Repo 更难,但不能反过来把所有 Repo 都理解成“难题”,也不能把 Atom 理解成简单算法题。

Agent 看见什么,judge 又知道什么

每题给 Agent 一个任务说明和 Godot 项目,其中有可运行的 preview 与调试输出。Agent 知道功能规则、允许修改的文件、接口和“场景每次会重新生成”等事实;它只看到一个公开的 baseline scenario(基线场景)。隐藏 judge 使用同一接口,但会选择其他布局、输入序列、调用顺序和 seed。隐藏的是具体测试实例与 judge 代码,不是另外一套规则。

BRIEF告诉 Agent 要实现的行为、接口和可修改文件。
PREVIEW一个公开 scenario,可换 seed 运行并查看调试输出。
PATCHAgent 只交付指定脚本及允许的 helper。
HIDDEN JUDGE换 scenario、seed、调用时序,并读取运行状态。
VERDICT只要任何一个 test case 破坏规则,任务失败。

从候选机制到可计分任务

GameLogicBench 的任务构建与验证流水线
原论文 Figure 3。人类负责入口筛选和最终录取;Agent 辅助完成 feasibility、blueprint、implementation 与 independent review。所有 Agent 产物仍要经过校准和人工复核。
  1. 01 · SOURCING从开源 Godot 项目、已发布游戏和功能能力 taxonomy 收集候选机制。
  2. 02 · SCREENING三名人工标注者判断规则能否写成唯一值、合法事件顺序或守恒量。
  3. 03 · BUILDAgent 依次做可行性试验、blueprint 和可执行 task / judge。
  4. 04 · CALIBRATE用正确解、等价正确解、naive 解和 mutants 检查 judge。
  5. 05 · REVIEW独立 review agent 复跑,再由三名人工标注者录取、退修或淘汰。

大约 200 个候选想法中,122 个走完构建、校准和 Agent review,最终 72 个进入 benchmark。标注者先判断一种行为能否客观测量,Agent 再把它做成项目和 judge,随后用反例检查 judge 有没有漏掉必须满足的能力。这里的“标注”包含了一整套可执行测试的构建与校准,不是给样本贴一个静态标签。

为什么要同时放“两个正确解”和“多个错误解”

一套测试只跑过 reference solution,只能说明它能接受一种写法;它可能把变量名、控制流程或某个偶然数值当成正确性的必要条件。GameLogicBench 因此设置四类 calibration artifact(校准样例):

校准样例必须得到的结果它在检查什么
proper solution所有 scenario 与 seed 都通过,并离容差边界有余量任务确实可解
behavior-preserving control通过另一种内部实现只要可观察行为相同,也应被接受
naive solution失败常见的“看似能跑”实现不能蒙混过关
single-capability mutant至少被一个专门 scenario 抓住每次只删掉一项能力,验证 judge 对这项缺失确实敏感

Mutant 校准确实找到了会改变榜单结果的漏洞。作者在 36 题审计子集上检查“未经过 mutant 修补”的 criterion(判定规则):666 个 mutant 中有 127 个错误实现通过,暴露出 19 道任务里的 24 个缺失检查。修补后,9 个模型 × 2 个 scaffold 的历史提交里,有 3 个结果从 PASS 改成 FAIL,涉及 2 道任务和 3 个模型;正确解与行为等价的 control 仍然通过。

Judge 如何把 scenario、seed 和 tick 串起来

求解和判分运行在两个独立、断网的容器中。judge 拿到 Agent 工作目录的副本和冻结的测试文件;对每个 scenario 与 seed,它用固定 timestep 启动游戏。scenario 定义测试结构,例如“从相反方向接近台阶,前方再放一堵过高的墙”;seed 再为台阶高度、距离或输入时序等参数取具体数值,于是形成一个 test case。

官方仓库目前公开的是评测 harness,而不是 72 道题各自的 judge。能直接核查到的隔离机制是:harness 按 game → solution → judge 的顺序组装项目,最后覆盖冻结的 judge 文件;隐藏 seed 通过命令行参数传入,不写进 Agent 可见的项目目录。论文链接的 GameLogicBench-Tasks 仓库在本文核查时返回 404,所以 task-specific checker 的具体断言仍然无法公开审计。

下面的代码只把论文协议写成伪代码,帮助读者看清逐 tick 断言的粒度;它不是官方 task-specific judge 源码。

1
2
3
4
5
6
7
8
9
for scenario in hidden_scenarios:
for seed in scenario.seeds:
var run = launch_fixed_timestep(scenario, seed)
while not run.finished:
run.advance_one_tick()
assert(run.state.resources >= 0)
assert(event_order_is_legal(run.event_history))
assert(no_collision_violation(run.state))
assert(run.completed_required_goal())

judge 还会改变接口允许的调用计划,包括 concurrent calls(同一阶段出现多个调用)、re-entry(一次流程尚未退出又再次进入)和 stretched time base(拉长时间步尺度)。这些变化只影响执行条件,功能要求不变。断言读取运行状态(runtime state)与事件历史(event history),不读 Agent 的源码,也不接受 Agent 自己上报“我通过了”。

判分按四层汇总。每个 test case 先得到二元 PASS/FAIL;一个 scenario 只有在它的全部 seed 都通过时才算 strict pass;一项 task 的所有计分 case 都通过,才记为 solved;主表的 solve rate = solved tasks / 72。没有可判定 solution 的运行和不可用的 computation 都算失败,不会从分母中剔除。Figure 8 的能力分数另按 scenario 统计:先要求一个 scenario 的所有 seed 通过,再在带有相应能力标签的 scenario 上汇总。一个 scenario 可以有多个能力标签,所以七类能力的分母不能相加。

真实 case 1:Atom / Enemy Navigation

一个方向向量,为什么也值得逐帧测

来源:论文 Appendix F.2 的完整任务 brief

  1. 任务。Agent 只实现 res://logic/controller.gd 中的 decide(state) -> Vector2。返回值表示本物理帧的移动方向。
  2. 公开输入。state 给出当前位置、目标、角色半径、物理世界、navigation map、dt 和累计时间;preview 展示一张固定示例地图。
  3. 隐藏变化。墙、门洞、起点和终点会程序化重排;不同 seed 生成不同数值实例。
  4. 逐帧观察。每一帧都要确认圆形角色没有碰墙;结束前还要确认它在时限内抵达终点。
PASS所有 seed 都在时限内到达,并且整条轨迹从未触墙。
FAIL哪怕最终到了终点,只要中间一帧擦过墙角,仍然失败。

这个 case 也说明了 terminal-only(只看最终状态)的盲点:controller 可以先穿墙、再到终点。终局满足“抵达”,运行轨迹已经违反碰撞规则。

真实 case 2:Combo / Platform Guard

同一个 controller 同时承担巡逻、视线、跳跃与返回

来源:论文 Appendix F.3 的完整任务 brief

  1. 任务。decide(state) 每帧返回 {"move": float, "jump": bool, "chasing": int}
  2. 场景。两块平台之间有落差和深坑,tower(障碍塔)会遮挡视线;平台宽度、gap、访客出现时间和出生点都会变化。
  3. 动作。安静时覆盖 home platform(初始驻守平台)至少 34% 的可行走范围;看见 intruder(入侵者)后接近到 130 units 内,而且必须站在地面上;失去目标后 6 秒内返回 home。
  4. 观察。judge 逐帧检查是否坠落、是否把墙后的目标谎报为 chasing、跳跃轨迹是否真正落到目标旁、离家是否超时。
PASS不同布局和访客时序下,所有职责都持续成立。
FAIL只靠距离判断“可见”、在空中掠过目标、或追完后一直留在远端,都属于明确失败。

这里的 34%、130 units 和 6 秒都来自公开 brief,不是本文自行推测。真正的隐藏 scenario 与 judge 源码没有随论文主仓库公开,因此本文不会把示意伪代码冒充官方实现。

真实 case 3:Repo / AMSG Character Movement

Repo 示例要求 Agent 在一个 MIT 许可的第三人称角色 kit 中重建 CharacterMovementComponent.gd。它要保持原有类、导出属性、状态字段和方法签名,让动画、相机、控制器和数据资源继续读取同一个组件。功能包括三种 gait(步行、奔跑、冲刺)、松开输入后的减速、蹲下与头顶阻挡、任意方向的台阶攀爬、落地后跳跃,以及离地 0.1 秒后才确认下落。

论文正文给出了一组具体的公开/隐藏差异:preview 让角色从 +X 方向走上一个合法台阶;某个计分 scenario 改为从 -X 接近,并在路径更远处放一堵超过最大台阶高度的墙。正确实现必须爬上台阶,但不能把后面的高墙也当成台阶。judge 会检查角色能否从不同方向识别可攀爬高度,同时维持整套 character rig(角色控制系统)的接口和状态约束。

规模与覆盖

GameLogicBench 的游戏类型、能力场景与 token 规模
原论文 Figure 4。72 题覆盖 12 种来源类型。403 个 scenario 可以同时标记多个能力类别,因此中间七根柱子的数量之和大于 403。右图表明在配对设置下,GameLogicBench 的输出 token 约为 GameDevBench 的 15 倍,非缓存输入接近 4 倍。

每道任务有 2–12 个 scenario、10–38 个 test case。Repo 平均含 209 个游戏文件、17,599 行游戏代码;Atom 与 Combo 平均都只有 5 个游戏文件,代码量分别为 316 与 421 行。Repo 题需要先找到真正控制行为的代码位置,再维持周边系统依赖的接口和共享状态。

Q4: 做了哪些实验?效果如何?

实验设置

作者测试了 20 个 model + scaffold 组合。scaffold 是承载模型、提供读文件、改代码和执行命令等工具的 Agent 框架;论文使用 Claude Code 2.1.177、Codex 0.144.1 和 OpenCode 1.17.18。所有配置使用 effort=high,每题上限 3,600 秒,求解与判分都运行 Godot 4.4。主表中的每个组合只运行一次,因此它是 observed result(观察到的一次结果),不能当作稳定的模型能力估计。

原论文 Table 3:20 个模型与 scaffold 组合的分层通过率、轮数和成本
原论文 Table 3。最佳单次结果是 Claude-Opus-5 + Claude Code:38/72,即 52.78%。同一模型换 scaffold 后差异很大,例如 Qwen-3.8-Max 从 26.39% 到 44.44%,因此论文强调比较单位应是 model + scaffold 这套完整配置。

结果 1:集成范围越大,所有模型都明显掉分

固定 Claude Code 后,12 个模型都从 Atom 到 Combo、再到 Repo 下降。聚合 solve rate 是 45.2% → 31.5% → 21.7%。与此同时,平均 turn 增加,Agent 启动 Godot 的次数更多,重新选择 preview seed 的 session 比例从 Atom 的 38.9% 上升到 Repo 的 55.8%。Agent 确实在尝试运行反馈,但项目范围扩大后,它们仍然难以同时满足全部规则。

原论文 Figure 5:任务层级对 solve rate、轮数、运行实验和工具调用的影响
原论文 Figure 5。从 Atom 到 Repo,检查代码的 tool call 明显增加;修改和执行调用主要在 Atom → Combo 阶段增加。

结果 2:多数失败项目能运行,但机制行为不合格

作者把失败 scenario 分成三类:74.3% 是 mechanism failure(机制失败),即提交可以运行和判分,但行为违反了契约;17.2% 没有可判定的提交;8.5% 的求解过程本身不可用。把全部配置和 scenario 合并统计后,Engine contract 的 strict pass rate 为 88.75%,在 20 个配置中的 19 个排第一;Commitment、Spatial 与 Timing 只有 53.4%–58.57%,在 20 个配置中的 17 个排倒数三位。

原论文 Figure 8:20 个配置在七类能力上的严格场景通过率
原论文 Figure 8。“会用 Godot 的时钟和求解器”通常不是瓶颈;难的是让决策承诺、空间约束和时间不变量在完整运行中持续成立。每个 scenario 可以属于多个能力类别。

结果 3:删掉逐帧检查或隐藏场景,分数会被大幅高估

在 36 题审计集上,作者固定同一批 666 个 mutant 和 20 个配置产生的 720 份提交,分别做两种消融:

  • Terminal-only:保留所有 scenario 与 seed,但只看终局。236/666 个 mutant 逃过检查,34/36 题至少漏掉一个 mutant;原本失败的提交中有 64/488 被误判为通过,平均 solve rate 增加 8.9 个百分点。
  • Preview-only:保留完整逐 tick criterion,但只跑公开 preview。508/666 个 mutant 逃过检查,36/36 题都有漏网;418/488 个原本失败的提交被误判为通过,平均 solve rate 增加 58.1 个百分点。
原论文 Table 5:仅终态和仅公开场景的评测消融
原论文 Table 5。逐 tick 与多 scenario 不是两种可替代方案:前者防止“中途错、结尾对”,后者防止“只对已见示例过拟合”。

结果 4:同一模型换一个 scaffold,表现和工具使用都会变

Qwen-3.8-Max 在三个 scaffold 下分别得到 44.44%、26.39% 和 29.17%;GPT-5.6-Sol 则是 41.67%、34.72% 和 34.72%。Codex 平均 turn 最少,Qwen-3.8-Max 与 GPT-5.6-Sol 在 Codex 下的各分位 tool call 也更少,但“交互少”并不稳定对应更高或更低的分数。模型与框架是共同起作用的系统,单独按模型名字排榜会掩盖这个差异。

结果 5:开放网络会让 Repo 题变成源码检索题

作者另外让五个配置在 Repo 题上开放网络,并审阅 URL、查询和命令轨迹。四个配置确实找到了上游源码,而且人工检查发现了直接复用;这四个配置的 Repo 成绩都高于各自的断网版本。最大增幅是 Kimi-K3 + Claude Code,从 3 题增加到 12 题(+9)。

原论文 Figure 7:开放网络时的上游源码检索与 Repo 成绩变化
原论文 Figure 7。断网不是为了人为增加难度,而是避免 benchmark 已知源项目被直接检索和复制。第五个配置没有尝试检索,成绩也没有变化。
完整原始图表索引论文的 9 张 Figure 与 12 张编号 Table 均有记录

Q5: 有哪些值得继续探索的问题?

第一,扩大可测范围。当前 benchmark 主动排除了依赖审美和体验的规则,只覆盖能写成确定性状态断言的 gameplay logic。美术质量、节奏、关卡趣味和完整玩家体验仍要由其他 benchmark 补上;未来也可以加入更长交互和更多 runtime signal,但前提是保持 verdict 可重复。

第二,扩展引擎与多人系统。现在只测 Godot 4.4 + GDScript,单容器 harness 也不覆盖网络同步。迁移到 Unity、Unreal 或多容器 client–server 测试时,如何继续控制时钟、异步任务和网络事件顺序,是直接而困难的后续问题。

第三,增加统计稳定性。主表每个 model + scaffold 组合只有一次完整运行。论文另对 Qwen-3.8-Max、GLM-5.2、Kimi-K3 在 Claude Code 下各跑三次:单次分数的标准差相当于约 2–3 道题,而 pass@3 与 worst@3 相差 26.39–36.11 个百分点。“某配置这次解出 38/72”只能表示这一次运行的结果,不能当作稳定能力上限。

第四,补足公开复现链。论文主页提供了评测框架仓库,README 指向单独的 GameLogicBench-Tasks 仓库;截至本文核查时间(2026-09-22),该链接返回 404。因此可以审计 harness 和论文附录中的三个完整 brief,却不能从公开仓库逐项复跑 72 题、隐藏 judge 与 mutant archive。本文中的 task case 只引用论文公开材料;任何等价代码都明确标为示意,而非官方 judge 源码。

第五,把“防污染”当成 benchmark 设计的一部分。Repo 任务来自公开项目;只要模型能联网,测试可能从“理解仓库并实现机制”变成“找到原文件并复制”。未来发布任务时,需要在可审计性与防直接检索之间做版本化设计,例如保留可公开的构建记录,同时把真正计分的新变体放在受控评测环境中。

Q6: 总结

GameLogicBench 的主要贡献是一条可以反复审计的评测链。人类先筛选能够确定性判断的机制;Agent 辅助构建 task 与 judge;正确解和等价正确解检查测试是否错误地绑定某种实现;naive 解与单能力 mutant 检查测试能否拒绝缺失能力的实现;最终 judge 再用多个 scenario、多个 seed 和逐 tick 状态断言评测提交。

它的实验给出三个直接结论:第一,只看最终状态会漏掉大量中途违规;第二,只测公开 preview 会严重高估成功率;第三,大部分失败项目其实能运行,真正薄弱的是跨帧维持决策、空间和时间约束。最佳单次结果仍只有 52.78%,而 Repo tier 在 Claude Code 下的聚合 solve rate 只有 21.7%。

读这篇论文时也要保留两条边界:它不评价一款游戏是否好玩,只评价指定逻辑是否满足可执行规则;主结果是一次运行的观察值,并且完整任务库当前无法从公开链接取得。把它当作“怎样设计可信的 runtime benchmark”比把它当作模型绝对排名更有收获。

图表版权:原论文以 CC BY 4.0 发布;本文保留原图并标注 Figure / Table 编号。Godot 背景资料来自 Godot 4.4 官方文档。文中的中文解释与示意代码由本文整理。

留言

留言正在载入…

搜文章