三场景实测记录 v1.0

S1 工业振动 / S2 野外巡检 / S3 私域知识库 · 实测日期 2026-10-28 · 真源脚本 C:\emgw-build\em_scenarios.py · 设备 Dev-A(γ 磁吸卡 EME-71FAC8,COM3)/ Dev-B(β USB Dongle EME-21E980,COM4)

结论 三场景在两块板上各 36 条判据,36/36 全绿,红 0;两板结果逐条一致。

过程中查出并修复两个真缺陷(时序窗口闭区间、base64 缓冲区差 1 字节),均已回灌规格与代码。 G1 判据(Dev-B ≥12,000 条)已由 .emf 产品通道实测达成:单包 12,000 条一次灌入, 重启后 12,000 条保全、抽样可查回(见 §3.1)。判据脚本是可复跑的——结论一旦失效,对应行会变红。

0. 一句话交代每个场景验的是什么

场景验什么怎么判它成立结果
S1
工业振动
故障指纹碎片按域隔离 + 时序异常分钟可被聚合值检出 4 个域(轴承/齿轮/电机/泵)各自成碎片;按域名检索只命中本域且带出处四字段;注入 12 个分钟窗口、第 8 分钟幅值 ×8,逐分钟 rms 必须在第 8 格出现尖峰 22/22
S2
野外巡检
基础包 / 专家包分层 + ADR-014 拒答语义 两层碎片各自灌入;通用词只命中基础包、专家专属词只命中专家包;无关查询与红线条目必须拒答且不回退「最接近一条」 9/9
S3
私域知识库
批量灌库正确性 + 抽样可检索 12 包 × 10 条 = 120 条;库条数精确对账 120;随机不重复抽样 8 条每条命中 1 条;库外编号反向对照必须查不到 5/5

1. S1 工业振动(22 条,双板全绿)

1.1 故障指纹:按域隔离

判据内容Dev-A 实测结果
S1-01 / 01b时序区可用,且如实否认原始波形能力 streams=3, l1_pages=122, pages_per_stream=40, raw_waveform=false绿
S1-02四个故障指纹域灌入 7101 轴承 3/3(484B)· 7102 齿轮 2/2(316B)· 7103 电机 2/2(324B)· 7104 泵 2/2(320B)绿
S1-03frag.list 列出 4 个碎片n=4, ids=[7101,7102,7103,7104]绿
S1-04按域名检索只命中本域 轴承→[7101×3] · 齿轮→[7102×2] · 电机→[7103×2] · 泵→[7104×2]绿
S1-05命中带出处四字段(可引用) {"id":1,"frag_id":7101,"score_q8":255,"text":"轴承外圈故障特征频率 BPFO…","src":"cwru"}绿

1.2 时序异常:12 分钟窗口,第 8 分钟幅值 ×8

判据内容实测结果
S1-0612 个分钟窗口全部注入成功12/12 rc=0绿
S1-07窗口内 L1 行数精确等于 12rows=12绿
S1-08异常分钟 rms ≥ 2× 正常段均值正常均值 111.0 / 异常 890.0 → 8.02×绿
S1-08b正常段各分钟量级一致(无假阳性)min=111.0 max=111.0(完全无波动)绿
S1-09六种聚合算子全有值min=-1617 max=1617 mean=0 std=278.033 rms=278.033 pp=3234绿
S1-10同分钟重复注入被明确拒绝(不静默覆盖)rc=-5 EM_E_EXISTS,附人话:「该分钟已有聚合行…请把 ts_us 错开到下一分钟」绿

逐分钟 rms 曲线(修复前后对照,这是本轮最关键的取证)

修复前(闭区间 [t0,t1]):111 111 111 111 111 111 634 634 111 111 111 111
                                              ↑ 第7格本该111  ↑ 真值被稀释成 634
修复后(左闭右开 [t0,t1)):111 111 111 111 111 111 111 890 111 111 111 111
                                                   ↑ 异常精确定位在第8格

8.02× 与注入的 8× 幅值数学自洽;修复前的 634 恰为 sqrt((111²+890²)/2), 是「一格混进两行」的数学确证,不是巧合。

2. S2 野外巡检(9 条,双板全绿)

判据内容实测结果
S2-01 / 02基础包 8101 / 专家包 8102 分层灌入各 accepted=3/3绿
S2-03frag.list 列出 2 个分层碎片ids=[8101,8102]绿
S2-04通用词「巡检」只命中基础包count=2, frag_ids=[8101,8101]绿
S2-05专家专属词「污闪」只命中专家包count=1, frag_ids=[8102]绿
S2-06ADR-014:无关查询拒答count=0, reject=LOW「全库最高分低于 min_score_q8」绿
S2-07拒答时不回退「最接近一条」hits=0绿
S2-08 / 09ADR-014:红线条目命中即拒且不返回内容写入 id=4;查询 reject=REDLINE, count=0绿

S2-06 / 07 / 09 三条是产品承诺「有边界」的直接落点—— 宁可说「不在范围内」,也不给一条勉强沾边的结果。这三条绿,ADR-014 才算真落地,不是写在文档里。

