P123 · 实时调度 · 实验记录与相关篇目
P123 · 实时调度单通道读取进程、主读取与旁路读取、按画面时间排序

2026-10-15:按画面时间排序已接入 App;读取开销的两项修复(快速 JSON 编码、旁路不等主读取的后续工作)已实现,离线回放里排队等待 p95 约减半,实时里尚未实测。3 分钟实时真值是模型目视(MODEL_VISUAL_CHECKED)的已暴露开发数据,不是准确率。

摘要

Battle Notes 是我们为《Heroes of Might and Magic: Olden Era》做的观战助手:玩家打游戏时,它读屏幕上的行动条、英雄数字、法术书、生物面板和伤害预览,再由状态估计(State Estimation,下称 SE)整理成减员、魔法变化等记录。实时模式下,这些读取都要排进同一个读取进程,一次只能做一件事。

2026-10-14 夜里用户用安装版实时玩了三局,第一局的简报里同一条“我方魔法 +43(147→190)”重复了 6 次。原因不是读错数字,而是顺序:整帧的“主读取”排在快速的“旁路读取”后面,取到的是 15–37 秒以前的画面,旧画面的读数被插在新读数之后,SE 便看到魔法 147→190→147 来回跳。我们改为按画面出现的时间排序;在同一局 600–780 秒逐帧标注的三层真值上,段内魔法净变化从 14 行(10 行假、4 行重复)变成 1 行真变化,魔法来回跳从 25 次变成 0 次,两次真实减员两种顺序都找到了。

同一份真值也说明了剩下的问题不在“读对”而在“读到”和“读得及时”:App 读出的 HUD 数字 280/280、行动条数量 116/117 都对(1 个未读),但 47 个画面区间只采到 21 个,有的信息页读数在画面出现几百秒后才存好。我们查到主读取后 App 自己的处理随对局变长而变慢,期间读取进程有 43% 的时间空着却拿不到任务;修了 JSON 编码和旁路放行两处,离线回放里各类排队等待的 p95 约减半,实时效果还没测。

1. 实时模式在做什么,为什么会排队?

这个游戏的战斗是回合制:双方各有最多七堆生物,画面上方的行动条按行动顺序排着每堆的卡片,卡下写着这堆还剩几只;左右上角的英雄信息区写着攻击、防御、法术强度和魔法值。每当一堆生物行动完,行动条整条左移一格。下图是一局实时对局的行动条,按录像时间(图中 v 后面的数字,单位秒)排列:

实时对局中五个时刻的行动条:602.5 秒金色头盔那张卡的数量从 12 变成 6,607 秒整条左移一格、最右边出现数量 25 的新卡

图 1:10-14 23:58 那局。602.2 秒我方英雄放连锁闪电,天使从 12 只变成 6 只(第 5 张卡);607 秒凤凰行动结束,整条左移,原来排在条外看不见的活化盔甲(25)露了出来。图上每行左上角的英文标注是帧号和录像秒,由实验脚本加上。

用户希望“玩的时候几乎实时地拿到所有能分析的信息”。我们因此在两次行动之间读行动条来算减员,在鼠标悬停出现伤害预览时读预览,在玩家打开法术书或生物面板时读那一页。这些读取都交给同一个读取进程(代码里叫 router,路由):它常驻在后台,装着文字识别和图标匹配模型,App 每次发一个请求,它读完一张图再接下一个。所以实时模式的核心问题是一个调度问题:谁先读、谁后读,读完的结果按什么顺序交给 SE。

2. 任务定义:什么叫“实时地读对、用对”?

输入:游戏画面的实时帧流(原生 2560×1440),以及 App 内各个触发器给出的“现在值得读”信号。

输出:一串读数,每条带上它对应的画面时间(媒体秒),交给 SE;SE 据此给出减员时间线、魔法净变化等简报行。

