摘要

一场战斗结束时,游戏会弹出结算画面:双方各一排卡片,每张卡是一堆生物的头像、开局数量和损失数量。状态估计(SE)——把观察累积成对局状态推断的模块——用它来核对自己一路跟踪的每堆兵的最终减员。

问题:画面稳定后,全灭的兵堆头像上会盖一个红骷髅,读取器在原门槛(分数 ≥0.80、领先 ≥0.03)下认不出兵种。如果同一方有两堆开局数量相同,SE 也没法按数量对应。三局训练录像里因此有 4 张卡“对不上”(UNMATCHED)。

Battle Notes App 本来就对每次结算画面读两次:刚出现的第一帧(头像干净,但损失行还没画)和至少 0.5 秒后的稳定帧。之前的检查显示,第一帧上兵种的第一名候选 42/42 都对,但门槛下只认出 15/42。本实验做了两处改动:

  1. 视觉模块:在“干净面板”(未稳定、没有闪白、没有大块白色、所有卡都还没有损失行)上,兵种门槛改为 0.63/0.09;稳定帧仍用 0.80/0.03。
  2. SE:稳定帧上某张卡没有兵种时,借用同一次结算第一帧读出的兵种;两次读出不一致时两边都不用。

结果三局全部 14/14 对上,兵种、数量、损失都与参考一致。但样本只有 3 页、42 张卡,第一帧上本来就没有认错的第一名,所以“0 错”测不出误认风险,门槛离已知最高的错误分数只有约 0.06 的余量。

要回答的问题

  1. 第一帧上可以把兵种门槛降到多少而不认错?
  2. SE 怎样安全地把两次读取配成“同一次结算”,只借兵种、不借第一帧还没画好的数量和损失?
  3. 改动后三局的结算对应是否全部正确?其他结果是否不变?

数据与来源

  • 三个训练会话(10 月 10 日 08:38、16:57、01:39 的录像)的已保存请求,每个都有两次结算读取。3 个结算页、42 张不同的卡,都是 7 对 7。
  • 参考来自 10 月 14 日的结算画面实验,是模型目视(MODEL_VISUAL_CHECKED),不是人工审核(REVIEWED)。以下都是计数,不是准确率。

方法

第一帧门槛(留一页交叉验证)

“干净面板”在原始返回里表现为:未稳定、没有闪白卡、每张卡的损失都是“损失行未稳定”——3 张第一帧的 42 张卡全部符合。没有用卡片红色比例判断骷髅,因为红色生物本身也会让这个比例大于 0。

在分数 0.40–0.90、差距 0.00–0.30(步长 0.01)的网格上,每折用另外两页选“0 错里接受最多、并列取更高门槛”的点,再用到留出页。

SE 配对规则

稳定读取的“前一个”是 SE 收到的上一个结算读取。两者算同一次出现,要求:前一个是已读、未稳定;标题相同;左右旗子颜色分别相同;每边卡数相同;每张卡序号相同、卡框四个坐标差都不超过 12 像素(实测最多 5 像素,相邻卡相距 ≥116 像素);两边都读到的数量必须相等。任何一条不符,整对不用。

只借兵种:第一帧不闪白、两次数量都读到且相等时才可用;第一帧的数量和损失不用。稳定帧已有相同兵种则不变;不同则这张卡兵种置空、标“兵种冲突”,既不按兵种也不按数量对应。原有保护全部保留(只在 SE 在这一方跟踪该兵种时才对应;同一方两张同兵种不按兵种对应等)。

审阅后新增:借来的兵种只在卡片开局数量等于 SE 对这一堆的第一次读数时才对应;不等时标“兵种数量不符”、不加读数。

设置

三个请求分别跑三种情况:改动前;新 SE + 原请求(第一帧仍是旧门槛的兵种);新 SE + 请求副本里第一帧按新门槛重定兵种。另在 App 保存的 6 张结算截图上用新读取器实跑(CPU),兵种和候选顺序与重定结果完全相同:第一帧 14/14、14/14、14/14 认出,稳定帧 4、1、0 认出(不变),每帧 8.9–14.5 秒(含首次加载)。

结果

门槛交叉验证

留出 另两页选出的门槛 另两页 对/错/未认 留出页 对/错/未认 旧门槛在留出页
16:57 0.63 / 0.09 28 / 0 / 0 14 / 0 / 0 4 / 0 / 10
01:39 0.64 / 0.11 28 / 0 / 0 13 / 0 / 1 4 / 0 / 10
08:38 0.63 / 0.09 28 / 0 / 0 14 / 0 / 0 7 / 0 / 7

