test(tools): 行数组不变式收紧为"18 个全部等于行数"

背景:`AppendRow()`(提交 85aaed5)已经成为新增面板行的唯一入口,
18 个平行数组由它一次补齐。因此旧的宽松判据("13 个必须等长 +
4 个重采样专属列允许为空")不再需要 —— 任何一列短于行数,
就是"有人绕过 AppendRow 手写 push"的直接证据。

改动:
* `panels` 检查:改为断言 18 个数组长度**全部**等于 `rowRefs.size()`,
  失败时打印全部 18 列的长度便于定位。
* `panelscroll` 检查:同样收紧(大文件 566 行时才会暴露的问题)。
* `Tools/README.md`:同步不变式描述。

文档:
* `Documents/Code_Review_Plan.md` 新增 §八(第 36 轮节点与硬约束)。
* `Documents/Code_Review_Progress.md` 新增第 36 轮(两次重构 + 三个搬移陷阱)。
* `Documents/Code_Review_Report.md` 新增 §13(拆分职责表、为何不做完整合并、
  失败可见性对比、后续建议)。
* `Documents/agent_mem.md` 新增第 36 轮规则。

验证:
* `make release` 全量零 error / 零 warning,产物 **785,408 B**。
* `UnitTests` → checks=260 failed=0;`OffscreenChecks -all` → 6/6 PASS,checks=101 failed=0。
* `panels -mode compare` → 7 个面板像素哈希与几何逐项不变。
* `panelscroll`(MF4 566 行)→ 18 个行数组全部 = 566。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-04 15:20:07 +08:00
1 parent 85aaed55f6
commit 221f9d2410
6 files changed
+260 -35

No files matched your search

+20 -33
View File
@@ -418,22 +418,23 @@ namespace jdi
Expect(inside, std::string("panel inside plot area: ") + PanelKindName(kind));
// 不变式:`EdgePanelLayout` 的**平行数组长度必须全部等于行数**。
// 这 13 个按行平行的 vector 每个分支都要按同一顺序 push 恰好 13 项,
// 漏一项就会让某一列读到别的行的矩形("改布局就出错"的典型机制)。
// ⚠️ 「重采样」专属的 4 个数组(最大/最小间隔、间隔/频率输入框)在**其它**
// 面板里允许为空(没人读),但只要非空就必须同样与行数对齐。
// 这 18 个按行平行的 vector 现在由 `AppendRow()` 一次补齐
//(新增行的唯一入口),因此**不允许**任何一列短于行数 ——
// 短了就说明有分支绕过 `AppendRow` 手写 push("某列读到别人矩形"的根因)。
// 成本极低(O(1) 比长度),但能挡住整类错位缺陷。
const std::size_t rowCount = layout.rowRefs.size();
const std::size_t coreSizes[] = {
layout.rowRefs.size(), layout.rowHeaders.size(),
layout.rowNameRects.size(), layout.rowTriangles.size(),
layout.rowChecks.size(), layout.rowPoints.size(),
layout.rowIterations.size(), layout.rowStats.size(),
layout.rowLevelTracks.size(), layout.rowLevelValues.size(),
layout.rowLevelResets.size(), layout.rowIterTracks.size(),
layout.rowIterValues.size(), layout.rowIterResets.size()};
const std::size_t parallelSizes[] = {
layout.rowRefs.size(), layout.rowHeaders.size(),
layout.rowNameRects.size(), layout.rowTriangles.size(),
layout.rowChecks.size(), layout.rowPoints.size(),
layout.rowIterations.size(), layout.rowStats.size(),
layout.rowLevelTracks.size(), layout.rowLevelValues.size(),
layout.rowLevelResets.size(), layout.rowIterTracks.size(),
layout.rowIterValues.size(), layout.rowIterResets.size(),
layout.rowInfoMaxInterval.size(), layout.rowInfoMinInterval.size(),
layout.rowIntervalField.size(), layout.rowFrequencyField.size()};
bool parallel = true;
for (const std::size_t size : coreSizes)
for (const std::size_t size : parallelSizes)
{
if (size != rowCount)
{
@@ -441,19 +442,7 @@ namespace jdi
break;
}
}
const std::size_t resampleSizes[] = {
layout.rowInfoMaxInterval.size(), layout.rowInfoMinInterval.size(),
layout.rowIntervalField.size(), layout.rowFrequencyField.size()};
bool resampleParallel = true;
for (const std::size_t size : resampleSizes)
{
if (size != 0 && size != rowCount)
{
resampleParallel = false;
break;
}
}
if (!parallel || !resampleParallel)
if (!parallel)
{
EmitLine("[panels] %s row arrays: refs=%d hdr=%d name=%d tri=%d chk=%d pts=%d it=%d "
"stat=%d lvl=%d/%d/%d iter=%d/%d/%d info=%d/%d fld=%d/%d\n",
@@ -476,9 +465,7 @@ namespace jdi
static_cast<int>(layout.rowIntervalField.size()),
static_cast<int>(layout.rowFrequencyField.size()));
}
Expect(parallel, std::string("row arrays have identical lengths: ") + PanelKindName(kind));
Expect(resampleParallel,
std::string("resample row arrays empty-or-aligned: ") + PanelKindName(kind));
Expect(parallel, std::string("all 18 row arrays match row count: ") + PanelKindName(kind));
char buffer[160] = {};
std::snprintf(buffer, sizeof(buffer), "%s=%016llX,%d,%d,%d,%d\n", PanelKindName(kind),
@@ -605,7 +592,8 @@ namespace jdi
initial.scrollTrack.Width > 0.0f ? 1 : 0,
static_cast<int>(initial.rowRefs.size()));
// 多信号场景下额外守住"平行数组长度一致"(本轮新增,成本 O(1))。
// 多信号场景下额外守住"18 个平行数组长度全部等于行数"
//(`AppendRow()` 是新增行的唯一入口 ⇒ 任何一列短了就是绕过它的证据)。
{
const std::size_t rows = initial.rowRefs.size();
const std::size_t sizes[] = {initial.rowHeaders.size(), initial.rowNameRects.size(),
@@ -621,14 +609,13 @@ namespace jdi
bool aligned = true;
for (const std::size_t size : sizes)
{
// 「重采样」专属数组在其它面板允许为空(见 `panels` 检查的说明)。
if (size != rows && size != 0)
if (size != rows)
{
aligned = false;
break;
}
}
Expect(aligned, std::string("row arrays aligned with rows: ") + name);
Expect(aligned, std::string("all 18 row arrays match row count: ") + name);
}
if (maxScroll <= 1.0f)