成功标准分三层:

  1. L1 采到:画面上出现的每个值得读的区间(一次行动后稳定的行动条、一次预览、一次打开的法术书或面板),App 至少读了其中一帧;
  2. L2 读对:读出的每个字段与画面相符;
  3. L3 用对:SE 给出的减员、魔法变化与真实发生的事一致,没有多出来的假变化。

另外关心延迟:从画面出现到读数存好要多久。实时里玩家不会等,一张信息页如果几分钟后才读到,对当时的判断就没有帮助了。

故意不用的信息:不暂停游戏、不读游戏内存,只看屏幕;不在用户玩的时候抢 CPU(App 及其子进程被限制在一组逻辑处理器上、低优先级)。

3. 数据:三局实时对局与一段逐帧真值

  • 三局实时对局:用户 10-14 23:58 起用安装版 battle-notes-2026.10.14-1 实时玩的三局(会话名 23:58、00:35、00:44)。只读,没有改动会话。这三局整局没有真值。
  • 3 分钟三层真值:23:58 那局录像 600–780 秒(标注时放宽到 598–782 秒)。用户同意作开发数据,所以它是已暴露的回归基线,不是独立准确率。标注等级 MODEL_VISUAL_CHECKED:助手按每秒 2 帧看原尺寸画面写的,预览和换手处用每秒 5–10 帧补看;标注按画面写,不按 App 结果改。没有运行文字识别或模型,只用视频解码、看图和现有 SE。
  • 离线回放:用户选定的训练录像 10-10 16:57(前 23 分钟)在 App 离线视频回放(与菜单“离线视频”同一入口)上的多轮端到端运行,用来比较改动前后。离线回放会等旁路读完,比实时宽松。

时间对齐:App 记录的画面时间(media_seconds)用的是 App 自己的时钟。把会话里第 22–45 张整帧截图和录像在 ±2.5 秒内逐帧比灰度差,7 张有唯一最小值,偏移 0.89–1.31 秒,中位 +1.21 秒(录像秒 = App 秒 + 1.21);其余 17 张画面静止,最小值不唯一但都落在 0.61–1.32 秒内。相邻两张截图之间就差 0.25 秒,是 App 时钟的逐帧抖动,所以不确定度按 ±0.35 秒计。

4. 方法

4.1 一个读取进程,两类读取

App 发给读取进程的请求分两类:

类别 什么时候发 读什么 实时耗时(23:58 局,中位 / p95)
主读取 关键帧服务选出的整帧截图(每个场景有固定间隔,例如战斗中 40 秒) 整帧所有能读的内容;每局最多 58 次(保存格式上限 64) 约 5 秒一张
旁路:行动条 行动条左移后静止 0.5 秒(一次行动结束),或英雄魔法框变化 2 秒后 只读行动条卡片的兵种、颜色、数量 1.2 / 1.7 秒
旁路:攻击预览 每 0.5 秒检查一次预览线索,确认出现 预览框里的伤害、击杀范围 0.6 / 0.9 秒
旁路:信息页 场景线索发现法术书、面板等(法术书和日志等画面静止) 那一页 1.1 / 1.5 秒

旁路读取不占主读取的 58 次额度,结果存在会话的 action-strips/ 里,作为额外的帧交给 SE。读取进程空闲时的取件顺序是:行动条 → 预览 → 信息页 → 普通关键帧。理由是行动条和预览转瞬即逝,而战斗中关键帧本来就 40 秒一张,晚几秒无妨。待读信息页最多排 16 个,满了挤掉最旧的。

flowchart LR
 K[Keyframe service] --> Q[App request queue]
 T[Turn strip watcher] --> Q
 P[Preview cue at 2 Hz] --> Q
 C[Scene cue for pages] --> Q
 Q -->|one request at a time| R[Router process]
 R --> S[Readings with media time]
 S --> O[Sort by media time]
 O --> E[State Estimation]

