test(tools): rowgeom 逐行几何基线 + 长度不变式按类别判定

新增 `OffscreenChecks -run rowgeom`(§13.6 第 1 项),把 7 个面板的每个矩形的
每个字段导出为文本基线并逐项比对,作为"完整结构合并 / 重写布局算式"的前置护栏。

两层覆盖
- 细节层:6 信号 1 网格、7 面板全开、参数行展开、3 个真实固定点、折起+展开各一份,
  983 条键(-data MF4-Record.MF4 时 1304 条),失败时直接给出字段名+行号+新旧值;
- 规模层:全部信号合并成 1 个网格(MF4 = 566 行),每面板一个 FNV-1a 摘要 + 行数,
  任何一行变动都会改哈希,同时不产生几十万行基线。

负向自检(证明护栏不是空转)
- 篡改基线副本 `mark.markerRowCount=3 → 2` ⇒ `CHANGED … mismatches=1` + FAIL。

顺带修正第 36 轮定得过严的不变式
- `rowRefs` 是重载字段:MARK 面板用它表示"信号分组",真正的条目行在 markerRows,
  且只填 rowNameRects / rowNameCloses,其余 14 列必须为空;
  ⇒ `panels` / `panelscroll` 的"18 列全等"改为按 `PanelStyle::markerGroups` 分流判定,
  失败时打印 15 个计数字段便于定位。
- 导出器/不变式一律**逐列按自身长度**遍历(按 rowRefs.size() 索引其它列会越界,
  这是本轮第一次 0xC0000005 的根因)。

测试用例里的两处修正
- `MERGE_LANES` 之后补 `SynchronizeView(true)`:`SetMode` 只重建 TabConfig 的网格配置,
  `PlotView::m_lanes` 要等下一次布局才刷新;
- 场景 B 重新播固定点:`ShowSignals()` 先 `SetAllVisible(false)`,而"全部不可见 ⇒ 丢弃网格"
  是 RebuildLanes 的既有设计,网格级状态随之清空(不是缺陷),因此不能跨场景依赖。

验证
- make release:零 warning,785,408 B 不变(本轮只动工具/检查与文档)
- make -C Tools clean && make -C Tools:零 warning
- UnitTests:checks=260 failed=0 cases=19
- OffscreenChecks -all:7 检查 / 127 断言全 PASS(原 6 / 101)
- rowgeom -mode compare:默认数据 983+7 键、MF4 1304+7 键,0 差异
- panels -mode compare:7 个面板像素哈希 + 几何逐项不变

文档:Code_Review_Plan §九、Progress 第 37 轮、Report §14、agent_mem 第 37 轮、Tools/README §4.1/4.3/4.4
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-04 15:49:25 +08:00
1 parent 221f9d2410
commit 353c553721
6 files changed
+821 -48

No files matched your search

