P122 · 结算画面 · 实验记录与相关篇目
2026-10-15:读取器已接入生产路由与 Battle Notes 离线回放;三个训练结算页上稳定帧数量、损失 126/126 相符,三局 App 回放每局 28/28 个数字与参考相同。参考是模型目视(MODEL_VISUAL_CHECKED),只有 3 页、42 张不同的卡,不是准确率;实时模式与 CPU 生产路径尚未实测。
摘要
战斗一结束,游戏会弹出一张结算页:双方每支部队(游戏里叫“兵堆”)一张卡,卡上写着这堆开局有多少只、整场一共损失了多少。这正好是我们的状态估计(State Estimation,下称 SE)最缺的“终局值”——它平时靠行动条和日志一点点累计减员,可最后一击之后就再也没有画面可读了。
我们在三段训练录像里找到全部三个结算页(共 42 张卡、38 帧原生截图),做了一个只读这一页的读取器:先用标题和按钮文字确认这是结算页,再按卡片边框找卡,分别读数量、损失和兵种,并判断画面是否已经“稳定”。在读取器自己判为稳定、且出现 0.3 秒以后的 12 帧上,168 张卡的数量和损失全部正确;接入生产路由后在 9 张稳定帧上再测,126/126 数量、126/126 损失相符。 兵种是难点:稳定后很多卡上盖着红色骷髅,原门槛下稳定帧只认出 15/126 张。于是 App 改为同时读面板出现的第一帧(头像还干净),并用“留出一个结算页”的方式重新选门槛,第一帧 42 张卡全部认出且与参考一致。
这些数字的分量要说清楚:参考是模型对照画面目视写的(MODEL_VISUAL_CHECKED),不是人工审核;只有 3 个结算页,而且都是 7 对 7 的排法;门槛也是在同样这 3 页上选的。它说明读取器在这些画面上没有读错,不说明换一局、换一种部队数量就一定读对。
1. 战斗结束后,减员时间线缺了什么?
《Heroes of Might and Magic: Olden Era》的战斗是回合制:双方各带最多七堆生物,轮流行动。Battle Notes 是我们做的观战助手,它在玩家打游戏时读屏幕,再由 SE 把读到的数字整理成“每堆什么时候、少了多少只”的减员时间线。平时的读数来自三处:画面上方的行动条(每张卡下面写着这堆的数量)、战场上生物脚下的数量牌,以及战斗日志里的“被摧毁”等文字。
问题在战斗的最后一下。打出结束战斗那一击之后,游戏立刻转到结算,不会再有行动条或日志可读;如果某堆是在那一下被消灭的,时间线只能停在它最后一次被读到的数量上。2026-10-14 我们在一局训练录像(16:57 那段)上用目视写下的结算数字核对 SE:14 堆的开局数量全部与 SE 第一次读数相同,净减员 8 堆一致、0 堆冲突,但有 6 堆缺了结尾——SE 最后看到的还是 26、1、6、8、4、1 只,结算页却写着全部损失(state_estimation/experiments/2026-10-14-battle-end-consistency)。
结算页恰好能补上这个缺口。下面是同一局结算页的三个时刻,裁出了面板所在的横条(像素来自原生 2560×1440 截图):

图 1:面板出现的第一帧(录像 1304.470 秒)。标题“胜利”、按钮“接受结果”、左红旗右蓝旗、每边两行卡(4 张+3 张)。此时卡下还没有橙红色的损失数字。

图 2:约 0.07 秒后(1304.537 秒)。有损失的卡会逐张闪白 30–70 毫秒,闪白时卡框和数字都看不见。