图 2:所有读取排进一个队列,由一个读取进程逐个处理。本文的两项修复分别作用在“排队”(4.4 节)和“交给 SE 之前的排序”(4.3 节)。

这些触发器是此前在离线回放上做出来的。例如行动条:每秒 10 帧取行动条区域、缩成 64×8 的灰度签名,签名明显变化后再静止 0.5 秒就记一次行动结束;在 16:57 录像 390–600 秒的 16 次实际行动里检出 15 次,漏掉的是一次只交换前两张卡的“等待”(不改数量)。离线回放 e2e-08 里,行动结束到读完中位 2.38 秒、p95 5.53 秒,读完到 SE 存好中位 1.05 秒。

4.2 为什么旧画面会让 SE 看到假变化?

主读取一张约 5 秒,旁路一张约 1 秒,而旁路优先。战斗中旁路几乎不停,主读取只能在空隙里做:23:58 那局里,主读取的结果大约每 13 秒一张,取的却是队列里 15–37 秒以前的画面(例如日志里 741.7 秒取走的是 705.9 秒的帧),而且不总按时间先后取。

App 原来给 SE 的顺序是“截取顺序”:会话截图按截取先后排,旁路读数插在“当时已有的截图”之后。于是会出现这样一串:

  1. 旁路行动条读数(新画面):我方魔法 147;
  2. 主读取截图(30 秒前的旧画面):我方魔法还是 190;
  3. 下一条旁路读数(新画面):147。

SE 按顺序看,就是 147→190→147:先“回升”43,再下降。简报里“【魔法净变化】我方 +43(147→190)”就是这样来的。下图是这段时间英雄信息区的真实变化:

英雄信息区十一个时刻:我方魔法 190 先降到 168、再降到 147,中间出现 183、158 两个滚动中的数字,敌方 170 不变

图 3:23:58 局 598–607 秒,左边是双方英雄攻击/防御/法术强度和魔法(我方红框 4/8/18,敌方蓝框 12/10/21)。我方两次施法:190→168(花 22)、168→147(花 21);183、158 是数字滚动中的中间帧。真实的魔法只降不升,“+43”是顺序造成的。

4.3 修复一:按画面时间排序

  • 每张关键帧截图提交时,把它的画面时间(媒体秒)记在会话的 capture-media-seconds.json。旁路读数本来就带自己的画面时间。
  • 组 SE 请求时,如果所有截图和旁路读数都有画面时间,就按画面时间排序;只要有一张没有(旧会话、手动截图、文件夹导入),就保持原来的顺序。保存过的旧请求不变。

这一改动不让任何读取变快,只保证 SE 看到的历史是按画面发生的顺序排的。旧画面晚到时,它会被插回正确的位置,而不是压在新读数后面。

4.4 修复二:主读取之后 App 在忙什么?

主读取取到的画面为什么那么旧?我们把 23:58 那局的 58 次主读取逐次还原了时间线(文件时间加日志),发现一个主读取周期从开局约 6 秒拉长到第 46–58 次的约 17 秒,其中读取进程自己做主读取只占约 1 秒。

主读取序号 处理(取帧→接受) 读取进程自报 接受→投影提交 快照→下一次取帧 画面落后
1–12 1.73 秒 0.68 秒 1.17 秒 2.14 秒 –
13–30 3.64 秒 1.05 秒 2.94 秒 3.01 秒 9.5 秒
31–45 4.04 秒 1.05 秒 3.84 秒 3.94 秒 29.1 秒
46–58 4.67 秒 0.93 秒 4.78 秒 3.97 秒 35.5 秒

(各列为中位数;只列出主要几段,完整分段见证据。)