+74
View File
@@ -10564,3 +10564,77 @@ release/debug **零警告**;Release `TargetCheck.exe` **890,880 B**(< 1 MB
* **逐行几何基线**(导出每行 18 个矩形做逐项比对)—— 完整结构合并与布局算式重写的前置护栏。
* `rowPoints` / `rowIterations` 已无绘制方使用(旧式输入框排版早已删除),可标注/清理。
* `PaintPanelListRows` 仍约 470 行,可再拆三段,但已不在"改一次就出错"的路径上。
---
## 2026-11-04 · 第 37 轮(`Code_Review` 分支):逐行几何基线(§13.6 第 1 项)
> 提交:见本轮提交信息;规则原文 `Documents/Code_Review_Report.md` §14、`Code_Review_Plan.md` §九、
> `Code_Review_Progress.md` 第 37 轮、`Tools/README.md` §4.1/§4.3/§4.4。
### 1. ✅ 新增 `OffscreenChecks -run rowgeom`(`record` / `compare`)
两层覆盖:
* **细节层**(6 信号 1 网格、7 面板全开、参数行展开、3 个固定点、折起+展开各一份):
每个矩形的每个字段一条键,`Temp/tools/rows-baseline/rowgeom-baseline.txt` 共 **983** 条
(`-data MF4-Record.MF4` 时 1304 条)。
* **规模层**(全部信号 1 网格,MF4 = 566 行):每面板一个 FNV-1a 摘要 + 行数,7 条键。
键格式:`<panel>.<field>` / `<panel>.<列名>[NNN]=x,y,w,h` / `mark.markerGroup[NNN].header=…`。
比对双向(MISSING / ADDED / CHANGED),差异 0 才 PASS。
### 2. 🔴 新硬规则一:`rowRefs` 是**重载字段**,长度不变式必须按类别判定
| 面板类别 | `rowRefs` 含义 | 真正的行在 |
| --- | --- | --- |
| 列表式(mean/center/peak/resample/clip) | 每个信号一行 | `rowRefs` 自身 + 其余 15 列 |
| 边沿计数(count) | 每个信号一行(表格行) | 同上 |
| 「自由标记」(mark) | 每个**信号分组**一项 | `markerRows` / `markerGroups` |
MARK 面板**只填** `rowNameRects` / `rowNameCloses`,其余 14 列**必须为空**。
⇒ 第 36 轮定的"18 列全部等于行数"对 MARK 是**错的**,本轮已改为:
* `PanelStyle::markerGroups == true` ⇒ `rowNameRects`/`rowNameCloses` 等于分组数,
且 `rowHeaders`/`rowChecks`/`rowLevelTracks`/`rowIterTracks` 必须为空;
* 其余面板 ⇒ `rowRefs` + 16 列全部等于行数。
任何"遍历 18 列"的代码(导出器、不变式、批量处理)**都必须逐列按自身长度**遍历,
不能拿 `rowRefs.size()` 当统一行数(否则 MARK 面板直接越界 —— 本轮第一次 `0xC0000005`)。
### 3. 🔴 新硬规则二:`SetMode()` 不同步 `m_lanes`
`ExecuteToolbarAction(MERGE_LANES / SPLIT_LANES)`(以及 `TabConfig::SetMode`)只重建
`TabConfig` 的网格配置,`PlotView::m_lanes` 要等下一次布局/绘制才刷新。
⇒ **合并/拆分后、读 `LaneCount()` 或 `EdgePanelLayoutFor()` 之前,必须先
`app.SynchronizeView(true)`(必要时再 `RenderFrame()`)**。
### 4. ⚠️ 已知行为(不是缺陷):全部不可见 ⇒ 丢弃网格 ⇒ 网格级状态清空
`Session::ShowSignals()` 先 `SetAllVisible(false)`;`RebuildLanes` 的 MERGED 分支在
"无可见信号"时 `lanes.clear()`(与工具栏「清空」一致)。
⇒ 固定点 / 面板开关 / 面板滚动位置 / 面板显隐 **全部丢失**,不是新 BUG。
检查里跨场景依赖这些状态时,必须**重新播**(`rowgeom` 用 `addFreeMarkers(wanted)` lambda)。
### 5. 基线的定位
**检测器,不是黄金图。** 预期内的几何变化要重新 `-mode record`,并在提交信息里写明原因。
负向自检已做:篡改基线中 `mark.markerRowCount=3 → 2`,`-mode compare` 会
`CHANGED … mismatches=1` 并 FAIL ⇒ 护栏不是空转。
### 6. 本轮数字
* Release **785,408 B 不变**(本轮只动工具/检查与文档);零 warning。
* `UnitTests` 260 断言 / 19 用例;`OffscreenChecks -all` **7 检查 / 127 断言**(原 6/101)。
* `rowgeom -mode compare`:默认数据 983+7 键、MF4 1304+7 键,0 差异。
* `panels -mode compare` 7 个面板像素哈希 + 几何逐项不变。
### 7. 遗留(下一步候选,按价值)
1. **`rowPoints` / `rowIterations` 清理**(确认已无绘制方使用,仅 `BuildBoxes` / 命中键残留)——
当前唯一"写了没人读"的列,删掉可省每次 `AppendRow` 的 2 次 push。
判据:`rowgeom -mode compare` + `panels -mode compare`。
2. **完整结构合并(`std::vector<PanelRow>` + 列视图)**:护栏已就绪,但 §13.3 的
**失败可见性**问题未解(拷贝后忘重绑 = 静默悬垂)。若要做,必须同时给
`EdgePanelLayout` 加"视图自检"(比对 `rows` 容量与视图长度)。
3. `PaintPanelListRows` 再拆三段(纯可读性)。