MF4 ##CA 全字段/三维 + cc_type=8 区间枚举 + 稠密包络带线宽双向加粗

一、##CA 数组通道:从「按魔数猜」改成「按记录容量校验」
- 头字段全部按块长度做边界检查后读取:+0 ca_type / +1 ca_storage /
  +2 ndim(U16) / +4 flags(U32) / +8 ca_byte_offset_base(U16) /
  +10 ca_inval_bit_pos(U16) / +16 dim_size[](U64,步长 8),
  其后才是**可选**的 cycle_count[](U32)。
  ⚠️ 实测 CANape 写出的文件没有 cycle_count(块长度恰好到 dim_size 就结束),
  盲读会把下一个块头当数字(实测得到 1481909027)。
- 展开条件(任何一条不满足就原样保留,只显示元素 0,绝不猜):
  ca_storage == 0;ca_type <= 2(0 数组 / 1 缩放轴 / 2 区间轴,三者物理布局相同);
  cn_bit_count ∈ [1, 64];cycle_count 每维 <= 1(元素不被拆到多条记录);
  bit_offset + 元素数 × 位宽 <= cg_data_bytes × 8(真正的完整性校验)。
  规模护栏:元素数 <= 16384 且 元素数 × 记录数 <= 32M 单元格
  (取代原来的 count > 1024 魔数)。
