refactor(panels): 几何算式收敛为单一来源 + 参数行提取(§17.6 第 1/2 项)

第 1 项:宽度算式与行高累加各自合并成一份来源
- 新增 `PanelColumnMetrics` + `MeasurePanelColumns(lane, kind)`:全仓唯一的一次"逐信号量宽"。
  * `EdgePanelWidth(laneIndex, kind)` 退化为"量一次 + 算一次"(`EdgePanelWidthFor`);
  * `EdgePanelLayoutFor` 量一次后,宽度与列几何(表格列 / 滑块轨道)都读同一份度量。
  * 改前两者各写一份同名同形的 UiMeasureTextWidth 链,注释只写着"同一组算式 ⇒ 不会漂移",
    那是靠人守的约定,改一处漏一处就是"面板变宽而列不动"(文字压到轨道上)。
- 新增 `PanelRowPlan` + `PlanListPanelRows(lane, kind)`:行高累加与行几何的唯一来源。
  * 改前 `EdgePanelLayoutFor` 对同一批信号遍历两趟(先算总高、再出行几何),
    两处判定(可见/勾选/收起/统计行/参数行数)必须逐字一致 —— 这类"两趟必须同步修改"
    正是"改一次布局就出错"的另一半机制。
  * 现在一个 {ref, checked, collapsed, hasStats, sliderRows} 数组同时喂高度算式与几何生成。
- 合并前先做等价性判别:`valueColumn` 两处算法不同但结果相同(100000 最宽)⇒ 可合并;
  `sliderLabel` 与 `resampleLabel` 不可合并(重采样标签更长,并进会让均值/弱峰变宽)。

第 2 项:`PaintPanelListRows` 的通用参数行提取
- 新增 `PaintPanelParamRows(g, layout, row, kind, PanelParamState{…})`:中心轴统计行 +
  两条滑块分支(均值/弱峰),`paintCounts`/`paintMeanSlider`/`paintPeakSlider` 三个 lambda
  与 `valueColumn` 随之迁入。
- 逐行状态打包进 `PanelParamState`(解析结果 / 两种条目指针 / 网格下标)⇒ 参数表 5 个,
  而不是 9 个散参数(签名比函数体更难读)。
- `PaintPanelListRows` 由约 300 行降到约 120 行,只负责"条目栏 + 按 kind 分派"。
- 顺手把机械搬移遗留的"整个函数体多缩进 2 个 tab"修正。

过程记录(两次锚点误伤,已写入文档避免重犯)
- 锚点 `// 两行滑块` 在 `EdgePanelWidthFor` 内也匹配 ⇒ 误删宽度函数尾部;
- 锚点 `float rowY = ...` 在表格分支先出现 ⇒ 误删「边沿计数」的行发射。
两次都是编译器立刻报错,从 Temp 备份回滚后重做。规则:整段替换的锚点必须唯一,
同名语句多次出现时先定位所属区块再取偏移。

第 3 项(体积)按用户指示暂停投入。

验证(双基线均为"零差异")
- make release / make debug / make -C Tools:零 warning;产物 782,336 → 781,824 B(−512)
- UnitTests:checks=260 failed=0 cases=19
- OffscreenChecks -all:7 检查 / 133 断言全 PASS
- panels -mode compare:7 面板像素哈希逐项不变(含 resample=2BFC74B3D9B3239C)
- rowgeom -mode compare:965+7 键 0 差异;-data MF4-Record.MF4 时 1268+7 键 0 差异

文档:Plan §十二、Progress 第 40 轮、Report §17、agent_mem 第 40 轮
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-04 16:36:47 +08:00
1 parent 1463f9d3f8
commit a9f1563896
7 files changed
+1023 -599

No files matched your search

