MF4 补齐数值换算 2/3/4/5/6 + ##CA 数组通道 + 数值型 VLSD

标定量(特性曲线 / 公式)与「一个通道装整条曲线」的数组通道是 CANape /
Simulink 导出 MF4 时最常见的两种表达,之前只支持线性换算、数组通道整列丢失。

一、数值换算(cc_type 2/3/4/5/6)
- ChannelSpec 把 linearConversion/offset/factor 换成 ConversionType(0-8)+
  conversionValues[],ParseChannel 按 cc_type 校验参数个数后填充。
- 新增 ApplyConversion:
  1 线性  v0 + v1*raw
  2 有理式 (v0 + v1*raw) / (v2 + v3*raw)
  3 代数式 (v0*raw^2 + v1*raw + v2) / (v3*raw^2 + v4*raw + v5)
  4 表插值(交错 (键,值) 对,区间内线性插值、区间外夹紧)
  5 表阶梯(同一张表,取键 <= raw 的最后一段)
  6 区间映射((下限,上限,物理值) 三元组)
- 参数个数不符 / 表为空 / 分母为 0 一律返回原始值(宁可显示未标定值,
  也不显示编造值);线性换算 v0 == v1 == 0 视为未配置而非恒为 0。

二、##CA 数组通道(一个通道装整条曲线)
- 解析 CN 链接[1] 的 ##CA 块:+0 ca_type / +1 ca_storage / +2 ndim(U16) /
  +4 flags(U32) / +16 dim_size[U64](步长 8,Simulink 与 CANape 实测一致)。
- 新增 ExpandArrayChannel:仅当 ca_storage == 0(元素在记录内首尾相连)、
  1 < 元素数 <= 1024、bit_count != 0 时展开成多列,bit_offset += i*bit_count,
  名字 name[i] / name[i][j],换算与枚举复制给每个元素。
- MF4-Record.MF4 信号数 124 -> 283(9 点特性曲线读到 6000..14000,
  二维 map 读到 175/120/180...)。