- 元素名按 dim_size 从末维向前取余,1/2/3 维分别得到 [i] / [i][j] / [i][j][k]。
- MF4-Record.MF4 仍是 283 个信号(25 个 ##CA 全部通过新的容量校验,最大 7x9=63)。

二、cc_type = 8(值 -> 文本,区间表)补齐
- cc_val[] 交错存放 (下限, 上限),cc_ref[4 + i] 是第 i 段文本(最后一个引用是默认文本,不使用);
  cc_val_count 必须是偶数(奇数说明表残缺,整表丢弃);下限 > 上限时交换。
- SignalEnumText 新增 rangeMin / rangeMax / rangeTexts,并拆出 HasExact() / HasRanges();
  Find() 先查精确表(cc_type = 7)再查区间表(cc_type = 8,闭区间)。
- 界面没有改动:两种枚举共用同一出口,只在**光标取值标注**追加 " = <文本>",
  常规状态零变化。

三、修复:稠密包络带下改变线宽时竖直沿不变粗
- 根因:包络带每列只有 1 个 x 坐标,旧实现只在 top/bottom 各外扩半个线宽
  => 水平段会变粗,但方波竖直沿(上升/下降段)的横向宽度恒为 1 个像素列。
- 修法:EmitEnvelopeBand 新增 halfWidth 形参,先对上下包络做横向窗口膨胀
  (top 取窗口最小、bottom 取窗口最大,半径 ceil(halfWidth / 列距)),再纵向各外扩;
  膨胀只在同一段内生效,不会跨 NaN 断点连线。

验证(全离屏,不占用鼠标/键盘/屏幕)
- .preflight/convcheck 18/18:新增 3 维命名与取值、元素放不进记录时必须拒绝展开、
  cc_type = 8 的命中/未命中。
- 新增 .preflight/round5check 5/5:合成 20 万采样数字信号(必然走包络带),
  在网格中间行测量竖直沿横向像素宽度:线宽 1px -> 6px,10px -> 30px。
- A/B:临时把横向膨胀半径置 0(等价旧行为)=> 1px -> 2px、10px -> 2px 且如实 FAIL。
- 回归:check 137/137、editcheck 20/20、round3check 31/31、round4check 34/34;
  真实 MF4 信号数 283/8/18/24 不变;make release / make debug 零警告;
  TargetCheck.exe 808,448 字节(789.5 KiB)。

文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 165-170 条硬约束与文末本轮小节
(含「6 位有效数字」的口径说明)。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-20 14:29:49 +08:00
1 parent b315201598
commit 30f853a361
5 files changed
+460 -45

No files matched your search

+116
View File
@@ -3005,6 +3005,8 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
| 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)。 |
| 2026-10-20 | DeepSeek-V4.1-Flash(**本机**) | **MF4 `##CA` 全字段(`ca_type`/`cycle_count`/`byte_offset_base`)+ 3 维命名 + 记录容量校验;`cc_type = 8` 区间枚举;稠密包络带的线宽改为「纵横双向加粗」(详见 §12 末尾「2026-10-20 第五轮」与 §11.2 第 165–170 条)**。① **`##CA`**:`ce_type`/`ca_storage`/`ndim`/`flags`/`ca_byte_offset_base`/`ca_inval_bit_pos`/`dim_size[]`/`cycle_count[]` 全部按**块长度做边界检查**后读取(`cycle_count[]` 实测缺失,盲读会拿到下一个块头 1481909027);展开条件从「数一个魔数上限」改为**记录容量校验** `bit_offset + 元素数×位宽 ≤ cg_data_bytes×8` + `cycle_count 每维 ≤ 1` + `ca_type ≤ 2`;规模护栏改为「元素数 ≤ 16384 且 元素数×记录数 ≤ 32M 单元格」;名字按 `dim_size[]` 逐维取余 ⇒ 1/2/3 维分别得到 `[i]`/`[i][j]`/`[i][j][k]`。② **`cc_type = 8`**(值区间→文本):`cc_val` 交错 `(下限,上限)`、`cc_ref[4+i]` 为第 i 段文本,`SignalEnumText` 新增 `rangeMin/rangeMax/rangeTexts` + `HasRanges()/Count()`,`Find` 先精确表后区间表;**仍然只在光标取值标注里追加 ` = <文本>`**。③ **线宽**:包络带旧实现只在每列**纵向**外扩 `strokeHalf` ⇒ 方波的竖直沿横向宽度始终是 1 个像素列(用户把线宽 1px 拖到 10px 时竖直沿完全不变);`EmitEnvelopeBand` 新增 `halfWidth` 形参,对上下包络做**横向窗口膨胀**(`top` 取窗口最小、`bottom` 取窗口最大)后再纵向各外扩,两个方向都随线宽变化。验证:`convcheck` **18/18**(新增 3 维命名、放不进记录的数组必须拒绝展开、区间枚举命中/未命中);新增 `.preflight/round5check` **5/5**(合成 20 万采样数字信号 ⇒ 包络带,中间行竖直沿墨迹 1px→6px、10px→30px);**A/B 关闭横向膨胀 ⇒ 1px→2px、10px→2px 并如实失败**;回归 `check` 137/137、`editcheck` 20/20、`round3check` 31/31、`round4check` 34/34,真实 MF4 信号数 283/8/18/24 不变;`make release`/`make debug` 零警告;`TargetCheck.exe` **808,448 字节**(789.5 KiB)。 |
## 11. 跨设备协作须知(起源设备视角)
> **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。
@@ -3796,6 +3798,39 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
判定数值型时只要求 `cn_data_type` 是数值类型,不能再要求位宽(旧实现会让整列被丢弃)。
164. ⚠️ **VLSD 通道不得作为主通道(时间轴)候选**:其记录里存的是 `##SD` 偏移,不是物理量。
`SelectMasterChannel` 的顺序是「`cn_sync_type` 命中**且**数值**且**非 VLSD」→ 名字命中 → 首个数值通道。
165. ⚠️⚠️ **`##CA` 的所有字段都必须按「块长度」做边界检查后再读**:数据段布局是
`+0 ca_type` / `+1 ca_storage` / `+2 ndim(U16)` / `+4 flags(U32)` /
`+8 ca_byte_offset_base(U16)` / `+10 ca_inval_bit_pos(U16)` / `+16 dim_size[](U64 × ndim)`,
随后才是 **可选**的 `cycle_count[](U32 × ndim)`。
⚠️ 实测 CANape 写出的文件**没有** `cycle_count[]`(块长度恰好到 `dim_size[]` 结束)⇒
盲读会读到下一个块头(实测得到 1481909027),必须用 `BlockLength - (data - block)` 判断。
166. ⚠️⚠️ **`##CA` 能否展开由「记录容量」决定,不再用元素数魔数**:
`bit_offset + 元素数 × 位宽 ≤ cg_data_bytes × 8` 是真正的完整性校验(元素是否真的都在本记录内);
再加上 `ca_storage == 0`、`ca_type ∈ {0,1,2}`、`cn_bit_count ∈ [1,64]`、`cycle_count 每维 ≤ 1`。
规模护栏:元素数 ≤ **16384** 且 `元素数 × 记录数 ≤ 32M` 单元格(防止畸形巨型数组把列表/网格撑爆)。
⚠️ `ca_type` 0/1/2 的**物理布局相同**(实测 25 个块全部是记录内连续),差别只在语义
(0 数组 / 1 缩放轴 / 2 区间轴)——所以三者都展开;`ca_type > 2` 视为未知,不展开。
167. ⚠️ **`##CA` 元素名必须按 `dim_size[]` 逐维取余**:从末维向前 `index % dim_i` 再 `/= dim_i`,
因此 1/2/3 维分别得到 `name[i]` / `name[i][j]` / `name[i][j][k]`。
⚠️ 旧实现只对「2 维」特判,3 维会退化成平面下标 `name[k]`(丢掉维度信息)。
168. ⚠️⚠️ **MF4 `cc_type = 8`(值区间 → 文本)**:`cc_val[]` **交错**存放 `(下限, 上限)`,
`cc_ref[4 + i]` 是第 i 段的文本,**最后一个引用是默认文本**(不使用)。
`cc_val_count` 必须是偶数(奇数说明表残缺,整表丢弃);`下限 > 上限` 时交换。
解析结果进 `SignalEnumText::rangeMin/rangeMax/rangeTexts`,
**只在光标取值标注**里追加 ` = <文本>`(与 `cc_type = 7` 同一出口,常规状态零变化)。
169. ⚠️⚠️ **稠密包络带的线宽必须「纵横双向」加粗**(用户 2026-10-20 报告:线宽 1px→10px 时
方波的**竖直沿**宽度完全不变):包络带每列只有 1 个 x,`top/bottom` 各外扩只能让**水平段**变粗,
竖直沿的横向宽度永远是「1 个像素列」。`EmitEnvelopeBand` 必须再做**横向窗口膨胀**
(`top` 取窗口内最小、`bottom` 取窗口内最大,窗口半径 = `ceil(halfWidth / 列距)`)——
等价于用边长 `2*halfWidth` 的方块做闵可夫斯基膨胀,竖直沿因此变成 `2*halfWidth + 列宽` 宽。
⚠️ 膨胀**只在同一段内**(`segmentColumns` 按段清空),跨 NaN 断点不得连线。
⚠️ 线宽本来就在 `PolylineCacheEntry` 的键里(2026-10-16 修过),所以这里不是缓存问题。
⚠️ 像素级验证时**不能用「隐藏信号的参考帧」做差**:隐掉唯一信号会改变网格幅值范围,
网格线与 Y 轴刻度跟着变,差分会把网格也算成波形(实测虚高到 57px)。直接匹配信号色。
170. ℹ️ **数值显示精度是 6 位有效数字**(`FormatValue` = `%.6g`),**不是**「小数点后 6 位」:
`123.456789 → 123.457`,`123.456789 ± 5e-7` 的两个值格式化后完全相同(波动在第 9 位有效数字上),
且指数 < -4 或 ≥ 6 时切成科学计数法(`0.0000123456 → 1.23456e-05`)。
想看到微小波动必须改显示格式(本轮未改,属显示策略而非数据丢失)。
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
@@ -6822,3 +6857,84 @@ DG3 data=0 cycle=0 (空组,一个字符串通道)
- `make release` / `make debug` **零警告**;`TargetCheck.exe` **804,352 字节**(785.5 KiB,仍在 800KiB 以内)。
- ⚠️ 复测探针时**不要覆盖正在运行的 `Target.exe`**:统一用 `make release TARGET_FILE_NAME=TargetCheck`。
---
## 本轮(2026-10-20 第五轮,本机):`##CA` 全字段 + 3 维命名 + `cc_type = 8` 区间枚举 + 包络带线宽双向加粗
> 用户要求:① 处理前面报告里的「护栏改成按单元格数」「读 `ca_type`」以及「真正的待办」三条
> (`ca_type` / `ca_cycle_count` / `ca_byte_offset_base`)和「ndim 支持三维」;② 若范围枚举文本映射
> (`cc_type = 8`)没实现就补上,且同样只在光标模式下显示;③ 修「改变线宽但图像上纵向的绘制线宽不变」;
> ④ 先回答「6 位有效数字」到底指什么;⑤ 完成后列举 MF4 特性与支持程度并提交。
> 全程离屏(合成文件 + 数据层断言 + 离屏像素探针),不占用用户的鼠标 / 键盘 / 屏幕。
### A. `##CA`:从「按魔数猜」改成「按记录容量校验」
- **三个待办字段的结论**(都用 `.preflight/mf4catype.ps1` 扫过 4 个真实文件):
- `ca_type`:**实测 0 / 1 / 2 都存在**,但三者**物理布局完全相同**(元素在记录内按 `cn_bit_count`
连续排布,`cg_data_bytes` 恰好等于 `头 + 元素数 × 位宽`)⇒ 语义不同、存储同构,
因此 0/1/2 **都展开**,`> 2`(未知)才不展开。
- `ca_cycle_count`:**实测不存在**(`##CA` 块长度到 `dim_size[]` 就结束)。盲读会拿到
下一个块头当数字(实测 1481909027)⇒ 必须先按块长度判断「读得到」才读。
真读到时只要任一维 `> 1`(元素被拆到多条记录),就**不展开**。
- `ca_byte_offset_base`:实测值 2(= uint16 的字节宽度,巧合),**不用于定位**;
只记录进 `ChannelSpec` 供诊断(定位仍用 `cn_byte_offset`,已被真实数据验证)。
- **展开条件重写**(`ExpandArrayChannel(spec, cycles, recordBytes, out)`):
`ca_storage == 0`、`ca_type ≤ 2`、`bit_count ∈ [1,64]`、`cycle_count 每维 ≤ 1`、
**`bit_offset + 元素数 × 位宽 ≤ recordBytes × 8`**(真正的完整性校验)、
元素数 ≤ 16384、`元素数 × 记录数 ≤ 32M`。
旧实现的 `count > 1024` 魔数与「`bitCount == 0` 就放弃」被上面这些**有依据**的判据取代。
- **3 维命名**:按 `dim_size[]` 从末维向前取余,1/2/3 维分别得到 `[i]` / `[i][j]` / `[i][j][k]`。
- 真实文件回归:`MF4-Record.MF4` 仍是 **283** 个信号(25 个 `##CA` 全部通过新的容量校验,
最大 7×9=63),其余三个文件 8 / 18 / 24 不变。
### B. `cc_type = 8`(值区间 → 文本):补齐
- `SignalEnumText` 新增 `rangeMin / rangeMax / rangeTexts`,并拆出 `HasExact()` / `HasRanges()`:
`Empty()` = 两者都无效;`Find(value)` **先精确表、后区间表**(闭区间);`Count()` 供诊断。
- `ParseChannel` 新增 `VALUE_RANGE_TO_TEXT` 分支:`cc_val[]` 交错 `(下限, 上限)`,
`cc_ref[4 + i]` 是第 i 段文本(**最后一个引用是默认文本,不使用**);
`cc_val_count` 必须为偶数(奇数 = 表残缺 ⇒ 整表丢弃);`下限 > 上限` 时交换;非有限值跳过。
- 界面**没有改动**:`cc_type = 7` 与 `8` 共用同一个出口(光标取值标注追加 ` = <文本>`),
因此「只在光标模式下显示」这条要求自动成立,常规状态零变化。
### C. 线宽:包络带必须纵横双向加粗(用户报告的缺陷)
- **根因(代码级确认)**:`bandColumn.top = yTop - strokeHalf; bottom = yBottom + strokeHalf;`
只在**纵向**外扩。包络带每列只有 1 个 x 坐标,填充多边形的横向跨度就是「1 个像素列」⇒
**方波的竖直沿(上升/下降段)横向宽度恒为 1px**,与线宽无关。用户看到的现象正是
「水平段变粗了,竖直方向的沿一点没变」。
⚠️ 不是缓存问题:`lineWidth` 早在 2026-10-16 就进了 `PolylineCacheEntry` 的键。
- **修法**:`EmitEnvelopeBand(..., float halfWidth)`:
1. **横向**:对上下包络做**窗口膨胀**(`top` 取窗口最小、`bottom` 取窗口最大,
半径 `ceil(halfWidth / 列距)`)——等价于用边长 `2*halfWidth` 的方块做闵可夫斯基膨胀,
竖直沿因此变成 `2*halfWidth + 列宽` 宽;
2. **纵向**:再各外扩 `halfWidth`(水平段厚度);
3. 膨胀**只在同一段内**(`segmentColumns` 按段清空),不会跨 NaN 断点连线。
调用方不再自己给列加 `strokeHalf`(列里存**精确**包络)。
### D. 关于「6 位有效数字」
- `FormatValue`(`Utilities/Src/Utf8Util.cpp`)用 `%.6g`:是**总共 6 位有效数字**,
**不是**小数点后 6 位。`123.456789 → 123.457`;`123.456789 ± 5e-7` 的两个值格式化后**完全相同**
(波动在第 9 位有效数字上);`%.6g` 在指数 < -4 或 ≥ 6 时切成科学计数法。
- 这只是**显示格式**,数据本身是完整 double(绘图、边沿统计、派生信号都用原值)。
若要观察微小波动需要改显示策略(本轮按用户要求**未改**)。
### E. 验证
- `.preflight/convcheck`(合成 MDF4,recordBytes 72 → 128):**18/18**
- 新增:`##CA` 3 维 → 8 个 `caCube[i][j][k]` 且 `[1][1][1]` 取值 8/18;
故意放到 `byteOffset = 124` 的 4 元素数组(需要 1056 位 > 1024 位)**必须不展开**;
`cc_type = 8` 两段区间 → `5.0 → LOW`、`15.0 → MID`、`25.0 → 无文本`。
- 新增 `.preflight/round5check`(离屏像素探针):合成 20 万采样「平坦 + 短脉冲」数字信号
⇒ 必然走稠密包络带;在网格中间行统计**接近信号色**的像素数(= 竖直沿横向宽度):
**1px → 6px、10px → 30px**,**5/5 通过**。
- ⚠️ 像素统计**不能用「隐藏信号的参考帧」做差**:隐掉唯一信号会改变网格幅值范围,
网格线与 Y 轴刻度跟着变,差分会把网格也算成波形(实测虚高到 57px)。
- **A/B**:临时令膨胀半径 `radius = 0`(只保留纵向外扩 = 旧行为)⇒ `1px → 2px、10px → 2px`
且「thickens the vertical stroke」如实 FAIL;恢复后 5/5。
- 回归:`check` 137/137、`editcheck` 20/20、`round3check` 31/31、`round4check` 34/34;
真实 MF4 信号数 283 / 8 / 18 / 24;枚举表仍解析出 32 / 12 / 5 / 32 项。
- `make release` / `make debug` 零警告;`TargetCheck.exe` **808,448 字节**(789.5 KiB,仍 < 800KB)。