增长来自主读取被接受之后 UI 线程上的工作:每次都把本局全部读取重新组装、编码、校验、写盘好几遍(投影请求约 0.8 MB、投影缓存约 2 MB)。离线重放同一段代码,第 11 次 0.73 秒、第 31 次 1.44 秒、第 58 次 2.53 秒;性能剖析里最大的一项是纯 Python 写的 JSON 编码器(占 47%),其次是深拷贝(25%)和投影校验(21%)。更糟的是,App 在这段时间里不给读取进程派任何活:原来的规则要求“没有投影任务、没有待接受读取”时才发主读取,也才发旁路读取。主读取那段时间里,读取进程有 43%(335/775 秒) 空着;行动条从待发到发出的等待中位 8.1 秒,其中 68% 是在这个空档里等的。

两项修改(审阅后实施):

  • 快速紧凑编码:对类型合格的值改用标准库的 C 实现编码(json.dumps,紧凑分隔符),输出字节与原编码器完全相同;不合格的值(超长整数、NaN、非字符串键、元组、深度超过 32、孤立代理字符)仍走原编码器,错误原因不变。离线测量 2.3 MB 的编码从 0.145 秒降到 0.019 秒。
  • 旁路不等主读取的后续工作:只要读取进程上没有主读取(只剩视图投影、接受已保存读取这类 App 端工作),就允许发旁路读取;主读取仍按原规则。旁路读取只写 action-strips/ 并刷新 SE,不碰视图存储。

第三项“发布时不再整局重新组装”(离线再省约 0.2 秒)没有做。

5. 实验设置:怎样数“采到、读对、用对”

  • 顺序对比(三局实时会话):同一份保存的 SE 请求,截图时间取自会话日志的 submitted 记录,分别按原顺序和按画面时间跑 SE。数简报行、魔法净变化行、“魔法来回跳”(同一英雄 HUD 魔法 A→B→A 的次数)、减员段、回升段、“可疑读数”(前后相同、中间单独不同、不算减员的读数)。这三局没有真值,所以只比较 SE 输出,不判断哪个对。
  • L1 采到:真值里 47 个画面区间(行动后稳定的行动条、预览、法术书、生物面板、英雄页、日志、英勇攻击提示)。“主读取读到”= 会话截图帧落在区间内;“旁路读到”= 旁路记录的画面时间落在区间内;两者都有算主读取。落在 ±0.35 秒不确定度边上的记为“边界”。
  • L2 读对:段内 37 个读数(主读取 16、信息页 15、行动条 4、预览 2)逐字段比对;屏上有行动条但请求只读 HUD 的情况另记“未尝试”,不算未读。
  • L3 用对:保存的 SE 请求(268 帧)只留画面时间不超过 780.79 秒的 74 帧,两种顺序各跑一次 SE,与真值的减员和魔法时间线比较。
  • 离线回放对比(e2e-16 与 e2e-17):同一段 16:57 训练录像,e2e-17 加上两项开销修复,比较各类旁路“排队等待”和“发出到结果”的 p95。两次回放时机器负载不同,只算指示性对比。

6. 结果

6.1 按画面时间排序:三局实时对局的 SE 输出

局 帧 顺序 简报行 魔法净变化行 魔法来回跳 减员段 回升段 可疑读数 减员合计
23:58 268 截取顺序 169 59 68 35 7 28 633
画面时间 77 13 1 31 1 3 483
00:35 20 截取顺序 14 3 3 3 0 13 193
画面时间 10 1 0 15 0 1 696
00:44 96 截取顺序 31 11 20 19 3 9 146
画面时间 9 0 0 18 0 1 119

顺序错乱的影响不是单向的:23:58 那局的来回跳让减员多算(633),00:35 那局反而把真实减员当成“可疑读数”藏掉了(3 段变 15 段)。这些只是 SE 输出的对比,三局整局没有真值,哪个减员合计正确无法判断。23:58 那局按画面时间排序后剩下的唯一一次来回跳(我方魔法 137→123→137,约 1446–1547 秒),抽看录像确认是真变化:先花 14、后来又回了 14。

6.2 开销修复:离线回放里排队约减半

