摘要
用户的目标是:玩的时候几乎实时地拿到所有能分析的信息。飘出来的伤害数字很难读,用户提出另一条路:双方每次行动之间至少有约 1 秒的思考时间,在两次行动之间读每支部队(兵堆)的数量,就能知道每次行动造成的减员。
但当时 App 的实时选帧在战斗中 40 秒才取一张战场图,远不够按行动读数。本实验的关键发现是:战场上方的行动条(一排卡片,按顺序显示接下来轮到哪些部队)每张卡下面就写着该部队的数量,而且每次行动结束整条会左移一格。所以只要检测到“左移并静止”,读一次行动条就能拿到最多 13 支部队的数量,不必去战场上逐个找数量牌。
在一段训练录像(10 月 10 日 16:57,23 分钟)的 App 离线回放中,经过几轮修复,66 次行动结束全部读到;在“空闲时先读行动条再读关键帧”之后,“行动结束→读完”的 p95 从 9.25 秒降到 5.53 秒。之后又接入了实时攻击预览读取和“英雄施法后补读行动条”。这些都在离线回放里测得,实时模式还没验证。
要回答的问题
- 能否可靠地检测“一次行动结束”?
- 每次行动结束后读一次行动条,能否在 App 里及时完成并交给状态估计(SE,把观察累积成对局状态推断的模块)?
- 行动条读不到的变化(英雄施法、攻击预览)怎么补?
数据与来源
只用用户选定的那段训练录像。逐行动对比用的参考是模型目视核对(MODEL_VISUAL_CHECKED),不是人工审核。
方法
检测行动结束
每秒取 10 帧行动条区域(原生坐标 x 590–2020、y 10–125),缩成 64×8 灰度“签名”;签名明显变化(平均变化超过阈值 6)后又静止 0.5 秒,记为一次行动结束。
读取
每个事件帧交给路由(router,把截图分给各读取器的程序)只做战场任务。App 每会话最多 64 次读取,这个上限写在存储格式里,所以行动条读数走单独的旁路:App 的观察器在回放/实时帧上检测行动结束,空闲时送读,结果存在会话的单独目录里,作为额外帧交给 SE。
英雄施法补读
英雄施法会改变部队数量,但行动条不左移。比较双方 HUD 上魔法数字框(施法要付魔法,鼠标碰不到 HUD 角落):发现变化并静止后,2 秒后(等法术效果落地)补读一次行动条;如果期间正常行动已经读过就不补。
实时攻击预览
鼠标放在攻击目标上会弹出预览(击杀数和伤害区间)。App 战斗中每 0.5 秒把一个约 8 KiB 的“白色像素掩码”随帧发给关键帧服务,服务用已有的骷髅图标模板线索判断有没有预览;连续两次超过阈值算一次出现,同一次只读一次,走单独的攻击预览旁路,结果同样作为额外帧交给 SE。
设置
所有端到端轮次都是用 App 的离线视频工具回放同一段录像。回放比实时快,部分轮次 CPU 上同时有其他验证任务。
结果
检测与读数(390–600 秒,3.5 分钟战斗)
- 检测到 15 次行动结束,逐张目视每次都是左移一格,卡片数量变化清楚(例如 55→8、16→13、7→3)。
- 与逐行动参考对比:实际 16 次行动,检出 15 次,无重复;漏的 1 次是 583.1 秒的一次“等待”,只交换前两张卡,签名平均变化最大 4.7,低于阈值 6。等待不改变数量,漏掉不影响减员。
- 15 帧中 12 帧读出 9–13 张卡的兵种、阵营色和数量,与目视一致;3 帧没读出卡片。
速度
第一轮每帧 10–14 秒(当时后台有其他 CPU 任务)。在常驻进程里打开两个已有的加速开关后,热状态每帧 0.9–3.0 秒(首帧冷启动 17.4 秒)。瓶颈是 CPU 模板匹配;试过 GPU 版匹配器,但与 OCR 库在同一进程里有 cuDNN 动态库冲突,没有继续。
端到端回放的几轮修复
| 轮次 | 结果 | 发现与修复 |
|---|---|---|
| e2e-01 | 检出 8 次,读了 0 次 | 回放解码比实时快,路由总被关键帧占着。修复:有待读行动条时暂停解码(最多 30 秒) |
| e2e-02 ~ 05 | 卡在第一个行动条 | 图片写入函数在含中文的会话目录下静默失败。修复:先编码再写字节,拒绝原因写进日志 |
| e2e-06 | 80 次全读、0 次被替代;但 79 次读数中 24 次 0 张卡,1 次被判为结果不符 | ① 画面上有日志窗口、生物面板、攻击预览时,战场任务走了“只读 HUD”分支;② 当前回合只剩 1 张卡时读取器要求至少 2 张;③ 战斗结束的菜单帧返回“拒绝”,App 当成错误。修复:行动条请求忽略遮挡分支,连同下一回合的卡一起读;App 接受原生的全部状态 |
用 10 帧(含 8 个原来 0 张卡的帧)复查:修复前 8 帧中 7 帧 0 张,修复后全部 12–13 张卡。
延迟(离线回放实测)
| 阶段 | e2e-07 p95 | e2e-08(行动条优先)中位数 | e2e-08 p95 | e2e-08 最大 |
|---|---|---|---|---|
| 行动结束(静止 0.5 秒后)→ 送读 | 7.42 秒 | 0.08 秒 | 3.52 秒 | 5.70 秒 |
| 送读 → 读完 | 2.95 秒 | 2.16 秒 | 2.78 秒 | 3.12 秒 |
| 行动结束 → 读完 | 9.25 秒 | 2.38 秒 | 5.53 秒 | 7.80 秒 |
| 读完 → SE 结果存盘 | 1.83 秒 | 1.05 秒 | 1.94 秒 | 2.67 秒 |
e2e-07(全部修复后):66 次行动结束全部读到,65 次读出 12–13 张卡,唯一 0 张的是战斗结束后的菜单帧。超过 1 秒的送读等待中最长的几次,都是行动条结束后又有 1–2 张关键帧抢先送读;e2e-08 改为空闲时先读待读的行动条,p95 明显下降。行动结束到 SE 可见:中位数约 3.4 秒,p95 约 7.5 秒(两段 p95 直接相加,偏保守)。
SE 不是瓶颈:在常驻并预热的进程里,加一张行动条帧后重算中位数 0.14 秒、p95 0.20 秒。之前一次对外汇报曾误写 SE 是瓶颈,下一次汇报已更正。
e2e-07 的减员与日志关联:32 个减员区间中,12 个能直接对上日志行、2 个对应多次命中、18 个没有日志;21 行日志死亡记录用上 20 行。
低幅度交换的第二阶段确认(影子测试,未接入 App)
为了抓住“等待”这类只交换两张卡的事件,先把阈值放低到 2 作为候选,静止后再把前两张卡位与上一次稳定画面做去亮度相关比较(鼠标高亮只改亮度,相关接近 1;换了兵种相关 0.04–0.71),任一卡位低于 0.9 才确认。390–1300 秒:64 次普通事件,6 次候选被拒(都是高亮),4 次确认中 3 次真实、1 次误报。样本只有这一局,暂不接入。改用“按列/按格取最大变化”的指标虽然也能抓到等待,但会因鼠标高亮、浮动提示多出 5–27 次假事件,所以保留平均指标。
攻击预览
影子测量(390–1300 秒每 0.5 秒):预览在屏幕上的时间占战斗的 17.4%,共 81 次独立出现;线索每帧 13 毫秒;64 次行动结束的静止帧里只有 5 次同时有预览,所以“读行动条时顺便读预览”覆盖不了多少,需要单独触发。
| 项目 | e2e-09 | e2e-11(加入目标移动检测) |
|---|---|---|
| 预览出现 / 读取 | 46 / 46 | 54 / 54 |
| 成功读出 | 40 | 47 |
| 每次读取 | 中位 0.46 秒,最大 0.86 秒 | — |
曾试过直接发半分辨率 JPEG,会丢掉 20% 的预览(缩小后再取白色会把细笔画冲淡),所以改发掩码。e2e-09 的 6 次拒绝都是路由没确认版面;逐帧追查有三种整帧 OCR 问题:击杀行整行没读出、两个预览并排被合成一行、骷髅图标被读进数字。SE 起初一个预览也没用上,原因是旁路帧没带来源标识,补上后得到 43 行“攻击预览候选”(都标为候选,不是实际结算)。e2e-11 中 SE 对重复的相同预览只列一次:47 个预览帧 → 34 行候选。
英雄施法补读(e2e-10)
影子测试:比较行动条卡片数字区域会被鼠标高亮和光效误触发几百次,不能用;比较 HUD 魔法框,390–1300 秒变化 26 次、合成约 15 组,日志里记录的 7 次施法全部在 1.5 秒内抓到,另有一次是回合开始的魔法恢复,915 秒之后没读到日志无法核对。
e2e-10:66 次行动结束加 13 次施法补读,共 79 次,0 次被替代;预览照常 46/46。减员区间更细:我方巫妖被敌方“审判”打掉的 55→8,区间从 430.7–457.0 秒(混着下一次行动)缩到 430.7–435.0 秒。日志关联:直接对上 13、多次命中 2;21 行日志和 15 个已匹配区间的时间检查全部一致。
失败与负结果
- 漏检 1 次“等待”;第二阶段确认方案有 1 次误报,样本太少未接入。
- 巫妖按行动读到 55→8→7→8→3,中间回升一次,可能是复活也可能是读错,没有核实。
- 卡片数字区域比较、按列取最大变化、GPU 匹配器、半分辨率 JPEG 预览都试过并放弃。
- 6 次攻击预览读取失败来自整帧 OCR;用图标位置做局部重读的改进还没做。
局限
- 所有端到端结果都来自同一段录像的离线回放;回放比实时快,关键帧更密,部分轮次有 CPU 竞争。
- 逐行动参考是模型目视核对,不是人工审核;没有与完整逐行动人工标注对比漏检率之外的项目。
- 实时模式没有测;项目的“整帧 p95 ≤3 秒”等目标没有达到。
结论与下一步
“检测行动条左移 → 立即读一次行动条”让按行动读数量、算减员成为可能,并在离线回放中做到每次行动都读到。下一步:经用户同意后在实时模式里测一次(不动用户正在运行的 App);实测实时模式下关键帧读取造成的等待;部队被消灭时把“读数消失”当作降到 0,并按时间对齐日志;核对攻击预览失败和预览与实际伤害的关联。
Comments