SuperAPI AI 工具集

adversarially-validating-game-repairs

测试与质量

查看项目

更新于 10-15 10:16

技能介绍

用途

Adversarially Validating Game Repairs 是一个用于对抗性验证游戏 Bug 修复的技能。它针对的是这样一种情形:补丁已经写好,报告的症状已经消失,但对“为什么消失”信心很低。症状消失只证明在尝试过的那一个条件下症状消失,这既可能说明修复正确,也可能说明它只是在读取端加了守卫、对输出做了钳制、恰好覆盖了报告输入的特例,或者只在 60 fps、种子 2001 下有效。

该技能用作者未曾预料的条件攻击修复,并产出一份独立读者无需重新推导作者推理即可复核的记录。

何时使用

  • 补丁已存在,报告的症状已消失,但对原因的信心很低。
  • 修复触及生命周期、身份、时序、顺序、持久化或共享/池化资源——这些领域里局部正确的改动最容易全局错误。
  • 在向玩家可以刷取、重复或滥用的机制发布修复之前。

何时不使用

  • 尚无补丁,没有可攻击的对象。
  • 构建崩溃或抛异常——应先建立运行时健康;对无法运行的构建施加对抗条件只会产生噪声。
  • 问题是机制是否符合书面规格,或游戏是否平衡。这是不同的问题,需要不同的工具。
  • 改动是纯外观的,无法触及游戏状态。

所需输入

  • 补丁,以 diff 形式提供。
  • 补丁针对的 ReproCase,最小契约内联如下:
    • id:该缺陷的稳定名称
    • seed:RNG 种子,或 "none"
    • build:提交/构建标识
    • setup:起始状态或存档槽
    • inputs:带 tick 索引的有序输入事件——输入磁带
    • observe:错误的可观察量,作为值
    • expected:健康值
    • determinism:always | intermittent(n/m runs)
  • 补丁声称恢复的不变量,表述为可检查的谓词。
  • 补丁前已有的通过检查,以便 pass-to-pass 有基线。

流程

  1. 不借助作者解释重述主张。仅凭 diff 写出补丁改变了什么,以及要修复报告症状必须满足什么条件。不要先读作者的理由,读到时也不要将其视为证据。解释是带有动机的假设;diff 才是工件。

  2. 建立 fail-to-pass 与相关 pass-to-pass。在补丁前构建上运行 ReproCase(必须失败),在补丁后构建上运行(必须通过)。补丁前不失败的复现会使下游一切失效——停止并修复复现。然后运行补丁路径可触及的现有健康检查,确认仍然通过。说明这些检查实际覆盖了什么;无关的绿色套件不是证据。记录计数,而非形容词。

  3. 按可达性与失败成本选择对抗用例。将 references/adversarial-conditions.md 作为路由目录,而非通用清单。将 diff 与恢复的不变量映射到它们可触及的状态面,然后运行覆盖这些面的最廉价用例:

    • 仅当补丁路径消耗或依赖 RNG 时才变化种子;
    • 仅当触及经过时间、帧数、截止时间或顺序时才变化帧/tick 率;
    • 仅当触及该生命周期时才演练暂停/恢复、场景切换或存/读档;
    • 仅当触及身份、顺序或生命周期时才演练池化、身份复用、同 tick 事件或延迟完成;
    • 仅当存在先前预算且补丁可能影响它时才测量性能、内存或构建时间。 不要枚举补丁无法触及的目录行。当某个可达的高成本条件重要但无法运行时,将其记录在 untested_scope 中。
  4. 攻击修复的形态,而不仅是其输入。对以下各项以证据做出是/否判断:

    • 补丁改变的是第一个发散写入,还是在其下游添加检查?读取端的守卫会让坏写入留在原地,一旦出现第二个读取者就会失败。
    • 它是对输出做钳制(max(0, hp)、min(cap, score)),而非阻止坏值?钳制会把可见缺陷变成静默缺陷。
    • 它是否特判了报告输入——条件中提及了 bug 报告里的确切实体、关卡、种子或计数?
    • 它是否只在报告描述错误逻辑时改变了一个常量?重新调参不是逻辑修复,两者常被混淆,因为都能让症状消失。
    • 它是否引入了池、恢复或场景重载可以跨生命周期边界携带的状态?
  5. 测试反向。构造一个补丁代码路径不应触发的用例,确认它确实不触发。一个通过抑制整个机制来抑制症状的修复,会通过所有针对该 bug 的测试。

  6. 按结果判断,绝不按 diff 相似度判断。如果存在参考或“正确”补丁,不要与之比较。恢复不变量的不同改动是正确的;不能恢复的相同改动不是。

  7. 不要让修复作者提供结论。证据必须仅凭 diff、ReproCase 和记录的运行即可重建。如果记录中的某个主张只能通过询问作者意图来检查,它就不是证据——重写或删除。

停止条件

  • 复现在补丁前不失败。在第 2 步停止。其后一切都没有意义。
  • 任何对抗用例复现了原始症状。停止并报告;补丁不完整。不要在同一轮中扩展补丁并重跑——围绕抓住它的用例塑造的修复,比最初的过拟合更严重。
  • 不变量无法写成谓词。报告该补丁无法用此方法验证,并说明需要人工判断什么。
  • 在所有用例中,有无补丁的行为无法区分。报告为不可判定。不要通过偏好补丁版本来解决。

输出

一份 PatchEvidence 记录。字段列表与完整示例见 references/patch-evidence.md;必需字段包括:

  • patch_id
  • root_cause_hypothesis
  • violated_invariant
  • fail_to_pass
  • pass_to_pass
  • adversarial_cases
  • cross_configuration
  • performance_impact
  • fix_shape
  • inverse_test
  • untested_scope
  • confidence(high | medium | low | undecidable)
  • human_decisions_required

human_decisions_required 不得为空。untested_scope 包含补丁可能触及但本轮未运行的重要条件;当没有时使用 none identified from patch reachability。该短语的范围限于该补丁,并非声称穷尽覆盖游戏状态。

验证

  • fail_to_pass 命名了一个补丁前确实失败的运行。
  • pass_to_pass 触及被修补的子系统,且反向用例确认补丁路径在不应触发时不触发。
  • 恢复的不变量直接在补丁后复现上求值。
  • 每个选定的对抗用例都指明使其相关的补丁面。
  • confidence 由记录的运行证明,当 untested_scope 覆盖补丁可能触及的条件时降级为 low。
  • 没有任何字段以作者解释作为支持。
  • 第 4 步和第 5 步被明确回答,而非由其他结果暗示。

常见失败模式

  • 给解释打分。理由连贯,于是补丁被接受。连贯的理由伴随错误补丁的比率与伴随正确补丁的比率大致相当。
  • 把既有套件报告为 pass-to-pass。从未覆盖该机制的套件在补丁前后都通过,什么也证明不了。要说明通过的检查实际触及了什么。
  • 跳过已触及的变异来源。如果补丁路径消耗 RNG、跨越生命周期边界或依赖事件时序,相应条件就是相关的,即使 diff 看起来局部确定。
  • 混淆“症状消失”与“不变量恢复”。这两者恰在补丁错误时分离,而这正是值得检测的情形。
  • 在运行中途扩展补丁。每次扩展都会使已收集的证据失效,记录最终描述的是一个不再存在的补丁。

参考

  • references/adversarial-conditions.md——条件目录,含每个条件的设置与可观察的破坏。
  • references/patch-evidence.md——完整 PatchEvidence schema 与完整示例。