图 3:稳定后(1305.004 秒)。卡下出现“骷髅图标+负数”的损失行,例如左上 185 张卡下写 −185;很多卡的头像被半透明红骷髅挡住。
这三张图已经说明了读取的三个难点:什么时候读(太早没有损失行、闪白时卡找不到),数字在哪里(数量在卡内底部,损失在卡下),以及兵种看不清(骷髅盖脸)。
2. 任务定义:读什么,什么算读对?
输入:一张原生 2560×1440 截图,App 怀疑它是结算页(下文第 4.6 节说明怎样怀疑)。
输出(battle-result-screen.v1 格式,逐卡):
- 这是不是结算页;标题(“胜利”或“战败”);左右旗子颜色;
- 每张卡的卡框位置、数量、损失、兵种(标准兵种 id 或空)、兵种候选;
- 画面是否已“稳定”(
settled):损失行都已画出,没有卡正在闪白。
每个字段都允许“未读”(UNKNOWN)。没读出来不算错;把 110 读成 11、把还没画出的损失记成 0、把兵种认成别的,才算错。
故意不提供的信息:读取器不知道哪边是玩家自己(owner 永远为空),也不读英雄是谁。标题“胜利”是录像玩家的结果,不指出玩家在左还是右:16:57、01:39 两局玩家在右边,08:38 那局在左边,这是从日志和上下文推出来的,不是从面板上读的。哪边是自己由 SE 根据整局的其它线索决定。
成功的标准分三层,与我们审计实时采集时用的口径相同:
- 采到:App 在面板显示的几秒内真的送读了这一页;
- 读对:数量、损失、兵种逐字段与参考相符;
- 用对:SE 把每张卡对应到自己跟踪的那一堆,并据此补上终局读数,不因读错而改坏历史。
3. 数据与参考:只有三个结算页
只用用户选定的训练录像(pool=train),没有打开冻结或留出数据。在已有的 960×540、每秒 2 帧网格和 08:38 录像最后 20 秒里,用固定条带(标题条+按钮行)与一张结算页做相关,三段录像各找到一个结算页;126 秒的 01:11 剪辑没有。
| 录像 | 面板第一帧 | 面板消失 | 标题 | 每边卡片 |
|---|---|---|---|---|
| 10-10 16:57(前 23 分钟) | 1304.470 秒 | 约 1306.45 秒 | 胜利 | 7 / 7 |
| 10-10 01:39 | 1797.629 秒 | 约 1798.9–1799.2 秒 | 胜利 | 7 / 7 |
| 10-09 08:38(对官方 AI) | 759.192 秒 | 约 763.1 秒 | 战败 | 7 / 7 |
用户点“接受结果”很快,面板只停留约 2.0、1.3–1.6、3.9 秒。从视频只读解码保存了 38 帧原生截图:面板出现前几帧、出现后最初 0.1 秒、闪白期,以及稳定后若干帧。另查了 3 个确定属于训练源的已保存 App 会话,截图里都没有结算页;不知道属于哪个 pool 的 10-11、10-12 实时会话没有打开。
参考(reference.json)逐卡记录兵种、数量和损失,标注等级是 MODEL_VISUAL_CHECKED:数量和损失由助手在稳定帧的原生裁图上逐个目视读出;兵种由助手把出现第一帧的干净头像与 Game Knowledge 标准图标(匹配器前 5 名)并排比对选出。08:38 那页另外和同一录像的日志对得上(例如神弩手 110/−16)。这不是人工 REVIEWED。兵种参考是在匹配器自己的前 5 名里选的,所以“匹配器第一名都对”只是自洽,不是独立核对(第 6.4 节会再提)。
42 张卡里:30 张损失等于数量(都带骷髅),8 张损失小于数量且不带骷髅,2 张损失小于数量但带骷髅,2 张没有损失行。
数据角色:这 3 页同时用于设计读取器、调几何参数和选兵种门槛,没有独立的测试页。第 6.4 节的“留一页交叉验证”只能部分缓解这一点。
4. 方法:从“这是结算页吗”到“SE 用上了”
整条流程如下(图中文字为英文):
flowchart LR
S[Scene cue: VICTORY] --> F[Read first panel frame]
S --> L[Read a frame at least 0.5 s later]
F --> I[Species from clean portraits]
L --> N[Counts and losses]
I --> P[SE pairs the two reads]
N --> P
P --> M[Match cards to tracked stacks]
M --> E[End-of-battle readings]
图 4:App 每次结算页出现读两帧:第一帧取兵种,稍后的稳定帧取数量与损失;SE 把两次读取配对后再对应到自己跟踪的兵堆。
4.1 先确认这是结算页
问题:App 送来的可能是加载画面、菜单或别的页面。做法:标题框和按钮框各裁一张图,交给已有的文字读取器(原尺寸和三倍放大各读一次,两次一致且分数都至少 0.80 才接受)。标题读到“胜利/战败”,或按钮读到“接受结果”,才继续;否则返回 NOT_A_RESULT_SCREEN。这一步只读两小块,非结算页上很快就结束。
4.2 按卡框找卡
卡片位置固定(约 99×109 像素,行距约 146、卡距约 116.5),但两行各几张没有写死。每边有一个固定搜索区;对每个候选左上角,程序检查卡片四条边是否都压在图像边缘上(左右边看竖向边缘,上下边看横向边缘,避开圆角和中间的凸口),取四条边中覆盖最差的比例作为分数,至少 0.80 才算一张卡,然后取峰、抑制邻近、按行排序。第一版原型在一张闪白卡旁边把邻卡框错位了 14 像素,第二版(v1)因此加了一步:在 18 像素内用 ±1 像素的边缘图重新定位。
输出只报告真的找到的卡(DETECTED_CARDS_ONLY),不假设一定是 7 张。
4.3 读数量和损失
数量写在卡内底部,是白色字。程序找卡底部白色像素的列段,把卡中心左右 36 像素内的所有段合并,再左右各留 16 像素裁图交给文字读取器,只接受 1–4 位数字。留边是实测出来的:留得太窄会丢掉开头的“1”或“2”,太宽(整卡宽)又会把“7”读成“77”。数字段明显偏离卡中心时不读。
损失写在卡下,是橙红色字。卡下橙红色像素的第一段是骷髅小图标,从第二段(减号)开始裁图读字,必须是“−数字”。如果损失大于数量,两个都不保留。
4.4 什么时候算“稳定”?
问题:没有损失行,可能是这堆确实没有损失,也可能是损失行还没画出来。如果把后者记成 0,SE 就会以为这堆活到了最后。
规则:只有画面已经稳定时,没有损失行才记 0(NO_LOSS_LINE);否则记 UNKNOWN(LOSS_LINES_NOT_SETTLED)。“稳定”要求至少一张卡有损失行、没有卡正在闪白。原型用“找到的卡里没有闪白卡”来判断,可闪白的卡本身就找不到卡框,于是这条检查失效了(第 7 节);v1 改成:搜索区里把白色区域腐蚀 25 像素后若还剩大块白色,就不算稳定。
4.5 认兵种:骷髅为什么是主要障碍
兵种复用行动条卡片的标准头像匹配器:先粗筛 24 个候选,再把卡片上部(数字以上)放大 1.10 倍与 Game Knowledge 标准图标比较。放大倍数在 0.95–1.20 之间前几名不变。接受规则沿用行动条卡片的门槛:第一名分数至少 0.80,且比第二名高至少 0.03(下文写作“0.80 / 0.03”)。闪白的卡不认。
稳定后很多卡上盖着时强时弱的红色骷髅。骷髅不等于全灭:16:57 右边修道院长 16/−8、圣骑兵 18/−12 都带骷髅。下面两张裁图是同一位置在第一帧和稳定帧上的样子:


