Files
Just-DataInspector/Documents/Code_Review_Plan.md
T
DeepSeek V4.1 Flash f07ac74988 refactor(panels): 删除 rowPoints/rowIterations 死列 + 提取 PaintPanelResampleRows
对应 Code_Review_Report §14.5 第 1 项与第 3 项。

第 1 项:删除 EdgePanelLayout::rowPoints / rowIterations(行数组 18 → 16)
- 写入方只有 AppendRow(推空矩形),读取方只有调试框 AddAll 与 PanelSlotKey 的 mix 循环,
  绘制方零(旧式“点数 / 迭代次数”输入框早被滑块版 rowLevelTracks / rowIterTracks 取代)。
- 连带同步:AppendRow、调试框、缓存键、rowgeom 导出、panels/panelscroll 长度不变式、
  文档里的列数(18/14 → 16/12)。

等价性由基线证伪,而不是靠人工 grep:
  old lines = 983, new lines = 919
  only in OLD = 64, only in NEW = 0
  only-in-OLD lines NOT belonging to rowPoints/rowIterations = 0
即除被删两列外,其余 919 条键逐字节相同。

第 3 项(前半):提取 PlotView::PaintPanelResampleRows
- PaintPanelListRows 里 `if (resamplePanel) { …; continue; }` 共 132 行整块提取,
  签名 (g, layout, rowIndex, laneIndex, ref, item);两个局部 lambda
  (paintInfoRow 只读统计行 / paintInputRow 联动输入框)随之迁移。
- maxInterval / minInterval 两个纯拷贝局部量直接用 item.maxInterval / item.minInterval。
- 通用参数行(中心轴统计 + 两行滑块)未提取:需 9 个参数,签名比函数体更难读。

§14.5 第 2 项(完整结构合并)本轮不做,方案已定并写入文档:
- 列视图(PanelColumn<T>)方案否决:拷贝忘重绑 = 静默悬垂,需额外自检兜住;
- 若要做就做“vector<PanelRow> + 具名成员访问(无视图)”,242 处读点(127 处命中判定),
  必须单独一轮。它属整洁性收益(漏推一列已被 AppendRow + 长度不变式挡住),不是紧急项。

验证
- make release:零 warning;785,408 → 784,896 B(−512);make debug 同样零 warning
- make -C Tools:零 warning
- UnitTests:checks=260 failed=0 cases=19
- OffscreenChecks -all:7 检查 / 127 断言全 PASS
- panels -mode compare:7 面板像素哈希 + 几何逐项不变(resample=2BFC74B3D9B3239C)
- rowgeom -mode compare:919 + 7 键,0 差异(基线已按 16 列重录)
- PlotViewPaint.cpp 5575 → 5565;PlotViewPanelPaint.cpp 1016 → 1020

文档:Plan §十、Progress 第 38 轮、Report §15、agent_mem 第 38 轮、Tools/README §4.4
2026-11-04 16:02:15 +08:00