旁路(p95) e2e-16:排队 / 发出到结果 e2e-17:排队 / 发出到结果
行动条 4.48 / 6.68 秒 2.77 / 3.66 秒
预览 5.57 / 2.65 秒 2.58 / 1.55 秒
信息页 5.16 / 4.89 秒 2.43 / 3.08 秒
整次回放墙钟 2264 秒 1577 秒

两次回放所有页面的采集计数完全相同(生物面板 23/24、法术书 22/22、日志 9/9、预览 51/55、结算 1/1),说明修复没有让任何页面漏读。编码修复在真实会话的 58 个回看快照等文件上逐字节一致。e2e-16 前段与另一段录像的回放并行,e2e-17 时有其它后台任务,所以这只是指示性对比;离线回放会等旁路读完,实时里的收益要在下一次实玩时测。

6.3 3 分钟三层真值

这一段发生的事(录像秒):602.2 我方英雄放连锁闪电(22 魔),天使 12→6,弹到活化盔甲 52→25(它当时是行动条第 14 张,看不见);605.3 又放一个群体法术(21 魔,无减员);607.0 凤凰结束行动;之后对手一堆圣骑兵想了 142 秒(608–750),用户在这期间反复开法术书、生物面板、英雄页;750.0 圣骑兵等待,750.6 我方魔法 147→149、敌方 170→168 同时变;756.4 妖精巨龙等待;770.0 修道院长等待。

L1 采到:47 个区间,App 读到 21 个(另 1 个落在边界上读到)

类型 区间 主读取读到 旁路读到 采到没读出 边界 漏
行动后行动条稳定 5 2 1 1 – 1
攻击/法术预览 3 – 1 – – 2
法术书 14 3 6 – 1 未读 4
生物面板 16 2 3 2 1 未读 8
英雄页 6 2 1 – 1 读到 2
日志 1 – – 1 – –
其它(英勇攻击提示) 2 – – – – 2

延迟(区间开始 → App 取到那一帧 / → 读数存好):行动条 1.2–4.2 秒取到、11.1–18.5 秒存好;唯一读到的预览 0.3 秒取到、43.1 秒存好(排队 39 秒);主读取 0.2–2.3 秒取帧、15.7–48.5 秒存好;旁路信息页的帧在区间开始后 0.2–4.1 秒就存下了,读数却要 117–517 秒才存好——段内 15 个信息页读数都是在画面出现 114–523 秒后才从队列取出去读,其中 12 个是在主读取的 58 次额度用完后才轮到。

L2 读对:App 读出的值没有错,错在战场数量牌

字段 对 错 未读 不计分
HUD 攻/防/法强(两方) 210 0 0
HUD 魔法(两方) 70 0 0 没有读数落在数字滚动期
行动条卡数量 116 0 1
行动条卡颜色 117 0 0
行动条卡兵种 63 0 0 54(这段没开面板,名称核不了)
战场数量牌数量(主读取) 170 2 5
战场数量牌兵种(身体候选) 94 2 6 68
生物面板 数量/攻/防 15 0 3 个面板页被拒
预览 两行数字+目标兵种/颜色/数量 5 0 0

另有 26 个读数(主读取 12、信息页 14)屏上有行动条但只读了 HUD,记“未尝试”。

L3 用对:减员两种顺序都对,魔法净变化只有按画面时间排才对

截取顺序 画面时间顺序
真实减员找到 2/2(天使 12→6;活化盔甲 52→25) 2/2
漏掉减员 / 假减员 / 假回升 0 / 0 / 0 0 / 0 / 0
错数量读数 2(判官 126),正确地列为可疑读数,不算减员 同左
假兵种序列 3(各只有 1 次读数) 同左
段内魔法净变化行 14 行:我方 +43(147→190)×6、+21(147→168)×1、敌方 +2(168→170)×3 是假的;我方 +2(147→149)×4 是真变化的重复 1 行:我方 +2(147→149),真
敌方魔法下降 170→168 找到 ×2(重复) 找到 ×1
段内魔法来回跳 25 次 0 次