三页一起用同一规则选出 0.63/0.09(42/0/0)。最低的一张卡分数 0.6365、差距 0.095。

结算对应

局 改动前 新 SE + 原请求 新 SE + 重定第一帧
08:38 12 对上 / 2 对不上 14 对上 14 对上
16:57 7 对上 / 6 缺结尾 / 1 对不上 同改动前 8 对上 / 6 缺结尾
01:39 12 对上 / 1 缺结尾 / 1 对不上 同改动前 13 对上 / 1 缺结尾

4 张原来对不上的卡(都是我方):08:38 大猩猩 52/52 和升级树人 52/52、16:57 死亡商人 50/0、01:39 一种升级生物 60/60,现在都对上。其余按数量对上的行改成按兵种对上,对象和状态不变。每局 14/14 有对应的行,兵种、数量、损失都与参考一致。

其他差别:新对上的 4 堆各多一条结算读数,都等于 SE 原来的最后读数,减员和回升不变;多一条假设说明。08:38 一条敌方宝物组合推断的组合数三次都不同(288/239/229),原因是它有 0.5 秒搜索预算,与本改动无关。

加入数量检查后重放:结算各行和各堆读数完全相同,没有一行被数量检查拒绝。

测试

视觉模块结算读取 45 项通过(新增 6 个用真实候选做的门槛夹具);SE 三个相关文件 51 项通过(新增 11 个配对场景和 2 个数量检查用例);相关扩大测试通过。SE 全套中 24 个失败,都与本改动无关:命令行子进程测试加载的是主检出(当时有未提交的模拟器改动),以及两项需要工作树里没有的被忽略数据。

失败与负结果

  • 审阅发现的缺陷:按兵种对应时从不核对卡片数量。审阅在 01:39 请求副本里给第一帧注入一个错兵种(分数 0.70,在新门槛内),修改前:情形 A,一张卡被记成另一兵种、真卡置空变成“缺结尾”,并给实际剩 26 的那堆加了一个读数 0;情形 B,错卡被判为“对上”(开局 54 ≠ SE 第一次 60)。修改后两种情形的错卡都“对不上”并标“兵种数量不符”,没有加错读数;被置空的真卡保守地保持“对不上”。
  • 交叉验证的“0 错”是构造使然:每折只是选“把训练两页全部接受的最紧网格点”,第一帧上第一名本来就 42/42 对,测不出误认风险。
  • 唯一的反例来源是稳定帧上被骷髅挡住的卡:第一名错 46 次,最高分 0.5654;新门槛离它只有约 0.06 余量。
  • 同族升级形态:真兵种都排第一,但最小差距(神弩手 vs 弩手)0.1009,门槛 0.09 只比它低约 0.01;差距类似的错误升级形态会被接受。

局限

  • 参考不独立:第一帧兵种是模型在匹配器自己的前 5 名候选里目视选的。独立旁证是:42 行按兵种对上的卡,开局数量都等于 SE 对该堆的第一次读数;08:38 另用了录像日志。但两张开局都是 52 的卡互换在数量上看不出来。
  • 只见过 7 对 7;少于 7 堆时卡框位置不同,未验证。
  • 依赖 App 抓到的第一帧确实在损失行出现之前(约 0.1 秒);抓晚了会自动回到旧门槛。
  • 读取器的策略字符串没改(为了兼容旧保存结果的严格校验),从保存结果里看不出用的是哪个门槛。
  • 不同的两次结算画面恰好标题、旗子、卡数、数量都一样时会被当成同一次(同一会话一般只有一局)。App 不把失败的读取送给 SE,所以“中间读取失败就断开配对”在实际会话里不起作用。
  • 游戏资料里没有的兵种(新兵种、皮肤)可能以 0.63 以上的分数被认成别的。
  • GPU 路径没有重跑。

结论与下一步

“稳定帧更好”在这里不成立:稳定后的结算画面会被骷髅遮挡,而刚出现的第一帧头像是干净的。用第一帧的兵种、稳定帧的数量和损失,再由 SE 严格配对并核对开局数量,三局的 4 张对不上的卡全部正确对应,没有引入错误读数。但门槛是在 42 张卡上定的,余量很小;需要更多结算页(尤其不足 7 堆、新兵种、同族升级形态)来检验误认风险。