refactor(panels): 平行数组合并为 std::vector<PanelRow>(消除错位与字段重载)

对应 Code_Review_Report §15.6 第 1 项(方案 B:有结构,无列视图)。

结构
- EdgePanelLayout 的 16 个平行 std::vector 合并为 std::vector<PanelRow> rows,
  PanelRow 带 17 个具名成员:ref / header / nameRect / nameClose / triangle / check /
  stats / levelTrack / levelValue / levelReset / iterTrack / iterValue / iterReset /
  infoMaxInterval / infoMinInterval / intervalField / frequencyField。
- 唯一新增入口:AppendRow(ref, header, nameRect, triangle, check)(返回新行引用)与
  AppendMarkerGroupRow(ref, nameRect, nameClose)(「自由标记」分组行)。
  参数行/重采样行一律 layout.rows.back().xxx = … 赋值。
- 原 int rows(可见信号数)与新容器同名 ⇒ 改名 signalCount,并注明它与 rows.size()
  在 MARK 面板里不相等(只有有固定点的信号才有行)。

消掉的两类问题
1. 某分支漏 push 一列 ⇒ 该列之后所有行读到别人的矩形(静默错位)。现在一行一次成型。
2. rowRefs 字段重载(列表式 = 行信号 / MARK = 分组信号,其余 12 列必须为空)。
   现在每行自带 ref,语义由“填哪些字段”表达;不变式简化为语义断言
   (每行必须有名称区;MARK 行不得带 header/check/stats/滑块/重采样字段)。

顺带清理
- 合并后 rowIndex < layout.rowXxx.size() 全部变成恒真,批量删除 50+ 处
  (含 15 元组长度数组、? x : gd::RectF() 三元判空、if(...){extendRow(...)} 包裹)。
- 调试框与缓存键改为逐行遍历(新增字段只需在 PanelRow 与这两处循环各加一行)。

等价性证明(rowgeom 键名刻意保持不变,故可逐键比对)
  old keys = 919, new keys = 965
  仅存在于旧 = 0;已有键值变化 = 20(全部是 ".count":0 -> N)
  仅存在于新 = 46(全部是新增列的 [NNN],值 0.00,0.00,0.00,0.00)
⇒ 逐行逐字段的真实几何零位移;7 个面板像素哈希(含 resample=2BFC74B3D9B3239C)逐项不变。

验证
- make release:零 warning;784,896 → 782,336 B(−2,560);make debug / make -C Tools 同样零 warning
- UnitTests:checks=260 failed=0 cases=19
- OffscreenChecks -all:7 检查 / 133 断言(上轮 127)全 PASS
- panels -mode compare:7 面板像素哈希逐项不变
- rowgeom -mode compare:965+7 键 0 差异;-data MF4-Record.MF4 时 1268+7 键 0 差异
- 读点:旧 243 处 layout.rowXxx[…] → 现 95 处 layout.rows[…](84 命中判定 / 11 绘制)

过程记录:清理恒真判空时曾用整段替换把「均值滤波」双滑块段误换成「弱峰」双滑块,
编译器报 brace 不平衡,用备份回滚后重做 —— 整段替换必须先精确读出上下文。

文档:Plan §十一、Progress 第 39 轮、Report §16、agent_mem 第 39 轮、Tools/README §4.1/§4.4
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-04 16:18:56 +08:00
1 parent f07ac74988
commit 1463f9d3f8
11 files changed
+672 -591

No files matched your search

+37
View File
@@ -343,3 +343,40 @@
| 通用参数行(中心轴统计 + 两行滑块) | ⏸ 未提取:依赖 3 个 kind 标志 + 2 套拖拽/编辑状态 + 2 个滑块 lambda,提取需传 9 个参数,可读性收益低于参数表本身 |
| 条目栏(三角 + 名称 + 复选框 + 悬停) | 保留在 `PaintPanelListRows` 里(它是三种排版的公共前缀) |
---
## 十一、第 39 轮追加(2026-11-04):平行数组合并为 `std::vector<PanelRow>`(§15.6 第 1 项)
> 触发:用户要求「继续按照上轮的建议继续推进」。上一轮结论是"若要做就做**方案 B**:
> `std::vector<PanelRow>` + 具名成员访问(**无视图**),并且必须单独一轮"。
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **P33** | `EdgePanelLayout` 的 16 个平行 `std::vector` 合并为 `std::vector<PanelRow> rows` | `rowgeom -mode compare`:**0** 条键消失、**0** 条已有键的值变化;新增键**全部**是空矩形 |
| **P34** | 唯一入口收敛为 `AppendRow` / `AppendMarkerGroupRow`;`int rows` 改名 `signalCount` | 编译零 warning;7 个面板像素哈希**逐项不变** |
| **P35** | 删掉因合并而变成恒真的 50+ 处 `rowIndex < layout.rows.size()` 判空 | 全量检查 133 断言全绿 |
### 11.1 本轮新增硬约束
1. **行的几何**只能通过 `rows[i].<字段>` 访问;**禁止**再加回平行数组,也**禁止**引入
`PanelColumn<T>` 列视图(拷贝 `EdgePanelLayout` 后忘重绑 = 静默悬垂,没有检查可以抓到)。
2. **新增一个行字段** = `PanelRow` 加一个成员 + `AppendRow` 里赋默认值(现在连这一步都不用,
成员自带默认构造)+ 需要绘制的地方读 `rows[i].字段`。**不需要**再同步任何"列清单"。
3. **`signalCount` ≠ `rows.size()`**:前者是"网格里的可见信号数"(`EdgePanelLayoutFor` 开头统计),
后者是"本面板实际产生的行数"。两者在「自由标记」面板里**不相等**(只有有固定点的信号才有行)。
4. **`rowgeom` 的键名保持 `<panel>.<旧列名>[NNN]`**:这样重构前后的基线可以逐字节比对,
等价性不靠人工核对。
### 11.2 等价性是怎么证明的(可复用的流程)
```text
old keys = 919, new keys = 965
仅存在于旧 = 0 ← 没有任何键消失
已有键的值变化 = 20 ← 全部是 ".count" 键:0 -> N(结构变化,不是几何变化)
仅存在于新 = 46 ← 全部是 <column>[NNN],且值全为 0.00,0.00,0.00,0.00
```
含义:旧结构里"某列根本没被 push"的那些行,现在每行都拥有该字段(值为空矩形)。
**逐行逐字段的真实几何一位都没变**,7 个面板的像素哈希也逐项未变。