摘要
在《英雄无敌:上古纪元》里,每一堆生物(兵堆)的数量会随着战斗减少(减员),偶尔也会因复活而回升。状态估计(SE)通过在两次行动之间读行动条卡片、战场数量牌、面板上的数量,以及战斗日志(Logs)里的“被摧毁”行,累积出每一堆的减员时间线。
战斗结束时,游戏会弹出结算画面,列出双方每一堆的开局数量和整场总损失。这正好可以用来核对 SE 的累计结果。外部审阅把“结算画面读取及终局核对”列为优先事项。当时结算画面读取器还在并行开发,所以本实验先用从 960×540 帧上模型目视读出的结算数字(MODEL_VISUAL_CHECKED,不是人工审核 REVIEWED)作为参考。
两局训练录像的结果很不一样:
- 10 月 10 日 16:57 局:14 堆开局数量全对;净减员一致 8 堆;“缺最后一下”6 堆;不一致 0 堆。
- 10 月 10 日 01:39 局(旧基线):14 堆开局全对;一致 3 堆;缺尾 11 堆,其中 9 堆是战斗中途整堆消失、行动条上不再出现,却没读到“被摧毁”日志,时间线就停在最后一个读数上;不一致 0 堆。
把结算读数作为最后一帧交给 SE 后,所有缺尾的堆都补上了最终的 0,但对于中途消失的堆,补上的时刻比实际消灭晚很多(最长约 20 分钟)。
要回答的问题
SE 整场累计的减员与结算画面的开局数量和总损失是否一致?不一致时,错在哪里?结算画面能不能补上 SE 的缺口?
数据与来源
- 两段用户选定的训练录像的 App 回放会话:16:57 局一次端到端回放(260 帧请求);01:39 局一次旧回放(550 帧:41 张关键帧截图 + 509 个行动条 / 预览 / 信息页旁路读数,快照标记为未完成)。01:39 的回放是在当晚“战斗阶段把关”和“按画面时间排序”两个修复之前跑的,所以是旧基线。
- 结算参考:从结算画面帧上模型目视读出的每堆开局数量和损失。01:39 局:左边红旗是敌方、右边蓝旗是我方(录像方胜,只有右边留下一堆)。
- 只读,没有写入新的录像目录。
方法
在 App 保存的 SE 请求上重跑 SE(放开每堆最多保留 24 次读数的上限),对结算画面上的每一堆比较:
- 开局:SE 第一次读数 vs 结算的开局数量;
- 净减员:SE 的减员合计减去回升合计 vs 结算的损失。
分类:
- MATCH:净减员相等;
- MISSING_END:SE 最后一次读数高于结算后的数量(最后的损失发生在最后一次读数之后);
- MISMATCH:其他情况;
- NOT_TRACKED:SE 没有读数。
结算画面上没有、SE 却有的堆单独列出。不为了对上而改 SE 的历史。
后来 SE 正式接入了结算读数,又做了一个对照:在原请求末尾加一帧按参考构造的“已稳定结算读数”(兵种全部为空,只能按开局数量对应)。对 01:39 局另外比较了“按保存顺序”和“按画面时间重排”两种帧顺序。
设置
16:57 局用旧版和新版核对脚本各跑一次,打印与输出完全相同。
结果
16:57 局:
| 结果 | 堆 |
|---|---|
| 开局数量 | 14 堆全部与 SE 第一次读数相同 |
| MATCH 8 | 敌方圣殿狮鹫 48、羽蛇 18;我方骷髅 385→336(SE 减 60、回升 11)、深渊使者 12→9、不死宠物 134→130、死亡商人 50→50(减 1、升 1)、修道院长 16→8、圣骑兵 18→6 |
| MISSING_END 6 | 敌方剑士(最后读到 26)、神化者(1)、织光者(6)、骑兵(8)、妖精巨龙(4);我方巫妖(1) |
| MISMATCH | 0 |
MISSING_END 的含义:打出结束战斗的那一下之后,不再有行动条或日志可读;整堆被消灭而那段时间没读到“被摧毁”时,时间线只能停在最后一个读数。结算画面恰好补上这个终局缺口。
SE 有、结算没有的堆:我方星之子(21 次读数,多半是召唤物,结算画面不列召唤物);我方鬼魂 50(8 次,待查:召唤物或兵种认错);另有几堆各 1–2 次读数的,多半是行动条或数量牌把兵种认错(同一时刻真正的兵堆是羽蛇 18、死亡商人 50 等)。敌方神化者 SE 减员 100、回升 94、净 6:中间一次把别的数量当成神化者又改了回来,净值没错但过程是噪声。
加构造结算帧后:MATCH 7、MISSING_END 6、UNMATCHED 1、MISMATCH 0;6 个缺尾都在结算帧补上了最后的减员(例如敌方剑士 26→0)。UNMATCHED 是我方死亡商人 50:第一次数量变化之前 SE 读到 3 个开局 50 的堆(其中两个是认错的兵种),按数量对应不唯一。
01:39 局(旧基线): 550 帧全部有时间;保存顺序里有 15 处相邻两帧时间倒退(最多退 4.5 秒),重排后 34 个位置变化。但两种顺序下每堆的结果(开局、最后读数、减员、回升、读数次数)完全一样,所以下表对两者都成立。
| 结果 | 堆 |
|---|---|
| 开局数量 | 14 堆全部相同 |
| MATCH 3 | 我方凤凰 10→0(日志“被摧毁”)、真菌伊利亚德 52→26、晨曦蛇麻虫 60→0(日志“被摧毁”) |
| MISSING_END 11 | 敌方修道院长 16→1、观世者 18→18、太阳圣盾 140→140、织光者 60→6、神化者 7→3、圣殿狮鹫 48→3、驯鹰师 110→11;我方判官 26→1、麒麟 24→3、羊人 135→96、水泽精灵 54→18 |
| MISMATCH / NOT_TRACKED | 0 / 0 |
结算画面约在 1797.6 秒出现。与 16:57 不同,这一局的缺尾多数不是“最后一下”:只有修道院长(1795.9 秒)和麒麟(1777.4 秒)的最后读数贴近终局;其余 9 堆是战斗中途整堆消失。最明显的是敌方观世者 18 和太阳圣盾 140:551.8 秒的行动条上还有这两张卡,554.0 秒已经没有,SE 一次减员都没记。按现有规则“卡片不再出现本身不当作 0”,这是设计内的缺口,不是读错。
SE 有、结算没有:一次把晨曦蛇麻虫认成其未升级形态;一次把当时正好是 18 的水泽精灵认成天使 18。我方麒麟减员合计 226、回升合计 205、净 21:中间三次把别的数 79 读成麒麟,净值对,但过程是噪声。
加构造结算帧后:MATCH 2、MISSING_END 11(全部补上最终的 0)、UNMATCHED 1(我方 60:开局变化前有两堆 60,按数量对应不唯一)、MISMATCH 0。
SE 请求规模(把 260 帧请求重复拼接,只测大小,不是更长的对局):
| 帧数 | 请求 JSON | 第一次 | 之后三次中位 | 结果 JSON |
|---|---|---|---|---|
| 260 | 3.5 MB | 27.7 秒(冷启动) | 0.52 秒 | 0.46 MB |
| 520 | 7.1 MB | 1.63 秒 | 1.20 秒 | 0.72 MB |
| 780 | 10.6 MB | 2.35 秒 | 1.77 秒 | 0.98 MB |
| 1024 | 14.0 MB | 2.84 秒 | 2.19 秒 | 1.23 MB |
约每帧 2 毫秒、近似线性;上限 1024 帧可用,但很长的对局每次重算会到 2 秒左右,实时模式要盯这一项。暂不压缩历史。
失败与负结果
- 01:39 局 9 堆中途消失没被记录,是现有规则下的设计缺口。
- 两局都有兵种认错导致的“多出来的堆”和中间读数噪声(净值仍对)。
- 内存峰值没有测成:脚本把进程句柄当 32 位整数传,调用失败、显示 0;之后已修(按指针宽度传、失败时报错),待机器空闲时重测。
局限
- 结算数字是模型目视参考,构造的结算帧兵种全为空;真实读取器的输出可能不同。
- 01:39 局是旧基线、快照未完成;我方几乎全灭、没有复活,无法核对结算“总损失”是否为净损失(16:57 局的骷髅减 60、回升 11、结算写 −49,支持净损失)。
- MATCH / MISSING_END 只比较第一次和最后一次读数,中间读错不影响分类,也不计数。
- 计数不是准确率;每局只有 14 堆。
结论与下一步
结算画面是整场减员的有力核对:开局数量两局全对,没有一堆“对不上”;但它暴露了两种缺口——终局最后一下读不到(16:57 局),以及战斗中途整堆消失没被记录(01:39 局)。结算能补上最终数,但补不回准确的消灭时刻。
下一步:用读取器在真实结算帧上的输出替换目视参考;核实结算“总损失”的语义(复活和召唤怎么算);把认错的兵种交给行动条金标准去量化;把“行动条上整堆消失”列为消灭的候选证据提案(需与日志、召唤、隐身等区分,先不直接改 SE);等 01:39 新回放跑完后重新核对。
Comments