三、VLSD 数值型
- 数值型不再要求 cn_bit_count > 0;新增 InferVlsdElementBits(按样本长度
  优先整除 64,再 32/16/8),DecodeVlsdValue 只按一个元素做越界检查并取
  样本首个元素;主通道候选排除 VLSD(其记录里存的是 ##SD 偏移)。
- 实测 4 个真实文件里 VLSD 通道只有 Comment(字符串、cycles = 0),
  数值型 VLSD 数量为 0,故本次不改变现有文件的输出。

验证(全离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/convcheck:现场合成一个最小 MDF4(1 DG / 1 CG / 2 记录 /
  recordBytes = 72),一次覆盖 cc_type 1-6、##CA、数值 VLSD => 11/11。
- A/B:强置 conversion = NONE 且关闭数组展开 => 8 项如实失败,还原后 11/11。
- 回归:check 137/137、editcheck 20/20、round3check 31/31、round4check 34/34;
  真实文件信号数 283 / 8 / 18 / 24 与预期一致。
- make release / make debug 零警告;TargetCheck.exe 804,352 字节(785.5 KiB)。

文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 160-164 条硬约束
与文末本轮小节(含 VLSD / 单位在文件中的实际情况报告)。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-20 11:25:03 +08:00
1 parent 181f95cc3a
commit b315201598
2 files changed
+477 -47

No files matched your search

+91
View File
@@ -3003,6 +3003,8 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
| 2026-10-20 | DeepSeek-V4.1-Flash(**本机**) | **MF4 `##SD`/`##SI`/VLSD + 信号枚举文本 `enumText`(光标尾缀)+ `.mat` 由内存映射改为随机读(详见 §12 末尾「2026-10-20」)**。① **§2.6 两处**:`LoadPlainObject` 不再 `ReadDataset` 二次解码(改用 `ReadDatasetInfo` 取维度);`MdfFile::TextBlock` 真正支持 `##MD`(UTF-8 XML)。② **枚举**:新增 `SignalEnumText`(`values[]`/`texts[]`/`Find`)挂在 `DataTable::columnEnumText` 与 `SignalData::enumText`;MF4 `cc_type = 7` 按 `cc_val[i] ↔ cc_ref[4+i]` 解析。界面只在**光标取值标注**追加 ` = <枚举文本>`,常规状态零变化。`NEW-MF4.mf4` 实测 8 信号 / 4 条带枚举(32+5+32+5 项)。③ **MF4**:`##SI` 采样索引(≥2 条且全部 < 周期数才生效;实测工具会给每个通道挂只含常量 513 的 `##SI`,必须忽略)、`##SD` + VLSD(`UINT32` 长度前缀 + 64 位偏移,直接偏移优先、`>>1` 回退)、CN 链接下标按链接总数反推(4.0 的 8 链接 / 4.1 的 9 链接)、主通道接受 `sync_type` 1/2/3 + `cg_flags` 远程主通道、记录长度优先 `cg_data_bytes`、进度分母改真实 DG 数并上报字节、`##DZ` 28 字节头修正。④ **`.mat`**:删除 `MappedBytes`,改 `ReadFile` 随机读 + 16×64KB 元数据 LRU + 按需数据读取(固定 4MB 预读会把读盘量放大 2.2 倍 ⇒ 改后 1.0 倍);新增 rank=2 连续 chunk 快路径(12.9 亿次逐行迭代 ⇒ 每块一次回调),`BIG_MAT.mat` **21.5s → 7.9s**、私有 +741MB;进度按真实 `bytesRead / totalBytes` 推进(不再一上来 100%),取消会中止而不是留下 NaN 空洞。验证:新增离屏 `loadercheck` **32/32**、`enumui` **12/12**;回归 `datacheck` 69/69、`matcheck` 21/21、`matui` 15/15、`verify` 49/49;`make release`/`make debug` 零警告;`Target.exe` 785,920 → **820,224 字节**。 |
| 2026-10-21 | DeepSeek-V4.1-Flash(**本机**) | **MF4 数值换算类型 2/3/4/5/6 + `##CA` 数组通道完整支持 + 数值型 VLSD(详见 §12 末尾「2026-10-21」与 §11.2 第 160–164 条)**。① **换算**:`ChannelSpec` 把 `linearConversion/offset/factor` 换成 `conversion`(枚举 0–8)+ `conversionValues[]`,新增 `ApplyConversion`:`1` = `v0+v1*raw`、`2` = `(v0+v1*raw)/(v2+v3*raw)`、`3` = `(v0*raw²+v1*raw+v2)/(v3*raw²+v4*raw+v5)`、`4` = 交错 `(键,值)` 对线性插值(端点夹紧)、`5` = 同表阶梯取值、`6` = `(下限,上限,物理值)` 三元组区间映射;**参数个数不够或格式不符一律原样返回原始值,绝不猜**。② **`##CA` 数组**:CN 链接[1] → `ca_type(+0)/ca_storage(+1)/ndim(U16,+2)/flags(U32,+4)/dim_size[](U64,**+16 起、步长 8**,实测 Simulink 与 CANape 一致)`;`ExpandArrayChannel` 只在 `ca_storage == 0`(元素在记录内首尾相连)、`1 < 元素数 ≤ 1024`、`bit_count != 0` 时展开成多列,名字 `name[i]` / `name[i][j]`,复用同一换算与枚举。`MF4-Record.MF4` 信号数 **124 → 283**(9 点特性曲线 → 6000…14000;二维 map → 175/120/180…)。③ **VLSD**:数值型不再要求 `bit_count > 0`(新增 `InferVlsdElementBits` 按长度整除推 64/32/16/8),`DecodeVlsdValue` 只按一个元素做越界检查并取样本首个元素;主通道候选排除 VLSD。实测 4 个 MF4 文件的 VLSD 通道只有 `Comment`(字符串、`cycles = 0`),**数值型 VLSD 数量为 0** ⇒ 本次不改变现有输出。④ **单位**:4 个真实文件的 `cn_md_unit` **全部为空链接**;单位只出现在 `cn_md_comment` 的 `<TX>` 文本里(如 `(单位:MV )`、`(单位:0.1*N-700℃)`)与 CANape `<display>` 区间串里,故本轮不读单位。验证:新增离屏 `.preflight/convcheck`(**现场合成**一个 1 DG / 2 记录的 MDF4,覆盖 cc_type 1–6、`##CA`、数值 VLSD)**11/11**;**A/B 禁用新换算 + 数组展开 ⇒ 8 项如实失败**;回归 `check` 137/137、`editcheck` 20/20、`round3check` 31/31、`round4check` 34/34,真实文件信号数 283 / 8 / 18 / 24 不变;`make release`/`make debug` 零警告;`TargetCheck.exe` **804,352 字节**(785.5 KiB)。 |
## 11. 跨设备协作须知(起源设备视角)
> **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。
@@ -3775,6 +3777,25 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
⚠️ 时间读数/Δt 是在取值标注**之后**绘制的,所以圆点必然被盖住;
⚠️ 像素级断言必须钉在**标记锚点附近的小框**:取值胶囊也用信号色作底,
数“面板上方的信号色像素”会把漏画三角误判为通过(A/B 已证)。
160. ⚠️⚠️ **MDF4 数值换算按 `cc_type` 分派,参数个数不匹配时必须原样返回**(用户 2026-10-21 要求
支持特性曲线 / 公式 2/3/4/5/6):`1` 线性 `v0+v1*raw`;`2` 有理式 `(v0+v1*raw)/(v2+v3*raw)`(需 ≥4);
`3` 代数式 `(v0*raw²+v1*raw+v2)/(v3*raw²+v4*raw+v5)`(需 ≥6);`4` 表插值(`(键,值)` 交错、
需偶数且 ≥4,端点外夹紧);`5` 表阶梯(同一张表,取 ≤raw 的最后一段);`6` 区间映射
(`(下限,上限,物理值)` 三元组、需 3 的倍数且 ≥3)。⚠️ 分母为 0、参数个数不符、表为空
**一律返回原始值**(宁可显示未标定值,也不要显示编造值);⚠️ 线性换算 `v0 == v1 == 0`
视为「未配置」而非「恒为 0」。实测 4 个真实文件只用到 `0/1/7` ⇒ 2–6 只能用**合成文件**验证。
161. ⚠️⚠️ **`##CA` 数组块的维度表从数据段 +16 开始、步长 8 字节**:`+0 ca_type`、
`+1 ca_storage`、`+2 ndim(U16)`、`+4 flags(U32)`、`+16 dim_size[U64]`(Simulink 与 CANape
导出的文件已实测一致)。**不要**写成 +8 或按 4 字节步长读,否则维度全错。
162. ⚠️⚠️ **`##CA` 只支持 `ca_storage == 0`**(所有元素在记录里首尾相连,`bit_offset + i*bit_count`):
展开成多列需 `1 < 元素数 ≤ 1024` 且 `bit_count != 0`,名字 `name[i]`(1 维)/ `name[i][j]`(2 维),
换算与枚举要**复制**给每个元素。`ca_storage != 0`(按通道/按引用存放)暂不支持 ⇒ 保留原样列,
**不要**猜元素位置。
163. ⚠️⚠️ **数值型 VLSD 不要求 `cn_bit_count > 0`**:VLSD 通道的 `bit_count` 常写 0,元素位宽要
`InferVlsdElementBits`(按样本长度优先整除 64,再 32/16/8)反推;多元素样本**取第一个元素**。
判定数值型时只要求 `cn_data_type` 是数值类型,不能再要求位宽(旧实现会让整列被丢弃)。
164. ⚠️ **VLSD 通道不得作为主通道(时间轴)候选**:其记录里存的是 `##SD` 偏移,不是物理量。
`SelectMasterChannel` 的顺序是「`cn_sync_type` 命中**且**数值**且**非 VLSD」→ 名字命中 → 首个数值通道。
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
@@ -6731,3 +6752,73 @@ DG3 data=0 cycle=0 (空组,一个字符串通道)
(字节进度与逐流解码进度取较大值),收尾 1.0。遮罩层百分比不再「一上来就 100%」。
取消会置 `m_readCancelled` 并让 `StreamDataset` 立即失败(不会留下 NaN 空洞)。
- 连续布局数据不足(文件被截断)现在**明确失败**,不再静默截短。
---
## 本轮(2026-10-21,本机):MF4 换算类型 2/3/4/5/6 + `##CA` 数组通道 + 数值型 VLSD
> 用户要求:对标定量(特性曲线 / 公式)与 `##CA` 数组通道补全支持,VLSD 只做数值型;
> 完成后**列举 MF4 支持情况**、说明文件里的 VLSD 内容、报告单位相关发现。
> 全程离屏(`.preflight` 合成文件 + 数据层断言),不占用用户的鼠标 / 键盘 / 屏幕。
### A. 换算:`linearConversion/offset/factor` → 类型 + 参数表
- `ChannelSpec` 原先只有线性的三件套,2–6 号换算根本没位置放参数 ⇒ 换成
`ConversionType conversion`(`NONE/LINEAR/RATIONAL/ALGEBRAIC/TAB_INTP/TAB/TAB_RANGE/
VALUE_TO_TEXT/VALUE_RANGE_TO_TEXT`)+ `std::vector<double> conversionValues`。
- 新增 `ApplyConversion(spec, raw)`,`ParseChannel` 按 `cc_type` 校验参数个数后填 `conversionValues`:
- `cc_type = 1` 线性 `v0 + v1·raw`(`v0 == v1 == 0` 视为未配置 ⇒ 恒等);
- `cc_type = 2` 有理式 `(v0 + v1·raw) / (v2 + v3·raw)`;
- `cc_type = 3` 代数式 `(v0·raw² + v1·raw + v2) / (v3·raw² + v4·raw + v5)`;
- `cc_type = 4` 表插值:`cc_val[]` 是**交错**的 `(键, 值)` 对,raw 落在两点之间时线性插值,
区间外夹紧到端点值;
- `cc_type = 5` 表阶梯:同一张表,取「键 ≤ raw 的最后一段」的值(不插值);
- `cc_type = 6` 区间映射:`(下限, 上限, 物理值)` 三元组,命中哪个区间就用哪个物理值。
- ⚠️ **安全兜底**:参数个数不足 / 表为空 / 分母为 0 ⇒ **返回原始值**。
用户看到未标定值可以自己判断,看到编造值会误判数据。A/B 已证这些 fallback 不影响正常路径。
### B. `##CA` 数组通道(一个通道装整条曲线)
- 真实文件里只有 `MF4-Record.MF4` 带 `##CA`:25 个通道,维度 `[9]` / `[7]` / `[7,9]` / `[2]`,
全部 `ca_storage == 0`、`dgCycles == 1`、CN `flags = 0x14`。
- 字节布局(hexdump 实测确认):`+0 ca_type`、`+1 ca_storage`、`+2 ndim(U16)`、`+4 flags(U32)`、
**`+16 dim_size[]`(U64,步长 8)**,维度表之后数据段就结束。
- `ExpandArrayChannel(ChannelSpec&&, channels)`:仅当 `ca_storage == 0`、`1 < 元素数 ≤ 1024`、
`bit_count != 0` 时展开 —— 每个元素一列,`bit_offset += i * bit_count`,
名字 `name[i]`(1 维)/ `name[i][j]`(2 维),换算与枚举**复制**给每个元素。
- 效果:`MF4-Record.MF4` 信号数 **124 → 283**;9 点特性曲线读到 6000…14000,
二维 map 读到 175/120/180…(与文件里的 `Win_CAL_*` 标定表目视一致)。
### C. VLSD:数值型完整支持,非数值型仍不支持
- 旧代码 `numeric = IsNumericType(dataType) && bitCount > 0 && bitCount <= 64` 把
`cn_bit_count == 0` 的 VLSD 通道整列丢掉 ⇒ 改为:VLSD 只要求「数值类型」。
- 新增 `InferVlsdElementBits(type, length)`:按样本长度整除优先 64,再 32/16/8;
`DecodeVlsdValue` 不再要求位宽非 0,越界检查只做**一个元素**,多元素样本取第一个元素。
- `SelectMasterChannel` 增加 `!vlsd` 条件(VLSD 记录里存的是 `##SD` 偏移,当时间轴会得到垃圾值)。
- ⚠️ 实测 4 个真实 MF4 文件里 **VLSD 通道各只有 1 个,且都是 `Comment` 字符串**(`cycles = 0`),
**数值型 VLSD 数量为 0** ⇒ 本轮改动不改变任何现有文件的输出(只是补上了能力)。
### D. 单位:文件里几乎没有
- 4 个真实文件的 `cn_md_unit` **全部是空链接**(非空单位的通道数 = 0),所以现在没有单位可显示。
- 单位信息只以**文本**形式出现在两处:
1. `cn_md_comment` 的 `<TX>` 里,例如 `车窗堵转电流阈值电机驱动电源断点(单位:MV )`、
`车窗电流堵转阈值温度断点(单位:0.1*N-700℃)`、`1号车窗电机上堵转电流阈值(单位:0.1A )`;
2. 同块 XML 里的 CANape `<display>` 串:`CANape: 65280 2 0 11 -55.2808 75.5730 0 3 -2 1 3`
(含显示区间 / 小数位等)。
- 结论:**读单位要用 `cn_md_unit`,但当前样本文件里它是空的**;若要显示单位,只能退而从
`<TX>` 里正则抽取(脆弱)或解析 CANape `<display>`(格式未公开)。建议等拿到带单位的样本再定。
### E. 验证
- 新增 `.preflight/convcheck`:**现场合成**一个最小 MDF4(64 字节 ID + `##HD` + 1 DG / 1 CG /
2 条记录 / `recordBytes = 72`),一次覆盖 cc_type 1–6、`##CA`(4 元素、`storage = 0`)、
数值 VLSD(`bit_count = 0` + `##SD` 里两个 double)⇒ **11/11 通过**。
- **A/B**:把 2–6 的 `spec.conversion` 强置 `NONE` 并把数组展开上限改成 0 ⇒ **8 项如实失败**
(5 个换算 + 3 个数组断言),还原后 11/11,证明断言不是自证。
- 回归:`check` 137/137、`editcheck` 20/20、`round3check` 31/31、`round4check` 34/34;
真实文件信号数 `MF4-Record` 283 / `Problem-MF4` 8 / `左后门意外下降` 18 / `按前门开关,后门动了` 24。
- `make release` / `make debug` **零警告**;`TargetCheck.exe` **804,352 字节**(785.5 KiB,仍在 800KiB 以内)。
- ⚠️ 复测探针时**不要覆盖正在运行的 `Target.exe`**:统一用 `make release TARGET_FILE_NAME=TargetCheck`。