减员区间偏宽:天使的区间从 599.1 秒到 613.6 秒,因为 609.2 秒那次行动条读数里天使的数量没读出;活化盔甲从约 547 秒到 609.2 秒,因为 602 秒时它是看不见的第 14 张卡。真实的 147→149(750.6 秒)SE 最早在 759.7 秒那帧看到,比实际晚 9 秒,换算成墙钟到存好晚 23 秒。我方两次花魔不会出现在净变化行里:SE 只列我方回升和敌方下降,这是设计,不算漏。

7. 失败与分析:漏读和错读从哪里来?

漏读主要是调度问题,不是识别问题。 查每个漏掉区间里 App 的触发记录:

  • 8 个区间(6 个生物面板、1 个法术书、1 个日志)的触发记录是排进了信息页队列,后来被挤掉:队列满 16 个时挤掉最旧的。这一段用户在对手长考的 142 秒里频繁开页面。
  • 7 个区间(3 个法术书、2 个英雄页、2 个生物面板)没有任何触发记录。原因没有逐个核实;离线回放里见过的一种情况是 0.5 秒一次的场景线索只采到淡入淡出帧。
  • 一次行动后的行动条(圣骑兵等待后 751–756 秒)没有记下“行动结束”,只在 756.6 秒记了魔法变化,下一次读行动条是 759.7 秒。原因未核实;离线回放里已知的一类漏检是“等待”只交换前两张卡、整条签名变化小,可能是同一类。
  • 两个预览(754.2–754.8 秒)没有触发预览线索。另有一次假触发(703.1 秒敌方回合,屏上没有预览),读数正确地被拒。
  • 23:58 那局整局预览读了 67 次,29 次是“没找到预览牌”(每次约 0.6 秒);预览原图读完即删,无法再核对。

错读集中在战场数量牌。 两次把判官的数量牌 26 读成 126:牌左边的白色标记被读成了“1”。

同一块判官数量牌在两个时刻的放大:牌上是 26,左侧有一个白色标记

图 4:632.2 秒与 707.1 秒两张主读取截图里的判官数量牌(放大裁图)。画面是 26,读取器读成 126。SE 因为前后读数都是 26,把这两次正确地列为可疑读数,没有算成减员。

另外两次是数量牌所属身体的兵种候选认错(例如把梅杜莎的牌连到妖精巨龙身上),SE 由此另建了三条只有一次读数的假兵种序列。这些是 App 自己标的第一候选,不是确认结果。牌子 126 的一个修复尝试因为太脆弱没有合入。

开销调查里有一部分没拆开:主读取“取帧→接受”里除读取进程自报和等前面旁路之外,还有 2–3 秒说不清是读取进程自身的墙钟还是 UI 线程忙,因为读取进程只自报计算时间、不含排队和读图。

8. 局限:这些结果不能证明什么?

  • 真值只有一段 180 秒、一局,而且是已暴露的开发数据,标注是模型目视。区间边界按每秒 2 帧标,±0.5 秒;画面时间有 ±0.35 秒不确定度。数量牌只对“读出的牌”判对错,被遮挡没读出的牌不算未读;6 个兵种名这段没有面板可核,不计分。
  • 三局整局没有真值:6.1 节只说明两种顺序的 SE 输出不同,不说明按画面时间排序后的减员合计就是对的。
  • 开销修复没有在实时里测过:6.2 节是离线回放,机器负载两次不同;离线回放会等旁路读完。按离线测量和约 3–4 倍的实时减速推算,每个主读取周期约少 3–4 秒,这只是估计。
  • 主读取取的画面仍然偏旧:排序修复只保证顺序,不让旧画面变新。实时模式下要不要让信息页只走旁路、或给主读取让路,是待定的取舍。
  • 信息页队列与额度:16 个上限和 58 次主读取额度在长考、频繁开页时都会成为瓶颈,这次没有改。
  • 测试通过和逐字节一致只说明修改没有改变保存格式与输出,不是识别准确率。

