摘要
游戏的**战斗日志(Logs)**是一页按时间排列的文字,每行形如“[01:02:03] 玩家:单位(31) 受到了 90 伤害。死亡单位:30。”。项目的日志读取器对每一行做三遍文字识别(OCR),三遍结果互相印证后才输出。
之前有一个私有候选规则(下称“三遍候选”):当两遍读数能完整解析且一致、第三遍解析不了时,也接受这一行。后来又给它加了一个“窄检查”:只有第三遍仅仅缺少时钟右括号 ]、其余原文一致时才合格。
本实验发现这个窄检查有个漏洞:它在比较前删除全部空白。这样“A B”和“AB”、“90”和“9 0”、甚至带换行的两行合并文本,都会被当成一致。而项目现有的日志解析器明确规定:名字里的空格要保留,被拆开的数字要拒绝。
新检查在保留旧检查所有拒绝条件的基础上,改用现有日志语法逐字段比较。结果:
- 52 个合成案例:16 个合格对照全部保留;36 个不合格要求全部拒绝,其中 18 个是旧检查仍会接受的。
- 已保存的 13 条私有新增输出全部仍合格,没有任何一条因为新检查被取消。
这是对软件资格条件的收紧,不是测得的识别精度提升。
要回答的问题
“删掉空白再比较”会不会把含义不同的读数误判为一致?换一种尊重语法的比较后,已经接受的输出还能保住吗?
数据与来源
- 合成案例:52 个固定输入(16 个合格对照、36 个不合格要求,其中 24 个来自前一轮检查、28 个本轮新增)。标签由资格规则决定,是规范要求,不是像素真值。
- 已保存输出:之前日志困难页实验的 6 张开发截图上,三遍候选比生产多输出的 13 条日志行(短页 3 条、长滚动页 10 条;洞穴页和 3 张无日志负样本为 0 条)。
- 本次新增原图读取、解码、OCR、GPU、候选生成、准确率重评分、评分参考读取均为 0。
方法
旧窄检查要求:三遍分数 ≥0.90、两遍完整读数的 claim(结构化字段)相同、两遍完整原文经 NFKC 归一并去空白后相同,第三遍只差时钟右括号。漏洞就在“去空白”。
新检查依次:
- 先跑旧窄检查;旧检查拒绝的,保持拒绝。
- 三遍原文中只要出现任何换行类字符(CR/LF、垂直制表、换页、记录分隔、NEL、Unicode 行/段分隔符),拒绝。不能用去空白把两行合成一行。
- 用 Perception 现有日志语法的正则模式解析:两遍完整原文用正常时钟模式;第三遍用只删去时钟右括号的比较模式。绝不往第三遍里补括号、修字或拼接。
- 比较三遍解析出的字段:时、分、秒、玩家、事件种类,以及主体/行动者/目标/标签/数值等。每个字段做 NFKC 归一和首尾去空白,但保留名字内部的空格和数字的连续写法。三者必须解析成同一种唯一事件、字段完全相同,才合格。
语法本身允许的分隔空格(例如冒号后、数字前后)、全角字符归一、制表符分隔仍然合格;名字内部空格增删、数字/时钟/谓词被拆开、括号内多空格则不合格。
检查只返回“是否合格 + 原因”,不生成新文字或新 claim。它是 Perception 内部实验对现有私有语法的适配,不是新接口;将来生产接入需要另行评审。
设置
协议、合成输入、实现和测试先提交冻结,然后对 13 条已保存输出只检查一次,之后不再调整。
结果
软件验证
67 项新增测试 + 227 项既有实验回归 + 94 项日志字面解析器测试,共 388 项通过(0.51 秒)。其中包括全部 10 种换行类字符、畸形输入,以及“输入不被修改”的检查。
合成案例
| 固定合成输入 | 旧窄检查合格 | 新检查合格 |
|---|---|---|
| 16 个合格对照 | 16 | 16 |
| 18 个既有不合格要求 | 0 | 0 |
| 18 个新增空白 / 分行不合格要求 | 18 | 0 |
新增被拒的 18 类:主体、玩家、法术名的空格被删或被插入;行动者、目标、能力名、效果名内部空格被删;伤害、死亡数、括号内数量、持续时间、时钟被拆开;谓词被拆开;括号内部多空格;多行合并。
几个例子:
| 两遍完整原文中的片段 | 第三遍(缺右括号)中的片段 | 旧检查 | 新检查 |
|---|---|---|---|
主体 A B |
主体 AB |
合格 | 拒绝 |
玩家 p q |
玩家 pq |
合格 | 拒绝 |
伤害 90 |
伤害 9 0 |
合格 | 拒绝 |
已保存输出
| 截图 | 检查数 | 合格 | 不合格 |
|---|---|---|---|
| 短页 | 3 | 3 | 0 |
| 长滚动页 | 10 | 10 | 0 |
| 洞穴页与 3 张无日志负样本 | 0 | 0 | 0 |
13 条的检查 CPU 时间约 2.085 ms(不含导入和输入预检),不是识别延迟。
沿用此前已保存的(未重新评分)成绩:可读整行 36/39 正确,已输出且可核验的整行 36/36,另有 1 条参考不确定。本次检查不会把不确定行变成已证正确,也没有解决剩下 3 条被拒识的行。
失败与负结果
旧窄检查存在被复现的漏洞(18 类输入被错误接受)。新检查没有让已保存的任何一条输出失去资格,也没有提高任何识别成绩。
局限
- 合成案例是规范要求,不能据此估计真实的误接受率。
- 三遍读数来自同一个识别引擎;如果三遍共享同一个错误,新检查仍会放行。多遍一致不等于像素真值,分数 ≥0.90 也不是校准概率。
- 全部是已暴露开发数据;像素真值、独立留出覆盖和独立验收都还需要各自的证据。生产默认和公共接口未改。
结论与下一步
“去空白后比较”过于宽松,会隐藏名字和数字内部的差异;用现有语法逐字段比较能挡住这些情况,同时不影响已保存的合格输出。新检查待独立审阅后再决定是否接入;生产接入需要单独评审,不能让外部模块直接依赖这些私有语法实现。
Comments