3. S3 私域知识库(5 条,双板全绿)

判据内容实测结果
S3-0112 包 × 10 条全部灌入accepted 合计 = 120 / 120绿
S3-02frag.list 列出 12 个碎片n=12绿
S3-03库条数对账精确lib_count=120,期望 120绿
S3-04随机不重复抽样 8 条,每条精确命中 1 条 0079 0071 0088 0092 0074 0119 0005 0087 → 全 1绿
S3-05反向对照:库外编号查不到0897 → count=0, reject=LOW绿

3.1 大批量灌库:G1 判据由 .emf 产品通道实测达成(Dev-B)

上面 S3 的 120 条是正确性验证(受单行上限约束,单包只能 ~1.2 KB)。 本轮补齐了规格第 343 / 377 行本就定义的分块灌库通道 (frag.begin / chunk / commit / abort,此前接的是整包 b64,与规格不符), 于是 12,000 条可以一个包灌进去。

判据Dev-B 实测(COM4,先 em.wipe 清库)结果
单包灌入 12,000 条.emf = 907,992 B / 12,000 条 / CRC32=0x662C9D01 → 887 块,211.9 s绿
accepted 与库增量对账accepted=12000;lib_count 0 → 12000(增量 12000)绿
重启后保全复位重启后 em.stat count 仍 = 12000,rec_pages 258/3040绿
抽样查回(5 个分散位置)kb 0 / 777 / 4321 / 8888 / 11999 → 均精确命中,frag_id=8701绿
唯一编号不重复kb 11999、kb 8888 均 count=1绿
坏 CRC 不破坏已有库(规格铁律)commit → rc=-9 明确报错,imported=0,库条数前后不变、原有记忆仍可检索绿
跳块 / 部分重叠 / 重发内容不符均 rc=-1 明确拒绝,received 不被推进绿

为什么串口侧是 1 KB 分块而不是规格写的 4 KB: LINE_MAX=2048,base64 膨胀 4/3 —— 1024 B → 1368 字符 + JSON 包裹 ~77 B = 1445 ≤ 2047; 4096 B → 5541 ≫ 2047,整行会被 cli_task 直接丢弃且零响应。 4 KB 是 HTTP 侧口径(请求体无单行上限),属承载差异不是退化;两侧共用同一套 commit 校验。 此结论已回填规格 §5.4。

3.2 头域翻转实测:commit 那道整体 CRC 是承重的,不是冗余

引擎 em_frag_import() 对 32 B 文件头只有结构校验、没有完整性校验: 它验 magic / version / header_len / 长度自洽 / payload_crc32, 但不验 frag_id、flags、v2 的 vec_dim。 而 payload CRC 对这三类损坏完全无感(payload 一个字节没动)。 所以分块通道 commit 那道覆盖整个文件(含 32 B 头)的 CRC32 是真正在扛事的那一道。 这一点已做成板端实测,不是推断:

判据内容实测(COM3,S3b 场景)结果
S3b.1翻转后 payload CRC 仍自洽(反证:引擎单独看不出问题) 头内 payload_crc32 = 0x6DAF6629,实算 = 0x6DAF6629 → 相等,必然放行PASS
S3b.2commit 被整体 CRC32 拦下 rc=-9:实收 0x300719CD vs begin 承诺 0x68828C92;已丢弃该包,记忆库未被触碰PASS
S3b.3未导入任何条目imported = 0PASS
S3b.4库条数前后不变1085 → 1085PASS
S3b.5翻转出的碎片域没有被凭空建出来 frag.list 现域中无 8402(原值 8403)PASS

数字版本说明:上表是重构后固件 v1.20261028-160744 上重跑的结果, 以这组为准(同一天更早的旧固件上是另一组数)。 ⚠ 引用纪律:这类 16 进制数是"那一次、那个包"的量,同一天三次复现得到三组不同数值 (0x9AA0EE81 → 0x18B91930 → 0x6DAF6629), 结论稳定、数值每次不同。判据是「两者是否相等」,禁止把任何一组数抄进断言当期望值—— 那会在换包后变成"期望值过期导致的假红"。

不拦会怎样:运输途中把头里 frag_id(offset 28)最低位翻掉, 那批记录会静默落进一个凭空冒出来的碎片域——事后在 frag.list 上只看到 「多了个不知道哪来的碎片」,无从追溯。flags bit0 被翻则整包变 PINNED_ALL 且无人发现。

口径提醒:整包少传字节时引擎返回的是 EM_E_INVAL(-1) 不是 EM_E_CORRUPT(-9)(长度自洽属结构校验)。做 G7 口径统计时别把它算成介质/校验失败。

一个没验成的项(明确列出):跨进程重启后的续传在串口侧验不了—— 本项目每开一次串口就复位一次板子,会话缓冲在 PSRAM 里,复位即丢。 真正验到的是「响应丢失 → 原块重发」与「坏块 → 明确拒 → 续传」两条板端语义; 「真杀进程再 --resume」那一步受物理条件所限没跑到,不拿"反正结果对"糊过去。