9. 结论与下一步

实时模式里,读对一个数字只是开始。所有读取挤在一个进程里,快的旁路优先、慢的主读取取旧画面,结果按截取顺序交给 SE,就会把正确的读数排成错误的历史。按画面时间排序以后,3 分钟真值段里 10 条假魔法变化和 4 条重复全部消失,只留下一条真的 +2;整局魔法来回跳在三局里从 68/3/20 次降到 1/0/0 次,剩下的那一次核实是真变化。同一段真值也显示,读出的 HUD 和行动条数字没有错,真正的缺口是采到(21/47)和及时(信息页读数晚到几百秒)。主读取后 App 自身的工作随对局变长,期间读取进程空着却拿不到活;快速编码和旁路放行两处修复在离线回放里把排队 p95 约减半。

下一步:

  1. 下一次实玩时测两项开销修复的实际效果,并在读取进程收到结果时记一条时间,把“其余 2–3 秒”拆开;
  2. 重新考虑信息页的排队:16 个上限与挤掉最旧的策略在长考时会丢掉大量页面;
  3. 对短暂页面复用视频帧流挑帧,而不只靠 0.5 秒一次的场景线索;
  4. 用另一段实时对局建第二份真值,避免只在一段已暴露的数据上调参。

附录

复现方式

以下路径相对于 olden-era 项目根目录;local_data/ 与 Videos/Captures/ 下是本机数据,不是公开网页。

  • 顺序对比:battle_app/experiments/2026-10-15-live-frame-order/run.py(同一请求,截图时间取自会话日志的 submitted),结果在同目录 results/。
  • 读取开销:battle_app/experiments/2026-10-15-read-overhead/ 中的 timeline.py(还原每次主读取)、side_waits.py(旁路等待)、profile_steps.py、replay_read.py(按 App 顺序真实接受第 N+1 次读取,可加性能剖析)、encoder_check.py(新旧编码器逐字节比较)。全部经 devtools/game_friendly_run.py --cores 2 运行。
  • 三层真值:perception/experiments/2026-10-15-live-ground-truth/ 中的 offset.py(对时)、app_readings.py、score.py(L1/L2)、se_compare.py(L3);标注 labels.json,结果 results/;2 帧每秒原尺寸帧与证据裁图在 local_data/perception/2026-10-15-live-ground-truth/。
  • 行动结束触发与旁路接入的离线回放:perception/experiments/2026-10-14-turn-change-trigger/ 与 battle_app/tools/review_offline_video.py。
  • 采集计数(e2e-16、e2e-17):perception/experiments/2026-10-14-capture-audit/ 的 capture_metrics.py。

证据

  • 按画面时间排序:battle_app/experiments/2026-10-15-live-frame-order/README.md(提交 9a5b41a6f)
  • 读取开销调查与修复:battle_app/experiments/2026-10-15-read-overhead/README.md(调查 7bd5989f4,实施 b575e15f7)
  • 3 分钟三层真值:perception/experiments/2026-10-15-live-ground-truth/README.md(提交 50f2f606c)
  • 离线回放 e2e-16 / e2e-17:perception/experiments/2026-10-14-capture-audit/README.md
  • 行动条触发、预览旁路、施法补读:perception/experiments/2026-10-14-turn-change-trigger/README.md(提交 279c2c60c、4a0b2a350 等)
  • 图 1、3、4 来自 local_data/perception/2026-10-15-live-ground-truth/crops/ 的 strip_a.jpg(前 5 行)、hud_a.jpg(前 11 行)、plate_126.jpg;原始会话与录像在 Videos/Captures/HeroesOldenEra 2026-10-14 23-58-24-OBS…。