摘要
状态估计(SE,把读到的观察累积成对局状态的模块)里有一张“减员时间线”:每个“兵堆”(一方 + 一个兵种)随时间读到的数量,用来算这一堆死了多少、有没有复活或召唤。数量读数有多种来源:战斗日志(Logs)、生物面板、屏幕顶部的行动条卡片,以及战场上每个兵堆脚下的小数量牌。
问题是:数量牌本身没有写兵种名,兵种要靠旁边的身体识别去猜。猜错时,同一堆真兵的数量就被记到一个根本不存在的兵种名下,时间线里凭空多出“假兵堆”。在四局录像里一共找到 29 个这样的假兵堆,它们造成了假减员、结算画面对不上,还会在 App 的减员表里挤掉真兵堆。
本实验(编号 E040)定了一条保守规则,把同时满足三个条件的兵堆从正常列表里分出、放进新的“可疑兵堆”列表(不删除,后来有依据就会回来):这一方的阵营已被行动条认满 7 种;这一堆自己没有任何依据(从没上过行动条、没有面板、没有日志);它的每一次读数在前后 8 帧内都有同方另一堆读到同一个数。结果:29 个假兵堆全部分出,60 个真兵堆 0 个被错分;逐帧前缀重放中真兵堆也一次都没被分出。真值是模型目视核对,不是人工审核,以下只报计数,不是准确率。
要回答的问题
能否只用对局内已有的证据,把“只因数量牌认错兵种而出现的兵堆”区分出来,并且绝不能丢掉任何真兵堆?
数据与来源
只读 App 已保存的分析请求,四局都是用户选定的训练/开发录像;没有打开冻结或留出录像,也没有重新跑 OCR 或模型:
| 局 | 帧数 | 帧顺序 |
|---|---|---|
| 训练录像(10 月 10 日 16:57) | 261 | 保存顺序 |
| 训练录像(10 月 10 日 01:39) | 554 | 保存顺序 |
| 训练录像(10 月 9 日 08:38) | 246 | 保存顺序 |
| 实时游玩录像(10 月 14 日 23:58) | 268 | 按画面时间重排 |
真值:前两局来自整场行动条逐卡标注(含召唤物)加结算画面参考;08:38 只有结算画面参考(不列召唤物);实时局只标了 600–780 秒的 14 堆。分类:真(在该方真值阵营里);同族形态认错(不在,但该方有同族另一形态,例如升级形态);假。真值不覆盖的堆再用两条推断分:日志有“被召唤”行算真;每次读数前后 8 帧内都有同方真兵读到同一个数算假。四局没有剩下分不了的堆。
方法
先清点。改动前四局共 89 个兵堆:
| 局 | 堆 | 真 | 假 | 真兵堆在行动条上出现的最少截图数 |
|---|---|---|---|---|
| 16:57 | 20 | 16(含召唤物星之子、鬼魂) | 4 | 8 |
| 01:39 | 16 | 14 | 2 | 9 |
| 08:38 | 32 | 16(含召唤物化身、火焰幼虫) | 16 | 13 |
| 实时 23:58 | 21 | 14 | 7 | 18 |
关键观察:29 个假兵堆共 42 次读数,全部只来自数量牌(兵种来源是身体识别第一名 39 次,或 SE 按数量表值改判 3 次),行动条卡片、生物面板、日志、结算画面一次都没有;而 60 个真兵堆全部在行动条上出现过至少 8 张截图。并且每个假兵堆的每一次读数,前后 8 帧内都有同一方的一堆真兵读到同一个数——比如 16:57 那局“我方战栗骑士 50”对应真兵“死亡商人 50”,实时局“敌方疫病巫妖 140”等 6 个假兵堆对应真兵“守卫队长 140”。
规则(第二轮修订后的版本):
- 自己的依据(有一条就保留):该方颜色的行动条卡片出现过(一张也算,数量读没读到都算);或有它的生物面板读数、日志“被摧毁”读数;或有归属给它的日志死亡/复活/召唤行;或已稳定的结算画面认出它。
- 阵营已知:竞技场每方布阵 7 堆。只有当该方颜色的行动条已认出 7 种开局兵种、每种至少 3 张截图时才算已知。开局兵种 = 在竞技场数量表里、且没有“被召唤”日志行。只数行动条:面板、日志、结算画面只保住自己那一堆,不帮着凑阵营(认错一个面板、一条归错方的日志都可能把阵营凑满)。
- 读数同数:这一堆的每一次读数,前后 8 帧内都有同一方另一堆读到同一个数。
- 列为可疑 = 阵营已知 + 自己没有任何依据 + 读数同数。读数从时间线拿掉(不算减员、回升,不参与结算对应),放进新的“可疑兵堆”列表,并注明原因和同族形态(不自动合并)。任一条件不满足就留在正常列表,输出与改动前完全相同。SE 每次从整段历史重算,后来有依据就回到正常列表。
为什么不简单地“只有数量牌读数就分出”:真兵堆也经常只靠数量牌读到(例如实时局羊人射手 33 次全是数量牌),但它们都上过行动条。
逐帧前缀重放:对每局每个前缀(共 1,329 个)重算时间线,相当于实时 SE 在那一帧会给的结论;日志只用已进入前缀的,或完全不用,两种结果相同。同时用改动前的代码跑同样参数作对比。
设置
核心环境、4 核限制运行。整份 SE 结果改前、改后各跑一次做逐键比较。
结果
| 局 | 真兵堆保留 | 假兵堆分出 | 真兵堆被错分 | 假兵堆留下 | 结算画面行(前 → 后) |
|---|---|---|---|---|---|
| 16:57 | 16 | 4 | 0 | 0 | 对上 7 / 缺结尾 6 / 对不上 1 → 对上 8 / 缺结尾 6 |
| 01:39 | 14 | 2 | 0 | 0 | 对上 12 / 缺结尾 1 / 对不上 1 → 对上 13 / 缺结尾 1 |
| 08:38 | 16 | 16 | 0 | 0 | 对上 14 → 对上 14 |
| 实时 23:58 | 14 | 7 | 0 | 0 | 无结算画面 |
| 合计 | 60 | 29 | 0 | 0 |
- 四局 SE 结果中减员部分以外的顶层内容全部相同;正常列表少的正好是分出的堆,可疑列表里的读数与原来逐条相同。
- 假兵堆原来造成的 2 条假减员(08:38 两个假堆 56→20、56→32)消失。
- 两条原本“对不上”的结算行变为对上:16:57 我方死亡商人 50、01:39 我方晨曦蛇麻虫 60,都与结算画面参考一致。
- App 减员表(最多 14 行):实时局我方妖精巨龙、判官、凤凰替换了三个假兵堆;08:38 我方草木术士替换了一个。
逐帧前缀:真值阵营里的堆在任何前缀里被分出 0 次。29 个假兵堆中 20 个在第一次读到时就被分出,其余 9 个要等该方阵营凑满 7 种(例如 01:39 我方阵营到第 106 帧才凑满);最后一帧 29 个全部分出。与改动前逐前缀对比,可疑列表以外的内容只在两处预期的结算行上不同。每局全部前缀用时 5.6–11.3 秒。
测试:新增 27 个测试(先写测试:放回第一轮代码时 16 个失败、11 个通过;新版本 27 个全过),减员/结算相关 4 个文件 78 项通过。一个既有测试原本断言“死亡商人 50 因同数假兵堆对不上”,现在有意改为对上。SE 全套 3,349 通过、3 失败、52 跳过——3 个失败换回改动前代码同样失败,原因是测试环境缺一份数据集图片,与本改动无关。
失败与负结果
第一轮规则被审阅指出的问题(已在第二轮修订):
- 第一回合不在行动条上的真兵堆可能被分出。行动条最多 13 张卡,每方 7 堆共 14 堆时,最慢的一堆第一回合不在条上。第一轮规则允许面板、日志、结算画面凑阵营,任何一条多余证据都可能把这堆真兵分走,让它在 App 里悄悄消失。四局最终结果里没有发生,但这是确认的潜在缺陷。修订后阵营只数行动条,并加上“读数同数”条件。核对三个这样的堆(16:57 敌方织光者、01:39 我方真菌伊利亚德、实时局敌方活化盔甲)上条前的全部读数:没有一次与同方另一堆同数,规则阵营数也都 ≤ 6。
- 第一轮的前缀重放有“偷看未来”:它把整份请求的日志行交给每个前缀,让阵营提早凑满,报告的“24 个在第一帧分出”偏高;那份输出保留但不再引用,修订后为 20 个。
- 拿掉被分出的堆后,时间线可能因“没有一堆读到两次”整个变成空(合成例子,四局中 0 次);修订后被分出的堆也计入这个检查。
局限
- 竞技场也能选的召唤物(没有日志召唤行、在行动条上 ≥ 3 帧)会被算进阵营,例如 16:57 的鬼魂让我方阵营数变成 8。此时如果另有一堆真兵还没上过行动条、读数又恰好与同方另一堆同数,它会被分出。测试里把这种情况记为已知限制。
- 行动条把一堆真兵持续认成别的兵种(≥ 3 张截图)时,错兵种也会凑进阵营。
- 阵营要等行动条把 7 种都显示满 3 帧才算已知,在此之前假兵堆留在正常列表(保守方向)。
- “同数”用帧号距离(8 帧)而不是时间;实时会话截图更密时,8 帧代表的时间更短。
- App 目前只显示正常列表,不显示可疑兵堆;减员表最多 14 行,有召唤物时仍会挤掉真兵堆。
- 真值是模型目视,08:38 和实时局部分靠推断;只有 4 局,都是暴露过的开发数据。
- 旁见:SE 在战斗中“读数超过表值时按表值改判兵种”的规则对召唤物不成立(例如召唤的星之子 66 超过其表值 34,被改判成表值为 66 的狮鹫)。这些改判出的堆现在被分出了,但改判规则本身已另立实验修改;用新规则重跑会得到不同数字(例如 16:57 可疑兵堆 4 → 3)。
结论与下一步
“数量牌认错兵种”造成的假兵堆有很清楚的特征:只有数量牌、从未上过行动条、读数总与同方真兵相同。用“阵营已由行动条认满 + 无自身依据 + 读数同数”三条件把它们分出,在四局上做到了全部分出、零误伤,并顺带修好了两条结算对应。可疑兵堆只是被分出而非删除;若要在 App 里展示,需要另做界面和默认值。
Comments