+29
View File
@@ -379,4 +379,33 @@ old keys = 919, new keys = 965
含义:旧结构里"某列根本没被 push"的那些行,现在每行都拥有该字段(值为空矩形)。
**逐行逐字段的真实几何一位都没变**,7 个面板的像素哈希也逐项未变。
---
## 十二、第 40 轮追加(2026-11-04):几何算式重写 + 参数行提取(§16.7 第 1 / 第 2 项)
> 触发:用户要求「继续上一轮报告中 §16.7 的 1 和 2 建议」,并明确第 3 点(体积)暂不投入,
> 等以后功能更新产生新的冗余时再优化。
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **P36** | 宽度算式与列几何共用一份 `PanelColumnMetrics`(`MeasurePanelColumns` + `EdgePanelWidthFor`) | `panels` 7 个面板像素哈希 + 矩形**逐项不变** |
| **P37** | 行高累加与行几何共用一份 `PanelRowPlan`(`PlanListPanelRows`):两趟遍历合一 | `rowgeom` 965+7 键 0 差异;`panelscroll` 全绿 |
| **P38** | `PaintPanelListRows` 的通用参数行提取为 `PaintPanelParamRows`(5 参数) | 双基线归零;`PaintPanelListRows` 由约 300 行降到约 120 行 |
### 12.1 本轮新增硬约束
1. **宽度算式与列几何只能有一份来源**:任何"这个面板的标签列 / 数值列有多宽"都从
`PanelColumnMetrics` 取。**禁止**在 `EdgePanelLayoutFor` 里再写 `UiMeasureTextWidth` 链
(旧代码注释写着"与 `EdgePanelWidth` 同一组算式 ⇒ 不会漂移"——那是靠人守的约定,
改一处漏一处就是"面板变宽而列不动",表现为文字压到轨道上)。
2. **行高与行几何只能有一份来源**:都读 `PlanListPanelRows` 的返回值(`PanelRowPlan`)。
**禁止**再出现"第一趟只算高度、第二趟才算几何"的双遍历 —— 两趟的判定条件必须逐字一致,
任何一处写错就表现为底部留白 / 最后一行被裁 / 没内容却能滚动。
3. **`PanelRowPlan::sliderRows`** 用整数表达"展开几个参数行"(0/2/4),
`hasStats` 表达「中心轴」多一行统计 —— 这两个量是"高度"与"几何"共享的**唯一判定**。
4. **参数行绘制**走 `PaintPanelParamRows(g, layout, row, kind, PanelParamState{…})`;
逐行状态打包进 `PanelParamState`(解析结果 / 两种条目指针 / 网格下标),
目的是把参数表压在 5 个 —— 一个个透传会让签名比函数体更难读。
+85
View File
@@ -628,4 +628,89 @@ old keys = 919, new keys = 965
| 读点数 | 旧的 243 处 `layout.rowXxx[…]` 全部消失;现在 95 处 `layout.rows[…]`(84 在命中判定、11 在绘制) |
| 行数 | `PlotView.hpp` 4795;`PlotViewInteraction.cpp` 4399;`PlotViewPanelPaint.cpp` 935;`PlotViewPaint.cpp` 5534;`PlotViewPanels.cpp` 1729 |
---
## 第 40 轮(2026-11-04):几何算式重写 + 参数行提取(§16.7 第 1 / 第 2 项)
> 上一轮已完成"结构合并 + 双基线",本轮把**算式**本身收敛成单一来源。
### P36 宽度算式与列几何共用一份度量
改前:`EdgePanelWidth`(决定面板宽)与 `EdgePanelLayoutFor`(决定列边界)**各自写了一份**
同名同形的 `UiMeasureTextWidth` 链(标签列 / 数值列 / 重采样列),注释写着
"与 `EdgePanelWidth` 同一组算式 ⇒ 不会漂移"。那是靠人守的约定。
改后:
```cpp
struct PanelColumnMetrics // 唯一的列宽度量
{
std::size_t signalCount; // 可见信号数
float nameColumn; // 名称列(已按上限夹取)
float statRising, statFalling; // 「边沿计数」/「中心轴」统计列
float sliderLabel, resampleLabel;// 参数行标签列(两种,互不合并)
float valueColumn; // 数值列(同时是绘制侧的裁剪宽度)
float resampleValueColumn; // 「重采样」只读统计的数值列
};
PanelColumnMetrics MeasurePanelColumns(const Lane&, EdgePanelKind) const;
float EdgePanelWidthFor(const Lane&, EdgePanelKind, const PanelColumnMetrics&) const;
```
`EdgePanelWidth(laneIndex, kind)` 退化为"量一次 + 算一次";`EdgePanelLayoutFor` 量一次后
**同时**用于宽度与列几何。
⚠️ 顺手验明一处"看起来重复其实必须分开"的量:`valueColumn` 在宽度算式里按
`max("100000", "-400%")` 量、在列几何里按 `max("100000", "1000")` 量 —— 两者结果相同
(`100000` 是最宽的),因此可以合并;而 **`sliderLabel` 与 `resampleLabel` 不能合并**
(「重采样」标签更长,并进来会让均值/弱峰面板整体变宽 = 像素变化)。
### P37 行高累加与行几何共用一份行计划
改前:`EdgePanelLayoutFor` 里**两趟独立遍历**同一批信号 ——
第一趟只累加 `meanContentHeight`,第二趟才生成 `rows` 并累加 `rowY`。
两趟的判定(可见性 / 勾选 / 收起 / 是否要统计行 / 参数行数)必须逐字一致。
改后:
```cpp
struct PanelRowPlan { SignalRef ref; bool checked, collapsed, hasStats; int sliderRows; };
std::vector<PanelRowPlan> PlanListPanelRows(const Lane&, EdgePanelKind) const;
```
高度 = `groupRow + (hasStats ? groupRow : 0) + sliderRows * sliderRow`;
几何按同一个 `plan.sliderRows / hasStats` 派发 ⇒ **结构上不可能不一致**。
「选区截取」未启用双光标时整份计划作废(面板只显示一行提示),与改前行为一致。
### P38 参数行绘制提取
`PaintPanelListRows` 里的「中心轴统计行 + 两条滑块分支(均值 / 弱峰)」
**整块提取**为 `PaintPanelParamRows(g, layout, row, kind, PanelParamState{…})`,
`paintCounts` / `paintMeanSlider` / `paintPeakSlider` 三个 lambda 与 `valueColumn` 随之迁入。
`PaintPanelListRows` 现在只负责"条目栏(叠底 + 悬停 + 三角 + 名称 + 复选框)" +
"按 `kind` 分派到重采样/参数行"。
### 过程中的两次返工(记录,避免重犯)
1. 用"整段替换"改宽度块时,锚点 `// 两行滑块` 在 `EdgePanelWidthFor` 里**也有一处**
⇒ 误删了宽度函数尾部。改用更长的锚点(带"的列宽")后重做。
2. 行发射循环的锚点 `float rowY = layout.content.Y - scrollMetrics.scroll;`
在**表格分支**里先出现一次 ⇒ 误删了「边沿计数」的行发射。改为"先定位**第二个**
`else if (listStyle)`,再从那里找 `rowY`"。
⇒ **教训:整段替换的锚点必须唯一;同一文件里同名语句出现多次时,先定位所属区块再取偏移。**
### 本轮验证
| 项 | 结果 |
| --- | --- |
| `make release` / `make debug` / `make -C Tools` | 零 error / 零 warning |
| 产物 | 782,336 → **781,824 B**(−512) |
| `UnitTests` | `checks=260 failed=0 cases=19` |
| `OffscreenChecks -all` | 7/7 PASS,`checks=133 failed=0` |
| `panels -mode compare` | 7 个面板像素哈希**逐项不变**(含 `resample=2BFC74B3D9B3239C`) |
| `rowgeom -mode compare` | 965 + 7 键 0 差异;`-data MF4-Record.MF4` 1268 + 7 键 0 差异 |
| 行数 | `PlotViewPanelPaint.cpp` 935 → 939;`PlotViewPanels.cpp` 1729 → 1766(新增两个几何函数,净增 37) |
| 样式 | 顺手把机械搬移遗留的"整个函数体多缩进 2 个 tab"修正 |
+74
View File
@@ -752,4 +752,78 @@ old keys = 919, new keys = 965
参数表从 9 个降到 `(g, layout, row, kind, state)` 5 个。
3. 若还要压体积:`EdgePanelLayout` 已无冗余列,剩余空间主要在字符串/浮点格式化(见 §11 的 gdtoa 结论)。
---
## 十七、第 40 轮:几何算式重写 + 参数行提取(§16.7 第 1 / 第 2 项)
### 17.1 结论
| §16.7 建议 | 结果 |
| --- | --- |
| 1. 面板几何算式重写(`EdgePanelWidth` + 行高累加) | ✅ 两处都收敛成单一来源(`PanelColumnMetrics` / `PanelRowPlan`),双基线零差异 |
| 2. `PaintPanelListRows` 通用参数行提取 | ✅ `PaintPanelParamRows`(5 参数),函数由约 300 行降到约 120 行 |
| 3. 体积 | ⏸ 按用户指示**暂不投入**(当前没有便宜的压缩空间,等后续功能更新产生冗余时再做) |
产物顺带 −512 B(782,336 → **781,824 B**),零 warning。
### 17.2 "重构"与"重写"的区别(本轮的核心方法论)
前几轮做的是**重构**(结构变、行为不变),判据是"双基线零差异"。
本轮做的是**算式重写**(把两份同源算式合成一份),风险更高:两份算式**本来可能已经不一致**,
合并后必然有一边要改数 —— 那就不是"行为不变"了。
所以先做了一次**等价性判别**:
| 量 | 宽度算式里的算法 | 列几何里的算法 | 是否等价 |
| --- | --- | --- | --- |
| `valueColumn` | `max(mono"100000", mono"-400%")` | `max(mono"100000", mono"1000")` | ✅ 都等于 `mono"100000"` |
| `labelColumn` | `max(均值标签, 弱峰标签)` | `max(均值标签, 弱峰标签)` | ✅ 同式 |
| `resampleLabel` | 单独算(不并入上面) | 单独算 | ✅ 同式,且**不能**并入 |
| `nameColumn` | 逐信号取 max(跳过无 axis 的) | 同上(另一处循环) | ✅ 同式 |
⇒ 只有"可以证明等价"的量才合并。`resampleLabel` 明确**不**并入 `sliderLabel`:
「重采样」标签("最大数据间隔")比均值/弱峰的长,并进去会让后两个面板整体变宽 —— 那是像素变化,
不属于等价重构。
### 17.3 双遍历合一:`PanelRowPlan`
改前 `EdgePanelLayoutFor` 里对同一批信号遍历两次(先算总高、再出行几何),
两处的判定条件(可见 / 勾选 / 收起 / 统计行 / 参数行数)必须**逐字一致**。
这种"两趟必须同步修改"的结构就是"改一次布局就出错"的另一半机制(前半是平行数组,已在 §16 解决)。
改后一个 `PanelRowPlan{ref, checked, collapsed, hasStats, sliderRows}` 数组同时喂给
高度算式与几何生成,判定只写一次。
### 17.4 过程中的两次锚点误伤(值得记)
1. 锚点 `// 两行滑块` 在 `EdgePanelWidthFor` 内也匹配到 ⇒ 误删宽度函数尾部;
2. 锚点 `float rowY = layout.content.Y - scrollMetrics.scroll;` 在**表格分支**先出现 ⇒
误删「边沿计数」的行发射。
两次都是编译器立刻报错(brace 不平衡 / 未声明标识符),从备份回滚后重做。
**规则:整段替换的锚点必须唯一;同名语句出现多次时先定位所属区块(例如"第二个
`else if (listStyle)`")再取偏移。**
### 17.5 验证
| 项 | 结果 |
| --- | --- |
| `make release` / `make debug` / `make -C Tools` | 零 warning |
| 产物 | **781,824 B**(−512) |
| `UnitTests` | 260 / 19 PASS |
| `OffscreenChecks -all` | 7 检查 / 133 断言 PASS |
| `panels -mode compare` | 7 面板像素哈希逐项不变 |
| `rowgeom -mode compare` | 965+7 键 / 1268+7 键(MF4),0 差异 |
### 17.6 下一步(按价值排序)
1. **面板"三处穷举 switch"再收敛**:现在新增面板仍要改 `PanelStyleOf` / `PanelTitle` /
`LanePanelScrollOf` 三处;可以考虑把它们合成一张 `constexpr` 表
(`{kind, title, style, scrollPtr}`),新增面板只加一行数据 —— 但 `PanelTitle` 需要
`EdgeRangeSuffix()`(运行时字符串),需先解决"标题是函数"的问题。
2. `PaintPanelMarkerGroups`(约 260 行)按"分组栏 / 表头 / 条目吸顶"再拆,
与 §17.3 同理:先给"吸顶推挤"单独一个可测函数。
3. 体积:等有新的冗余再说(用户已明确)。
+56
View File
@@ -10758,3 +10758,59 @@ brace 不平衡。用备份回滚后重做。
(`g, layout, row, kind, state`)。
3. 体积剩余空间主要在字符串/浮点格式化(gdtoa 30+ KB 不可去掉,见 §11)。
---
## 2026-11-04 · 第 40 轮(`Code_Review` 分支):几何算式重写 + 参数行提取(§17.7 第 1/2 项)
> 规则原文:`Documents/Code_Review_Report.md` §17、`Code_Review_Plan.md` §十二、
> `Code_Review_Progress.md` 第 40 轮。
### 1. ✅ 宽度算式与列几何共用一份 `PanelColumnMetrics`
```cpp
PanelColumnMetrics MeasurePanelColumns(const Lane&, EdgePanelKind) const;
float EdgePanelWidthFor(const Lane&, EdgePanelKind, const PanelColumnMetrics&) const;
```
`EdgePanelWidth(laneIndex, kind)` = 量一次 + 算一次;`EdgePanelLayoutFor` 量一次,
宽度与列几何都读它。
**硬规则**:`EdgePanelLayoutFor` 里**禁止**再写 `UiMeasureTextWidth` 链算列宽。
⚠️ 可合并 / 不可合并的判别(做这类"算式重写"时必须先做):
* `valueColumn`:宽度用 `max("100000","-400%")`、几何用 `max("100000","1000")` → **等价**,可合并;
* `sliderLabel` 与 `resampleLabel`:**不能**合并(重采样标签更长,并进会让均值/弱峰变宽)。
### 2. ✅ 行高累加与行几何共用一份 `PanelRowPlan`
```cpp
struct PanelRowPlan { SignalRef ref; bool checked, collapsed, hasStats; int sliderRows; };
std::vector<PanelRowPlan> PlanListPanelRows(const Lane&, EdgePanelKind) const;
```
高度 = `groupRow + (hasStats?groupRow:0) + sliderRows*sliderRow`;几何按同一份计划派发。
**禁止**再出现"第一趟算高度、第二趟出几何"的双遍历(两趟判定必须逐字一致 = 错误温床)。
### 3. ✅ 参数行绘制提取 `PaintPanelParamRows(g, layout, row, kind, PanelParamState{…})`
`paintCounts` / `paintMeanSlider` / `paintPeakSlider` 与 `valueColumn` 随之迁入。
`PaintPanelListRows` 现在只剩"条目栏 + 按 kind 分派"。
### 4. ⚠️ 整段替换的锚点必须是**唯一**的(本轮两次误伤)
1. `// 两行滑块` 在 `EdgePanelWidthFor` 里也有一份 ⇒ 误删宽度函数尾部;
2. `float rowY = layout.content.Y - scrollMetrics.scroll;` 在**表格分支**先出现
⇒ 误删「边沿计数」的行发射。
对策:先定位所属区块(如"第二个 `else if (listStyle)`")再取偏移;改前 `Copy-Item` 备份到 `Temp\`。
### 5. 本轮数字
* Release 782,336 → **781,824 B**(−512);release/debug/tools 全零 warning。
* `UnitTests` 260/19;`OffscreenChecks -all` 7 检查 / 133 断言全 PASS。
* `panels -mode compare` 7 面板像素哈希逐项不变;`rowgeom compare` 965+7 / 1268+7 键 0 差异。
* `PlotViewPanelPaint.cpp` 935 → 939;`PlotViewPanels.cpp` 1729 → 1766。
### 6. 下一步
1. 三处穷举 switch(`PanelStyleOf` / `PanelTitle` / `LanePanelScrollOf`)合成一张 constexpr 表 ——
但 `PanelTitle` 依赖运行时 `EdgeRangeSuffix()`,需先解决"标题是函数"。
2. `PaintPanelMarkerGroups`(约 260 行)按"分组栏 / 表头 / 条目吸顶"再拆。
3. **体积按用户指示暂停投入**(等后续功能更新产生新冗余时再做)。