346 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Code Review 计划(分支 `Code_Review`)
> 目标:在不引入性能回归、不改变用户可见行为的前提下,对全项目做一次**结构 + 性能 + 体积**
> 的查漏补缺,并把「改一个功能要动 40 处」的屎山点收敛成表驱动/单一实现。
>
> 基线(本计划开始时):
> - 分支 `Code_Review`,起点 `764e5be`(dev 已删除过时的 `UI_Refactor_*` 三份文档)。
> - `make release` 产物 **890,880 B**(`.text` 753,136 B、`.rdata` 36,936 B、`.rsrc` 20,656 B、
> `.pdata` 30,420 B、`.xdata` 29,496 B、`.idata` 12,856 B)。
> - 源码约 **5.6 万行**;最大单文件 `PlotViewPaint.cpp` 6,561 行、`PlotView.hpp` 4,654 行。
>
> 约束:
> - 每完成一个节点**编译验证(release + debug 零警告)**并**提交**,同时更新
> `Documents/Code_Review_Progress.md`。
> - 性能相关节点必须给出**同一次运行内的相对量**(历史教训:绝对毫秒阈值不可靠)。
> - 不占用用户的鼠标/键盘/屏幕(只用纯控制台探针或源码级核对)。
---
## 一、已确认的问题(本计划的事实基础)
### A. 体积
| 项 | 实测 | 说明 |
| --- | --- | --- |
| `dynamic_cast`(`Toolbar.cpp:370`) | **+75,264 B** | 最小复现程序:39,936 B → 115,200 B。它会链入 RTTI 元数据 + `cp-demangle.o`(map 里 **56,320 B**)。而该处 `Button::widget` **本来就是 `UiButtonWidget*`** ⇒ 转换完全多余。 |
| `-fno-rtti` | 未启用 | 移除上一条后即可开启,进一步去掉 `type_info` 表。 |
| `.pdata` + `.xdata` | 60 KB | `-fno-exceptions` 已开;SEH 展开表仍存在。可尝试 `-fno-asynchronous-unwind-tables`(历史上链接失败,需复核)。 |
| `libmingwex` 的 `strtodg/gdtoa/mingw_wpformat` | 27.8 KB | 来自 `wcstod` / `wprintf` 类调用;可评估用自写解析替代 `wcstod` 的收益(公式/输入框解析)。 |
| `.rsrc` | 20.7 KB | 图标(PNG 帧)+ manifest + 版本信息,已优化过。 |
| 重复常量/重复实现 | 多处 | `kFormulaEnvelopeBuckets` 已清理;仍有 `ClampDouble` 两份实现等。 |
### B. 性能(结构性热点)
1. **面板开关/参数处理是 6 个 `EdgePanelKind` × ~20–26 处 switch 分支**(实测每个 kind 出现
21–26 次,全部由手写 `if/switch` 维护)。这是**新增功能必然漏改 2–3 处**的根本原因
(历史已发生 4 次:`MARK` 漏拖动、`RESAMPLE` 漏拖动+关闭、`CLIP` 漏滚轮+缓存键)。
2. **`SceneContentHash` 里 6 段几乎相同的逐面板混入循环**(`PlotViewCacheKey.cpp`)。
3. **`EdgePanelLayoutFor` 是巨型分支**,`EdgePanelLayout` 结构体带 20+ 个按面板类型区分的
矩形数组;高度算式与几何算式分开写(历史上多减一个 pad 造成空白空白 BUG)。
4. **`PlotView` 有 30+ 个缓存**,每个都手写「键 + DC + 位图 + 贴图 + 失效」五件套;
失效路径散落在 8 个文件里,`m_sceneValid=false` 出现 6 处。
5. `ApplicationCommands.cpp` **2,165 行**混杂:标签页、数据派生、菜单、工具栏、键盘、鼠标。
6. `PlotView.hpp` 4,654 行(声明 + 内联实现),单文件认知负担极大。
### C. 过时/错误注释与命名(抽样已确认)
- `agent_mem.md` §3「目录结构」仍写着已不存在的 `Utilities/CsvTable/`、`Utilities/DataModel/`
等目录;§2 写 `-Os` 而实际是 `-Oz`。
- 源码中留有历史轮次的「本轮」「上一轮」叙述(对当前读者无意义),以及被推翻方案的注释。
- `PlotViewInternal.hpp` 里 `kFormulaEnvelope*` 注释已随实现迁移,需同步。
---
## 二、节点计划
> 优先级:**S**(安全、收益明确、风险低)→ **A**(结构调整,中风险,需逐步验证)→
> **B**(深度重构,必须有可验证的前后一致性)。
### 阶段 0:建立基线(节点 P0)
- P0.1 记录基线:release/debug 体积、`.text/.rdata` 等节表、关键路径的可复跑测量程序
(纯控制台,链接 `Build/Release/*.o`,不入库)。**已完成基线的部分见下表**。
- P0.2 建立 `Documents/Code_Review_Progress.md`,每节点完成后追加一行结论 + 数字。
### 阶段 1:体积(节点 P1–P5,安全优先)
- **P1 移除 `dynamic_cast` + 开启 `-fno-rtti`**
- 把 `Toolbar::BuildBoxes` 的 `dynamic_cast<const UiButtonWidget*>` 改为直接用
`Button::widget`(已是该类型)或给 `UiWidget` 加虚函数 `ContentBounds()`。
- Makefile `CXXFLAGS += -fno-rtti`;全仓确认无 `typeid`/`dynamic_cast`/`type_info`。
- 预期 **−70~80 KB**。验收:产物下降 ≥60 KB;功能逐项核对(工具栏按钮、布局边界调试)。
- **P2 体积审计第二轮**
- 复核 `-fno-asynchronous-unwind-tables`(历史上链接失败,试 `-fno-unwind-tables` 组合)。
- 评估 `wcstod` 替身(自写定点/浮点解析,仅在公式与输入框使用)去掉
`strtodg/gdtoa/gethex/hexnan`(约 20 KB)。
- 评估 `--gc-sections` 在 LTO 下是否已生效(读 map 确认无死节)。
- 验收:逐项给出「测得的字节数 + 是否采纳」的结论,**不接受「感觉变小」**。
- **P3 重复实现归并**
- `ClampDouble` 两份(`PlotViewInternal.hpp` / `UiLayout.cpp`)→ 统一到 `Shared`。
- 扫描全部 `static`/匿名命名空间重复工具函数,合并为单一实现。
- 重复字符串/常量字面量、重复的三元式开关表。
- **P4 死代码与未使用成员清理**
- 用非 LTO 对象 + `nm` 反查「定义但从未被引用」的函数;删除确认无引用的实现。
- 清理 `PlotView.hpp` 中已无人读取的成员/字段。
- **P5 资源与启动数据**
- `.rsrc` 复核(图标帧数、manifest、版本字符串);`.idata` 导入表复核。
### 阶段 2:性能(节点 P6–P10)
- **P6 建立可复跑的渲染性能探针(纯控制台)**
- 目标:能在**不弹窗口**的情况下测量 `BuildLaneVisuals` / `SceneContentHash` /
`EdgePanelLayoutFor` / 面板绘制 / 信号面板绘制的耗时。
- 方式:链接 `Build/Release/*.o` + 手工构造 `DataSet`/`TabConfig`,只调用纯函数与
`PlotView` 的非窗口方法(避免 `Application::Initialize` 触发模态框,见历史教训)。
- 所有断言用**同一进程内的相对量**。
- **P7 面板元数据表驱动(核心)**
- 新增 `EdgePanelSpec`(每 kind 一行:命令 id、默认尺寸、是否列表型、footer 类型、
滚动字段偏移、面板状态字段偏移、绘制函数指针、命中函数指针)。
- 把 6×~20 处 `switch (kind)` 收敛为「查表 + 3 个通用实现(列表型 / 表格型 / 固定行型)」。
- 验收:**逐像素**对比改造前后面板(离屏渲染哈希);新增一个假 kind 只需加一行表项即可
被绘制/命中/拖动/关闭/滚动全覆盖(用编译期断言或探针验证)。
- **P8 `SceneContentHash` 混入收敛**
- 把 6 段重复循环改为「按面板表遍历 + 每面板一个 `MixContent()`」。
- 验收:改造前后哈希值**逐位相同**(同一状态)。
- **P9 `EdgePanelLayoutFor` 表驱动 + 高度/几何同源**
- 高度算式与内容几何共用同一份 `PanelMetrics` 计算(消除重复减 pad 类 BUG)。
- 验收:探针打印「内容区下边界 vs 按钮上边界」的像素差,与改造前一致。
- **P10 缓存五件套抽象(低风险版)**
- 抽出 `OffscreenCache`(DC/位图/键/贴图/失效),**只替换三处结构完全相同的**
(derived / decor / panel),保留 scene 与 lane layer 不动(热路径最复杂)。
- 验收:逐像素一致 + 帧时间不回退。
### 阶段 3:架构(节点 P11–P14)
- **P11 `ApplicationCommands.cpp` 拆分**
- → `ApplicationTabs.cpp`(标签页)/ `ApplicationDerive.cpp`(派生/重采样/截取/克隆)/
`ApplicationCommands.cpp`(命令分发)/ `ApplicationMenus.cpp`(上下文菜单与设置菜单)。
- 纯搬移,零行为变更;Makefile 无需改动(按目录通配收集)。
- **P12 `PlotView.hpp` 拆分**
- 把 `EdgePanelLayout`、派生曲线缓存条目、图层结构体等**纯数据结构**搬到
`PlotViewTypes.hpp`;把 `PlotView` 的内联小函数按关注点分块放到 `PlotViewInline.hpp`。
- 目标:`PlotView.hpp` ≤ 2,500 行。
- **P13 UI 基础层职责核对**
- 核对 `UiWidget` / `UiComponent` / `UiLayout` / `UiBox` / `UiSurface` / `UiButtonWidget` /
`UiControls` 的边界是否重叠;合并确认冗余的(只保留一套矩形/命中/布局原语)。
- 明确依赖方向:`Components/*` → `Components/Shared` → `Utilities`(校验无反向)。
- **P14 绘制/功能组件边界**
- `PlotViewPaint` / `Raster` / `Panels` / `Features` / `Interaction` 的职责再核对;
把「面板几何」与「面板绘制」的交叉依赖理顺(几何 → 共享 `PanelMetrics`)。
- 把散落在 `PlotView` 里的「颜色/线宽/胶囊样式」常量收敛到 `Theme`/`PaintUtil`。
### 阶段 4:注释与命名清理(节点 P15)
- **P15 注释/命名审计**
- 删除「本轮/上一轮」叙述、被推翻方案的描述、与实现不符的注释。
- 统一命名:`*Dc`/`*DC`、`Lane*` vs `Grid*`、`Edge*` vs `Panel*` 等不一致处。
- 确保所有注释与当前实现一致(重点:`PlotViewInternal.hpp`、`PlotViewCacheKey.cpp`)。
- 同步更新 `agent_mem.md` 的 §2/§3(已经不准确的目录结构与 `-Os`)。
### 阶段 5:验证与报告(节点 P16)
- **P16 最终验证与报告**
- release + debug 零警告;产物体积对比;关键路径探针全部通过。
- 生成 `Documents/Code_Review_Report.md`:逐节点写「改了什么 / 为什么 / 效果 /
未做的事与原因」,并给出体积与性能的前后对照表。
---
## 三、验收标准(硬性)
1. `make release` 与 `make debug` **零 error、零 warning**。
2. 产物体积**必须下降**(目标:从 890,880 B 降到 ≤ 780 KB;若某项无法达成,在报告里
说明实测字节数与原因)。
3. 不改变任何用户可见行为(面板几何、绘制像素、交互语义一致)。
4. 每条性能结论都必须有**同进程相对量**或**逐像素/逐位一致性**证据。
5. 每个节点单独提交,提交信息用中文、说明改动与验证方式。
---
## 四、风险与对策
| 风险 | 对策 |
| --- | --- |
| 面板表驱动改造引入像素差异 | 改造前后用离屏渲染哈希对比;先改 `CLIP`/`MARK` 两个最简单面板做样板,再推广 |
| `-fno-rtti` 破坏第三方/库代码 | 全仓 grep 确认;先单独提交,失败可独立回退 |
| 缓存抽象拖慢热路径 | 只抽象非热路径(derived/decor/panel),scene/lane 不动;每次改完测帧时间 |
| 大文件拆分时误改逻辑 | 纯搬移(不重写函数体),逐文件 diff 核对;先拆 `ApplicationCommands` |
| 探针触发模态「内存不足」框 | 绝不在探针里调 `Application::Initialize()`(历史教训);只构造数据层/纯函数 |
---
## 五、进度索引
进度与每节点的实测数字见 `Documents/Code_Review_Progress.md`。
最终结论见 `Documents/Code_Review_Report.md`。
---
## 六、第 34 轮追加(2026-11-04):验证基础设施 + 后续建议落地
> 触发:§三/§10 之后剩下的三项建议(`EdgeScanFor` 哈希化、`EdgePanelLayoutFor` 表驱动化、
> `wcstod` 自写替身)都卡在**"没有可自证的验证手段"**上。
> 用户要求:先把验证工具做成仓库内的正式资产(`Tools/` 入库),再动被建议的代码。
### 6.1 节点与验收标准
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **T1** | `Tools/` 解除忽略;新增 `Tools/Makefile`;两个工具目录 + `README.md` | `make -C Tools` 构建全部、`make UnitTests` 构建单个、`make clean` 只清工具产物;改头文件/增删源文件自动重编重链 |
| **T2** | `Tools/UnitTests`:数据层语义单元测试 | `result=PASS`,断言数 ≥ 200 |
| **T3** | `Tools/OffscreenChecks`:隐藏窗口渲染 + 帧序列 + 面板像素基线 + 边沿统计参考实现 + 性能 | 零失败;全程不显示窗口、不动真实鼠标/键盘 |
| **T4** | 建议 #2:`EdgeScanFor` 状态查找哈希化 | **先量后改**;性能收益必须大于体积代价,否则回退 |
| **T5** | 建议 #3:`EdgePanelLayoutFor` 表驱动化 | 面板像素基线 A/B **逐像素一致**才允许合入 |
| **T6** | 建议 #4:`wcstod` 自写替身(省 gdtoa 链) | 体积收益可测且数值语义逐位一致 |
### 6.2 本轮新增的硬约束
1. **工具不得把产物写进 `Tools/`**:截图 / 基线 / 报告一律写 `Temp/tools/`(已被忽略);
`Tools/*/Build/` 同样不入库(其它协作者本地 `make -C Tools` 重建)。
2. **工具与本体 ABI 必须一致**:`-fno-exceptions`、`-fno-rtti`、链接时 `-flto -flto-partition=one`;
工具源码自身**不**编成 LTO 字节码(GCC 15.2.0 会在 `choose_baseaddr` 处 ICE)。
3. **"先量后改"**:任何以性能 / 体积为名的改动,必须在同一台机器、同一次会话内给出前后数字;
收益低于噪声、或换来体积上涨的,一律回退,并把数字写进 `Code_Review_Report.md`。
4. **像素基线只做本地 A/B**:哈希与 DPI/字体/数据文件绑定,**不入库、不当黄金图**。
---
## 七、第 35 轮追加(2026-11-04):浮动面板架构评估与收敛 + 整数格式化替换
> 触发:用户指出「7 个实验性功能面板观感/骨架高度一致,但每新增一种或改一次布局
> 总会出意料之外的错误」,要求先**评估**(能否抽公共基类 / 公共组件,浮动面板、
> 二级菜单、右键菜单是否算公共组件),再动手;同时把 §11.5 的
> 「替换 `std::to_wstring` / `std::to_string`」做掉。
### 7.1 节点与验收标准
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **P20** | 面板「特征层」:唯一分类 / 标题 / 滚动几何来源 | 面板像素基线与几何**逐项不变** |
| **P21** | 滚动设置收敛为唯一实现(7 个包装 + 2 条调用链) | `panelscroll` 新检查全绿 |
| **P22** | 全仓替换整数格式化(76 + 2 处) | 面板像素不变 + 单元测试逐字符对照 |
| **P23** | 其余审查清理(垃圾形参 / 重复判定) | 零 warning、像素不变 |
### 7.2 本轮新增硬约束
1. **新增一种实验性功能面板,只允许改三个地方**:
`LanePanelScrollOf` / `PanelStyleOf` / `PanelTitle`(都在 `PlotViewInternal.hpp` /
`PlotViewPanels.cpp`)。三者都是穷举 `switch` 且**不写 `default:`**,
漏登记会被 `-Wswitch` 编译报警。
**禁止**再在宽度 / 布局 / 绘制里各写一份 `kind == XXX` 组合判断。
2. **底部"有按钮行"与"是构建按钮"是两个概念**(`PanelStyle::footerButtons()`
vs `buildButton`)。混用会让面板高度与内容区高度不一致。
3. **整数转文本一律用 `Utf8Util::FormatInteger*`**,不要用 `std::to_wstring` /
`std::to_string`(它们把 MinGW printf 引擎拉进产物,且 libstdc++ 会强制
`__USE_MINGW_ANSI_STDIO=1`,命令行关不掉 —— 见 Makefile 注释)。
4. **改面板几何/绘制前后必须跑** `OffscreenChecks -run panels -mode record/compare`;
本轮它当场抓到了"截取面板少了 37px 高度"的回归。
---
## 八、第 36 轮追加(2026-11-04):§12.7 两项建议的落地
> 触发:用户要求「继续按照上轮报告的后续建议推进」。§12.7 列了两项:
> ① `EdgePanelLayout` 平行数组合并;② `PaintEdgeCursor` 拆分。
| 节点 | 内容 | 结论 |
| --- | --- | --- |
| **P26** | 拆分 `PaintEdgeCursor` 三段内容绘制 | ✅ 完成(新建 `PlotViewPanelPaint.cpp`) |
| **P27** | 平行数组错位隐患 | ✅ 用 `AppendRow()` 唯一入口消除(**未做**完整结构合并,理由见下) |
| **P28** | 收紧不变式 | ✅ 18 个数组**全部**必须等于行数(不再允许"重采样专属列可为空") |
### 8.1 本轮新增硬约束
1. **新增一种面板行,只能走 `EdgePanelLayout::AppendRow(...)`**。
18 个平行数组由它一次补齐;参数行 / 重采样行一律 `rowXxx.back() = …` **赋值**。
**禁止**在任何布局分支里手写 `rowXxx.push_back(...)`。
2. **面板内容绘制按排版类别分文件**:骨架(边框/标题栏/✕/按钮)在 `PlotViewPaint.cpp`,
内容在 `PlotViewPanelPaint.cpp`。新增一种面板 = 加一个 `PanelStyleOf` 分支 +
在对应内容函数里加一支。
3. **`OffscreenChecks -run panels` 的长度不变式现在是"全等"判定**:
任何一列短于行数即失败(这条检查是"绕过 `AppendRow`"的唯一探测器)。
### 8.2 为什么没有做「完整合并成 `std::vector<PanelRow>` + 列视图」
技术上可行(`PanelColumn<T>` 持有 `std::vector<PanelRow>*` + 成员指针,
读点只需在 `layout.rowXxx` 后加一对括号),但 **`EdgePanelLayout` 需要自定义拷贝构造/赋值**
来重新绑定视图;而它是 `EdgePanelLayoutFor()` 的返回值、又被存进 `std::vector`,
拷贝是热路径 —— 拷贝语义一旦写错,会产生**比原缺陷更隐蔽的悬垂视图**(原缺陷至少会被
长度不变式抓到,悬垂指针不会)。
本轮先用 `AppendRow()` 把**真实隐患**("某分支漏推一列")消除,
完整结构合并留待"逐行几何基线"检查就绪后再做。
---
## 九、第 37 轮追加(2026-11-04):逐行几何基线(§13.6 第 1 项)
> 触发:用户要求「按照你的建议继续推进」。§13.6 第 1 项 = 逐行几何基线,
> 它是 §13.3「完整结构合并」与「重写布局算式」的前置护栏。
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **P29** | 新增 `OffscreenChecks -run rowgeom`:把 7 个面板**每个矩形的每个字段**导出为文本基线,逐项比对 | `-mode record`/`-mode compare` 无差异;**篡改基线必须报错**(负向自检) |
| **P30** | 修正长度不变式的判据:从"18 列全等"改为**按排版类别**判定 | `panels` / `panelscroll` 仍全绿,且对 `MARK` 面板不再误判 |
### 9.1 基线的两层覆盖
| 场景 | 数据 | 记录内容 | 用途 |
| --- | --- | --- | --- |
| **A:细节** | 前 6 条信号合并成 1 个网格,7 个面板全开、参数行展开、3 个固定点、折起/展开各一份 | 每行每列的精确矩形(`Temp/tools/rows-baseline/rowgeom-baseline.txt`,983 条键) | 定位级:一眼看出"哪一行哪一列偏了几个像素" |
| **B:规模** | **全部**信号合并成 1 个网格(MF4 = 566 行) | 每个面板一个 FNV-1a 摘要哈希 + 行数/固定点行数 | 覆盖级:任何一行变动都会改哈希(不产生几十万行基线) |
### 9.2 本轮新增硬约束
1. **改面板几何(宽高算式 / 行距 / 内边距 / 折起规则)前后必须跑**
`OffscreenChecks -run rowgeom -mode record|compare`。
基线是**检测器**不是黄金图:变化是**预期**时要重新 record,并在提交信息里说明原因。
2. **`rowRefs` 是重载的**:「列表式面板」用 `rowRefs` 表示"每个信号一行",
而「自由标记面板」用它表示"每个信号一个分组",只填 `rowNameRects` / `rowNameCloses`,
其余 14 列**必须为空**(真正的条目行在 `markerRows`)。因此任何"平行数组长度"不变式
**必须按 `PanelStyle` 分类判定**,不能写"全部 18 列全等"。
3. **`ExecuteToolbarAction(MERGE_LANES / SPLIT_LANES)` 之后、读 `LaneCount()` 之前,
必须先 `SynchronizeView(true)`**:`SetMode()` 只重建 `TabConfig` 的网格配置,
`PlotView::m_lanes` 要等下一次布局/绘制才刷新。
4. 检查里**不要依赖"上一个场景的网格级状态还在"**:`Session::ShowSignals()` 先
`SetAllVisible(false)`,而"全部不可见 ⇒ 丢弃网格"是既有设计
(`RebuildLanes` 的 MERGED 分支),固定点/面板开关会随之清空 —— 该场景要**重新播**。
---
## 十、第 38 轮追加(2026-11-04):§14.5 第 1 项(删死列)+ 第 3 项前半(拆重采样行)
> 触发:用户要求「按照上一轮报告中的 §14.5 下一步(按价值排序)继续推进」。
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **P31** | 删除 `EdgePanelLayout::rowPoints` / `rowIterations`(写入方只剩 `AppendRow`,读取方只剩调试框与缓存键,几何恒为空) | `rowgeom -mode compare` 的差异**只**来自这两列(64 条键,0 条其它差异);`panels -mode compare` 像素不变 |
| **P32** | 从 `PaintPanelListRows` 提取 `PaintPanelResampleRows`("四行只读统计 + 联动输入框"这一段) | 面板像素哈希(尤其 `resample`)与逐行几何**逐项不变** |
### 10.1 为什么"删掉一列"也可以证明
`rowgeom` 的两层基线正好给出**可证伪**的等价性:
* 旧基线 983 行、新基线 919 行;集合差 = 只在旧里的 **64** 行、只在新里的 **0** 行;
* 那 64 行**全部**属于 `rowPoints` / `rowIterations`(脚本按正则断言,非人工核对);
* ⇒ 其余 919 条键**逐字节相同**,也就是说"删掉的两列从没被赋予过非空矩形"这一判断被基线证实。
### 10.2 本轮新增硬约束
1. **`EdgePanelLayout` 行相关的平行数组现在是 16 个**(原 18)。任何"列清单"
(`rowgeom` 导出、长度不变式、调试框、缓存键)都要同步;`AppendRow` 里少推一列
仍会被长度不变式当场抓到。
2. **只读统计/输入框这类"专属排版"要独立成函数**:`PaintPanelResampleRows` 的四行
与其它面板的滑块参数行没有共同点,混在同一个行循环里只会让三种排版互相纠缠。
3. **§14.5 第 2 项(完整结构合并)仍处于"闸门关闭"状态**,未做(理由见 Report §15.3)。
### 10.3 §14.5 第 3 项的完成度
| 段落 | 状态 |
| --- | --- |
| 重采样行(四行只读+输入) | ✅ 已提取为 `PaintPanelResampleRows` |
| 通用参数行(中心轴统计 + 两行滑块) | ⏸ 未提取:依赖 3 个 kind 标志 + 2 套拖拽/编辑状态 + 2 个滑块 lambda,提取需传 9 个参数,可读性收益低于参数表本身 |
| 条目栏(三角 + 名称 + 复选框 + 悬停) | 保留在 `PaintPanelListRows` 里(它是三种排版的公共前缀) |