图 5:上为第一帧,下为稳定帧(16:57 右侧,原生像素)。下排三张都带骷髅,但只有巫妖 55/−55 是全灭;修道院长 16/−8、圣骑兵 18/−12 并没有全灭。骷髅的含义我们还不清楚,SE 以数字为准。
因为第一帧的头像是干净的,App 每次都会读第一帧(4.6 节)。但沿用行动条的 0.80 / 0.03 门槛时,第一帧 42 张卡也只认出 15 张(第 6.2 节),而不设门槛的第一名其实 42/42 都对。结算卡的渲染比例和行动条不同,分数普遍落在 0.65–0.90。于是我们只对干净面板(未稳定、没有卡在闪白、搜索区没有大块白色、所有卡都还没有损失行)另设门槛,用“留一页”交叉验证来选:在 0.40–0.90 的分数门槛和 0.00–0.30 的差距门槛网格上(步长 0.01),每次只看两页,选“0 错前提下接受最多、并列取更高门槛”的点,再用到留出的第三页。三页一起按同一规则选出 0.63 / 0.09。稳定帧和其它帧仍用 0.80 / 0.03。
4.6 App 怎样知道结算页出现了,又读几次?
App 原先从不读结算页:关键帧服务把它判为菜单(SKIP_MENU)。我们把 38 帧按 App 的方式缩成 640×360 交给生产的场景判断(phase gate):面板帧 26/26 的场景都是 VICTORY——包括 08:38 的“战败”页,场景名只代表这种画面,不代表胜负——其中 23 帧判 SKIP_MENU、3 帧闪白帧判 ANALYZE;面板前 12 帧全是 BATTLE_ACTIVE。所以不用改关键帧服务的判决,App 看到场景线索 VICTORY(或 DEFEAT)就发一个 battle_result 读取请求。
每次结算页出现最多读两次:面板出现的第一帧(从上一个战场采样往后,在环形缓冲里找第一张明显变化的帧),以及至少 0.5 秒后的一帧(损失行已画出)。读数存为会话里的 action-strips/result-NNNNN.json,并保留原图;读取失败也保留原图并存一条 FAILED 记录(SE 跳过)。结算期间场景采样继续,但不再发攻击预览采样。路由给这类请求 180 秒超时,因为 CPU 上一帧要几十秒(第 6.2 节),而战斗已经结束,不影响实时。
4.7 SE 怎样对应卡片和兵堆?
SE 只用 settled 且读成功的读数。先按旗子颜色定哪边是我方、哪边是敌方,再逐卡找自己跟踪的那一堆:
- 卡片有兵种、SE 在那一方也跟踪这个兵种 → 按兵种对应;
- 卡片没有兵种 → 按开局数量对应,但只用本局第一次看到数量变化之前的读数,而且要求这一方所有卡片数量都已知、这些开局读数都能对上某张卡,才对应;
- 读取器给出的兵种 SE 那一方没有 → 不按数量改配,这张卡留作 UNMATCHED。
对应上以后比较“SE 最后一次读数”和“开局数量 − 损失”:相等是 MATCH;SE 最后读数更高,说明最后的损失发生在最后一次读数之后,记 MISSING_END,并在结算帧加一个读数(开局数量 − 损失)补上;其它情况是 MISMATCH,只列出、不加读数。SE 不为了对上而改动历史读数。
借第一帧的兵种(2026-10-15 加入):稳定帧上没认出兵种的卡,可以借同一次出现的第一帧读到的兵种。两次读取要像是同一次出现:标题、左右旗子、每边卡数相同,每张卡框四个坐标差都不超过 12 像素(三次出现实测最多 5 像素,相邻卡相距至少 116 像素),两边都读到的数量必须相等。两次兵种不一致时两边都不用(identity_conflict)。审阅中又加了一条:借来的兵种只有在卡片开局数量等于 SE 对那一堆的第一次读数时才对应,否则 UNMATCHED(identity_count_differs)。原因见第 7 节的注入实验。
4.8 中途消失的兵堆:结算页补得上,但补得太晚
在 01:39 那局上核对时,我们发现缺口不只在最后一击:很多兵堆是战斗中途被消灭的,既没读到日志“被摧毁”,行动条上也只是不再出现它,SE 规定“卡片不再出现本身不当作 0”,于是时间线一直停到结算页才补 0——观世者、太阳圣盾晚了约 20 分钟。
所以 SE 另加了一条条件推断(来源记为“左行动条”,LEFT_STRIP):一次行动条读取若同时读到当前回合和完整的下一回合(下一回合在条内结束、每张卡都有兵种和颜色、同一回合无重复、当前回合剩下的堆都在下一回合里),就叫“完整读取”。某堆在最后一次出现之后,连续 3 次完整读取都没有它,之后任何截图都不再出现,它此前在行动条上至少出现过 3 张截图,最后读数大于 0,且没有日志“被摧毁”读数——满足这些,才在第 3 次完整读取那帧加一个 0,并标为条件推断。之后若再看到它,SE 下一次重算就不再有这个 0。结算页对应上的卡若显示这堆活到了最后,这个 0 也不加。
3 次和 3 张这两个数,是在 01:39 读取器输出上逐个“故意认错”再逐帧重放选出来的(第 6.6 节)。它属于 SE,但与结算核对直接相关,所以放在这里说明。
5. 实验设置:每一层怎样计数
- 字段计数:每个字段记对 / 错 / 未读,UNKNOWN 算未读,不算错。卡片按位置(左上角 30 像素内)对应到参考卡位。
- 帧分组:面板出现前(应判非结算页);出现后按读取器自己的
settled和是否已过 0.3 秒分组。稳定且出现 0.3 秒以后的帧(下称“稳定帧”)才是 App 实际该读的帧。 - 离线原型与 v1:v0 在 1 个核心、CPU 上运行;v1 经重任务名额工具在 2 个核心上运行,与其它工作进程同时运行。
- 生产路由:按 App 的方式启动路由子进程(默认 GPU),逐条发送
battle_result请求,原样保存返回,再对每条返回重跑严格校验;每局选第一帧和 3 张稳定帧,另加面板出现前一帧作对照、01:39 面板关闭后的两帧加载画面。游戏没有运行,但同机有其它回放和助手。 - 逐帧探针:把三段录像从面板出现到消失的每一帧(共 232 帧)交给生产读取器,文字读取器和兵种匹配器换成假的(标题总读成“胜利”、数字永远不一致)。“稳定”和
NO_LOSS_LINE不依赖文字读取,所以这测的是生产几何在每一帧上的判断。 - App 端到端:Battle Notes 的离线视频回放(与菜单“离线视频”同一入口),对三段录像各跑一次完整回放,再把会话里的结算读数与参考比较;SE 的
result_screen行按 MATCH / MISSING_END / MISMATCH / UNMATCHED 计数。
所有计数都只说明这 3 页、38 帧(或回放取到的帧)上发生了什么,不是准确率。
6. 结果
6.1 离线原型与 v1:数量、损失在稳定帧上没有读错
| 字段(稳定帧:12 帧、168 张卡) | 原型 v0 | v1 |
|---|---|---|
| 是不是结算页(另有面板前 12 帧) | 12/12 对;面板前 12 帧全判非结算页 | 同左 |
| 标题、旗子颜色 | 12/12、24/24 | 12/12、24/24 |
| 找到卡 | 168/168 | 168/168,没有多余的卡 |
| 数量 | 168 对,0 错 | 168 对,0 错 |
| 损失 | 168 对,0 错 | 168 对,0 错(159 次读到数字,9 次是 NO_LOSS_LINE 的 0,来自 2 张不同的卡) |
| 兵种(0.80 / 0.03) | 22 对,0 错,146 未读 | 23 对,0 错,145 未读 |
| 兵种第一名(不设门槛,仅分析) | 133 对,35 错 | 133 对,35 错 |
面板出现后、还没稳定的帧上:v0 有 9 帧 126 张卡,v1 有 12 帧 168 张卡(v1 把 v0 误判为稳定的 3 帧归到这里)。v0 在这些帧上数量 103 对、1 错(110 读成 11)、22 未读;v1 数量 113 对、0 错、55 未读,损失 6 对、0 错、162 未读——还没有损失行的卡正确地不填。v1 另有 2 帧在 0.3 秒内已经稳定,28 张卡数量、损失全对。
稳定帧上兵种按骷髅分(v0,0.80 门槛):不带骷髅的卡 21 对 / 23 未读(第一名 44/44 对),带骷髅的卡 1 对 / 123 未读(第一名 89 对、35 错)。出现后第一帧的干净头像,第一名 104/104 都对。骷髅挡脸是兵种读不出来的主要原因。
骷髅线索本身:英雄头像的红色像素比例分得很开(输家带骷髅 0.199–0.201,不带 0–0.0015);卡片的红色比例则与红色生物重叠(带骷髅 0–0.30,不带 0–0.11),单帧不能当标记用。
耗时(CPU,文字读取不用 GPU):v0 稳定帧每帧 25.8–33.6 秒,其中卡片文字读取约 22 秒(28 张裁图各读两次 = 56 次识别),找卡只要 0.01 秒;非结算页约 1.6–2.3 秒。v1 与其它进程并行时稳定帧中位 24.3 秒(21.6–38.8),非结算页中位 1.6 秒。
6.2 生产路由在真实帧上:126/126,兵种门槛太严
16 条主返回的路由状态与读数状态一致,严格校验全部接受;12 张面板帧 READ,3 张面板前对照帧 NOT_A_RESULT_SCREEN,没有 FAILED。标题 12/12,旗子 24/24。所有帧缺卡、多卡都是 0。
| 帧 | 卡 | 数量 对/错/未读 | 损失 对/错/未读 | 兵种(0.80 / 0.03)对/错/未读 | 兵种第一名(仅分析)对/错 |
|---|---|---|---|---|---|
| 三局第一帧 | 42 | 42 / 0 / 0 | 0 / 0 / 42 | 15 / 0 / 27 | 42 / 0 |
| 三局稳定帧(各 3 张) | 126 | 126 / 0 / 0 | 126 / 0 / 0 | 15 / 0 / 111 | 80 / 46 |
- 第一帧的损失全是
LOSS_LINES_NOT_SETTLED,正确地不填;三张第一帧都没有卡在闪白,14 张卡全找到。 - 稳定帧的损失:120 次读到数字,6 次是
NO_LOSS_LINE的 0(16:57 死亡商人、08:38 缟玛瑙舞者各 3 帧,参考都是 0)。 - 稳定帧兵种:不带骷髅 30 张卡 14 对 / 16 未读(第一名 30/30),带骷髅 96 张卡 1 对 / 95 未读(第一名 50 对 / 46 错)。不设门槛的错误全在带骷髅的卡上;门槛下没有错。
- 第一帧与稳定帧逐卡位比较(126 对):两边兵种不同 0 次;按不同的卡算,第一帧认出 15 张,稳定帧认出的 6 张第一帧都已认出。读第一帧认兵种是对的,但 0.80 / 0.03 门槛偏严。
- 同一帧再读一次,除耗时外完全相同。
耗时(GPU):路由进程启动 0.37 秒;第一次请求 42.95 秒,其中读取 5.71 秒,其余约 37 秒是加载文字模型和兵种匹配器;之后 11 次面板读取 4.32–5.89 秒,中位 4.84 秒;非结算页 0.13–0.20 秒。CPU 生产路径(用户玩游戏时 App 走 CPU)这次没有测。
6.3 逐帧探针:有没有“稳定得太早”?
风险是:两次闪白之间画面看起来稳定,某张有损失的卡却还没画出损失行,被错记成 0。
| 局 | 面板期间帧数 | 判稳定 | 最早稳定 | 稳定且有损失的卡被记 0 | 稳定但有卡位没找到卡 |
|---|---|---|---|---|---|
| 16:57 | 62 | 58 | +0.133 秒 | 0 | 6 帧 |
| 01:39 | 48 | 45 | +0.100 秒 | 0(面板内) | 9 帧 |
| 08:38 | 122 | 117 | +0.167 秒 | 0 | 10 帧 |
面板显示期间没有找到这种帧。“稳定但缺卡”的帧集中在面板刚出现 0.13–0.33 秒内和面板关闭时:缺的卡只是缺,不会被记 0,但只看这一帧会少几张卡;这些帧没有逐个目视,不能排除当时卡框偏移。01:39 的 7 个“记 0”帧发生在面板关闭后,是探针的假标题造成的(第 7 节)。
6.4 第一帧兵种门槛:留一页交叉验证
| 留出的页 | 在另两页选出的门槛 | 另两页 对/错/未认 | 留出页 对/错/未认 | 旧门槛在留出页 |
|---|---|---|---|---|
| 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。分数最低的是 01:39 左边第 6 张神弩手,0.6365、差距 0.095。
这个“0 错”很轻:第一帧上第一名本来就 42/42 对,没有一个错的第一名,交叉验证只能说明“降到这里不会少认”,测不出会认错的风险;参考兵种又是从匹配器自己的前 5 名里选的。唯一的反例来源是稳定帧上被骷髅挡住的卡:第一名错了 46 次,其中最高分 0.5654,离 0.63 只有约 0.06。同族升级形态的最小真实差距是神弩手对弩手 0.1009,差距门槛 0.09 只比它低约 0.01。比较独立的旁证来自 SE:按兵种对上的 42 行,卡片开局数量都等于 SE 对该堆的第一次读数。
6.5 App 离线回放:三局结算页都采到、读对
Battle Notes 采集审计里,接入之前的回放(e2e-11 到 e2e-15)都没有读结算页(被判为菜单,计 0/1)。接入后用 10-15 当晚的全部新代码重跑:
| 回放 | 录像 | 结算读取 | 稳定帧数字与参考 |
|---|---|---|---|
| e2e-16 | 16:57 | 读 2 次(首帧 1304.5 秒、稳定帧 1305.3 秒) | 28/28 |
| e2e-0139-02 | 01:39 | 读 2 次:首帧 4.2 秒(14 卡,5 卡认出兵种)、稳定帧 5.5 秒 | 28/28 |
| e2e-0838-01 | 08:38 | 结算 1/1 | 28/28 |
“28 个数字”是每局 14 张卡的数量和损失。这三局是三次单独的回放,不是同一系统的连续进步。
6.6 SE 终局核对:分批报告
下面各批输入不同(目视参考构造的结算帧、读取器的真实读数、不同版本的回放快照),不能接成一条进步曲线。
(a) 16:57,用目视参考构造一帧结算读数(兵种全空,只能按开局数量对应):MATCH 7、MISSING_END 6、UNMATCHED 1、MISMATCH 0。6 个 MISSING_END 都在结算帧补上了最后的减员(例如敌方剑士 26→0、我方巫妖 1→0)。UNMATCHED 是我方死亡商人 50:SE 在第一次数量变化前读到 3 个开局 50 的“堆”,其中两个是兵种认错,按数量对应不唯一。e2e-16 用读取器真实读数时结果完全一致。
(b) 01:39,旧回放快照+构造结算帧:MATCH 2、MISSING_END 11、UNMATCHED 1、MISMATCH 0。11 堆缺结尾里,只有 2 堆的最后读数贴近终局;其余 9 堆是战斗中途整堆消失。e2e-0139-02 用真实读数:14 卡里 13 张按开局数量对应,11 个 MISSING_END 补上,2 个 MATCH,1 张(我方 60)未对应。
(c) 01:39,加入“左行动条”规则后(快照 analysis-001092,读取器真实结算读数):结算行由 MATCH 2 / MISSING_END 11 / UNMATCHED 1 变为 MATCH 12 / MISSING_END 1 / UNMATCHED 1。10 堆得到左行动条终局,全部是真的消灭:0 都放在行动条标注最后一次看到它之后,结算参考里也都是 0。剩下的 MISSING_END 是敌方修道院长,被结束战斗的那一下打死。16:57 那局行动条一直占满(12–13 张),没有一次完整读取,整份 SE 结果与改动前逐字节相同。
规则参数的选择:在 01:39 读取器的 117 次行动条输出上逐个改错再逐帧重放(“误判”= 判消灭的堆在之后还出现,或根本不存在):
| 设置 | 单卡认错兵种(512 种改法) | 同一堆连续两次认错(499) | 扫描在下一回合中途断掉(237) |
|---|---|---|---|
| 连续 1 次完整读取,至少 3 张截图 | 100 种改法出误判 | 139 | 42 |
| 连续 2 次,至少 3 张截图 | 0 | 71 | 0 |
| 连续 3 次,至少 3 张截图(采用) | 0 | 0 | 0 |
代价是 0 放得晚:0 放在第 3 次完整读取那帧,与行动条标注里“第一次不见”相比,羊人早 3.6 秒(仍在减员区间内),其余多数晚 5.0–112.8 秒;行动条占满的早期死亡更晚(圣殿狮鹫约 206 秒,观世者、太阳圣盾约 451 秒),这时只有区间起点可信。
(d) 第一帧借兵种之后(三个保存的请求,第一帧按新门槛重定兵种):
| 局 | 改动前 | 改动后 |
|---|---|---|
| 08:38 | 12 MATCH / 2 UNMATCHED | 14 MATCH |
| 16:57 | 7 MATCH / 6 MISSING_END / 1 UNMATCHED | 8 MATCH / 6 MISSING_END |
| 01:39 | 12 MATCH / 1 MISSING_END / 1 UNMATCHED | 13 MATCH / 1 MISSING_END |
四张原来 UNMATCHED 的卡(08:38 两堆开局都是 52 的大猩猩与 ent_upg、16:57 死亡商人、01:39 晨曦蛇麻虫)都对上了,每局 14/14 行的兵种、数量、损失都与参考一致。其余行只是从“按数量对应”变成“按兵种对应”,对象和状态不变。MISSING_END 在这里不是错误:它表示 SE 缺结尾、由结算页补上。
7. 失败与分析:哪些问题是怎样暴露的?
- 110 读成 11(原型 v0):数量裁图只取了一段白色像素,丢了开头的“1”。v1 改为合并卡中心附近所有白色段,并加了卡框精确定位,这个错误没再出现。
- 原型把 3 帧闪白帧判为稳定:闪白的卡找不到卡框,“没有闪白卡”的检查自然通过。这 3 帧没有因此填错 0,但这是漏洞;v1 用“搜索区里是否还有大块白色”替代。
- 兵种读不出来:稳定帧门槛下 1/124 张带骷髅的卡被认出,不设门槛的第一名错 35/124(原型)。这直接导致了“读第一帧”和“干净面板门槛”两项改动。
- 探针的假阳性:逐帧探针里 01:39 有 7 帧“稳定且判官记 0”,实为面板关闭后的加载画面——探针的假标题读取器总说“胜利”。把其中两帧交给真实生产路由,都返回
NOT_A_RESULT_SCREEN。标记帧保留作证据。 - 注入错兵种(审阅发现):在 01:39 请求副本的第一帧注入一个分数 0.70(在新门槛内)的错兵种。改动前,水泽精灵卡(54/−54)记成 ent_upg 时,真 ent_upg 卡被置空,SE 给实际剩 26 的 ent_upg 加了读数 0(情形 A);记成 twinkle_upg 时竟然 MATCH(情形 B)。原因是按兵种对应时从不核对卡片数量。加上“借来兵种的开局数量必须等于 SE 第一次读数”后,两种情形都变成 UNMATCHED,没有加错读数。
- 08:38 回放 SE 定不出我方(e2e-0838-01):结算 28 个数字都对,但开战前 3 次主读取被整页拒绝(准备页里有一个不在竞技场目录里的身份),SE 没有我方阵营,减员时间线整局为空,14 张卡全部 UNMATCHED。修好准备页与阵营颜色判断后(e2e-0838-02),58/58 次主读取完成,结算 12 MATCH / 2 UNMATCHED;剩下的 2 张正是开局都是 52 的两堆,后来由第一帧兵种解决(第 6.6 节 d)。结算页本身读对,不保证终局核对成功:上游任何一步丢了阵营,下游都对不上。
- 迟到的战场关键帧:01:39 回放里结算页之后又来了一张
BATTLE_ACTIVE关键帧,日志又记了一次“开战”并重启了行动条监视。无害,但需要修。 - e2e-16 第一次回放 30 秒就报视频解码异常,单独读同一视频正常,重跑没有复现;原因未定,作为未关闭故障保留。
8. 局限:这些结果不能证明什么?
- 样本极小:3 个结算页、42 张不同的卡,全部是训练源、全部 7 对 7。少于 7 堆时卡片怎么排列没见过;读取器只报告找到的卡,但没有验证。
- 参考不是人工审核:MODEL_VISUAL_CHECKED;兵种参考选自匹配器自己的前 5 名。
- 门槛在测试页上选:0.63 / 0.09 由这 3 页选出,余量只有约 0.06(分数)和 0.01(差距)。Game Knowledge 里没有的兵种(新兵种、皮肤)、差距类似的同族升级形态可能被接受。读取器的
policy字符串没改(为了旧保存读取仍能通过严格校验),保存的读取里看不出用了哪个门槛。 - “总损失”的语义只在一局上得到支持:16:57 我方骷髅 SE 减 60、回升 11,结算写 −49,像是净损失;复活和召唤怎么算还要更多局。
- 骷髅含义未知:修道院长 16/−8、圣骑兵 18/−12 带骷髅说明它不只表示全灭。临时部队(例如 08:38 的火焰幼虫)在结算页上没有卡。
- 英雄身份没有读,左右哪边是自己由 SE 决定。
- 左行动条规则只在一局(01:39)上触发过 10 堆;连续 3 次以上认错同一堆、让活着的堆暂时离开行动条的效果(隐身、睡眠等跳过回合的状态)、召唤物到时消失,都可能误判,这两局没出现。
- 没有在实时模式和 CPU 生产路径上验证:上面的 App 结果都是离线回放;用户点“接受结果”很快(1.3–3.9 秒),App 要在面板关闭前把帧存下来,实时里还没测过。App 界面也还不显示
result_screen各行。 - 测试通过(例如读取器 45 项、SE 51 项)说明代码按设计工作,不是识别准确率。
9. 结论与下一步
结算页是一张“答案卡”:它给出双方每堆的开局数量和总损失,能把 SE 减员时间线最后缺的那一段补上,也能检查中途的推断有没有越界。在三个训练结算页上,稳定帧的数量与损失没有读错(v1 离线 168/168、生产路由 126/126、三局 App 回放各 28/28);兵种必须改读面板出现的第一帧,配合在同 3 页上选出的干净面板门槛,三局 14/14 行全部对应。核对也暴露出一个更大的缺口:很多兵堆是中途消失的,结算页能补数却补不了时刻,于是有了“连续 3 次完整行动条读取不见才判消灭”的条件推断。
下一步:
- 在实时模式里验证 App 能否在面板关闭前抓到第一帧和稳定帧,并测 CPU 生产路径的耗时;
- 收集更多结算页(每局只有一张),尤其是少于 7 堆的排法,独立检验 0.63 / 0.09 门槛;
- 用更多局核实“总损失”是否为净损失(复活、召唤);
- 在 App 界面显示结算核对行和 SE 的条件推断说明。
附录
复现方式
以下路径都相对于 olden-era 项目根目录;local_data/ 下是本机生成数据,不是公开网页。
- 找结算页与抽帧:
perception/experiments/2026-10-14-battle-result-screen/中的scan_grid.py、scan_video.py、extract_frames.py(只读视频)。原生帧在local_data/perception/2026-10-14-battle-result-screen/frames/,文件名带媒体毫秒。 - 原型与 v1 评估:同目录
reader.py+evaluate.py(eval-01)、evaluate_v1.py(eval-02);相位门检查phase_gate_check.py。 - 生产路由检查:
perception/experiments/2026-10-15-result-screen-production/run.py(启动screenshot-router-v2子进程并发battle_result请求)、compare.py、probe_dense.py。 - 第一帧门槛与 SE 配对:
state_estimation/experiments/2026-10-15-result-identity/中的gate_folds.py、regate_requests.py、replay_requests.py、check_after.py、inject_wrong_identity.py。 - 左行动条:
state_estimation/experiments/2026-10-15-left-strip/中的validate.py、perturb.py、adversarial.py。 - 终局核对:
state_estimation/experiments/2026-10-14-battle-end-consistency/run.py(--session按画面时间重排,--settled-screen加构造结算帧)。 - 重任务都经
devtools/game_friendly_run.py或perception/tools/heavy_slot_run.py限核、低优先级运行。
证据
- 结算页读取实验(含参考
reference.json、生产接入说明第 9 节):perception/experiments/2026-10-14-battle-result-screen/README.md - 生产路由在真实帧上的检查:
perception/experiments/2026-10-15-result-screen-production/README.md - 终局一致性核对:
state_estimation/experiments/2026-10-14-battle-end-consistency/README.md - 左行动条条件终局:
state_estimation/experiments/2026-10-15-left-strip/README.md - 结算兵种第一帧门槛与配对:
state_estimation/experiments/2026-10-15-result-identity/README.md - App 回放计数(e2e-16、e2e-0139-02、e2e-0838-01/02):
perception/experiments/2026-10-14-capture-audit/README.md - 相关提交:
a22d8adb7(读取器原型)、e99794be5(生产接入:路由请求、App 两次读取、SE 终局读数)、4346ea525(生产路由真实帧 126/126)、ca160d33b(左行动条)、16f1679c1(e2e-0838-02)、149925686(第一帧门槛与借兵种)。 - 图 1–3、5 裁自
local_data/perception/2026-10-14-battle-result-screen/frames/1657/的r1657_1304470.png、r1657_1304537.png、r1657_1305004.png;文末 08:38 图裁自frames/0838/r0838_0761527.png。

图 6:08:38 那局(761.527 秒,稳定后)。标题是“战败”,场景判断仍叫 VICTORY;这一局录像玩家在左边,面板上没有任何标记说明这一点。
Comments