4. 过程中查出并修复的两个真缺陷

4.1 时序窗口是闭区间 → 已立 ADR-019 改成左闭右开

现象:逐分钟 rms 曲线整体错位一格,异常被「提前」一格报出,真值被稀释(890 → 634)。

根因:em_ts_query.c 里 emlite_ts_agg() 判 m_us > t1 排除、 emlite_ts_window() 判 base+k > m1 排除 —— 两者都是闭区间。 按「第 i 分钟」的自然写法查 [base+i, base+i+1),会把下一分钟那行也算进来。

规格状态:02 分册 §ts.query / §ts.agg 原只给了函数签名、未定义开闭区间。 所以这不是违反规格,而是规格缺口——已回填条款并立 ADR-019(左闭右开是时序 API 通行约定:相邻窗口不重不漏)。

修复中发现的第二个坑(比原缺陷更隐蔽):不能简单地把 agg 改成比微秒、window 保持比整除分钟—— t1 不是整分钟时 m1 = t1 / 60s 的整除会比微秒更严一格,两个函数的行集合就分叉了 (实测 6/6 全分叉)。故两函数统一按分钟序号 mi = base + k 与 [m0, m1) 比较,口径逐字一致。

4.2 base64 缓冲区比需要少 1 字节 → 「包太大」被误报成「包坏了」

现象:S3 首轮 12 包全红,报 rc=-1「包非法(magic/version/… 任一不符)」——但包本身是合法的。

根因:cli_json.c 原 static char b64[1600]。1199 字节的 .emf 恰好产出 1600 字符 base64,而放进 char[1600] 需要 1601 字节(含结尾 NUL) ⇒ arg_str → mj_str 静默截成 1599 ⇒ 解出的包短 1~2 字节 ⇒ 引擎判「包非法」。

讽刺点(值得立为通用教训):那段代码注释明写「绝不静默截断、别把包太大误报成包坏了」—— 但防线装在 b64_decode 里,截断却发生在更早的 arg_str 拷贝,防线根本没被触发。 已修:b64[LINE_MAX] + 贴边即判太大(strlen(b64) >= sizeof-1 → 明确报「包太大请分包」)。

5. 诚实边界:这份记录没有证明什么

  1. S3 的 120 条那条判据只验正确性(受单行上限约束,单包 ~1.2 KB); 大批量容量已由 §3.1 的分块通道单独实测(Dev-B 12,000 条,重启保全、抽样可查回)。 另有一条更早的容量结论来自 em.bulk 脚手架实测(Dev-B 97,737 / Dev-A 23,132, 见 G1 容量实测记录)——那是灌库脚手架不是产品通道,两个数各有用途,别混着引用。
  2. HTTP 侧 /api/frag/import 仍未接线(规格 02 分册第 377 行)。 本轮只做了串口侧;main/http_srv.c 里目前没有任何 api/frag 处理,这条欠账还在。
  3. 时序验的是「异常分钟能否被聚合值检出」,不是频谱分析、不是波形回放。 L0 不落 flash(ADR-006),板上没有原始波形;ts.info 已如实输出 raw_waveform=false。
  4. 数据源是板内确定性合成源(CWRU 风格正弦叠加),不是真实传感器。 S1 的「故障指纹」条目是人工编写的领域知识文本,不是从真实振动数据学习的。 此前不得对外宣称召回率已验证。
  5. score_q8=255 是过滤路径的固定值,不是真实相似度——响应里的 match_mode 字段会说明匹配方式, 别把 255 当相似度读。
  6. frag.list 的 name / ver 恒为未知(32 B .emf 头里没有这两个字段), 如实输出空串与 0,不假装有名字。同名高版本覆盖(规格 5.3)本期未实现,返回 EM_E_NOSUPPORT。
  7. 本次每个场景开头都执行了 em.wipe 清库(保证条数可精确对账)。这是破坏性操作, 生产路径是否开放需单独评估。
  8. G7 掉电台架仍未搭,本轮修的两个缺陷与断电路径同源,搭好台架后必须补测。

6. 复现方法

cd /c/emgw-build
python em_scenarios.py COM3 --scenario all     # Dev-A(γ 磁吸卡)
python em_scenarios.py COM4 --scenario all     # Dev-B(β USB Dongle)
# 单场景:--scenario s1|s2|s3   不清库复跑:追加 --no-wipe

脚本自带 36 条断言,红/绿逐条打印并汇总。 每个场景开头 em.wipe 清库,清库后必须轮询等引擎就绪(wipe 只擦页 0..7, 开库判 CORRUPT 后整片重建,Dev-B 需几十秒)——定时睡眠会误判成「卡死」。

判据的可失效性(诚实纪律要求「已实测」必须落到会随结论失效变红的用例上): 区间语义若回退成闭区间 → S1-08b 立即变红; ADR-014 若被绕过 → S2-06/07/09 变红; 灌库若丢条或串号 → S3-03 对账与 S3-04 抽样变红。