fix(resample): 重采样间隔/频率按真实值显示(次级单位与频率保留三位小数)

问题(用户实测 MF4-Record.MF4 / m_MOT_Current_DCom[0]):
- 最小数据间隔真实值 0.003696631s,却被显示成 3ms 700us(us 部分被向上截取);
- 对应频率真实值 270.516586589Hz,却被显示成 270.27Hz;
- 最大数据间隔 0.016182614s 显示成 16ms 200us、频率显示成 61.7284Hz,同样不准。

根因(两个,同一个坑):
1. RoundIntervalForDisplay() 把间隔先舍入到 3 位有效数字(0.003696631 -> 0.0037),
   次级单位因此变成 700us,真实尾数被抹掉;
2. 频率用舍入后的 3.70ms 换算得 270.27Hz,而输入框用原值得 270.517Hz,两处不一致。

修法(一律使用真实值,舍入只发生在输出文本的最后一步):
- Utf8Util 新增 FormatDecimalFixed(value, decimals=3):固定小数位、保留尾随 0、
  绝不出现科学计数法;
- Utf8Util 新增 FormatSubUnit(value, maxDecimals):带小数才补到 3 位,整数不加小数点
  (5.06s -> 5s 60ms,而不是 5s 60.000ms);
- FormatDurationCompound 的两处次级单位(s+ms、ms/us/ns)改用它;
- FormatFrequency 改为 FormatDecimalFixed(scaled, 3);若保留 3 位后变成 0.000
  (超低频,如 1/3662s ≈ 0.27mHz)则退回 FormatDecimal,避免有意义的值显示成 0;
- PlotViewInternal.hpp 删除 RoundIntervalForDisplay,FormatIntervalText 直接用真实值;
- PlotViewPaint.cpp 的统计行尾缀与频率输入框都用真实 interval 换算。

修复后的显示:
  min interval 0.003696631s -> 3ms 696.631us
  frequency   1/0.003696631 -> 270.517 Hz
  max interval 0.016182614s -> 16ms 182.614us
  frequency   1/0.016182614 -> 61.795 Hz

同时撤销了此前两次仅修改 agent_mem.md 的提交(47b1213 / caa035a,内容为乱码且无有效信息),
并按正确编码重新整理本轮记忆章节。

面板仍为四行(最大间隔 / 最小间隔 / 重采样间隔 / 重采样频率),未改动任何 UI 布局。
make release 零 warning。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-03 14:16:25 +08:00
1 parent 9fdbf49841
commit c4300bd0e1
6 files changed
+260 -43

No files matched your search

+100
View File
@@ -9762,3 +9762,103 @@ E 面板:四行几何存在;最大间隔行文本(含(频率)尾缀)
* **先打印数据、再改 UI**;`FormatIntervalText` / `FormatFrequencyText` / `RoundIntervalForDisplay`
这类"显示层"函数的**舍入基准**是首要怀疑对象。
* **不要擅自改用户的 UI 布局/行数**——发现问题优先问,或只改数值来源。
---
## 2026-11-03 第二十六轮(本机 dev):重采样面板拖动/关闭失效 + 显示精度按真实值 + 派生烘焙接入公式 + 克隆拆分
> 用户报告的六件事:①「重采样」面板拖不动、✕ 关不掉;②统计「最小数据间隔」与「重采样频率」
> 输入框显示值不一致;③所有派生烘焙没接入「公式映射」;④信号右键菜单要拆成两个克隆;
> ⑤显示精度:间隔要 3ms 696.631us、频率要 270.517 Hz(真实值保留三位小数),
> 而不是 3ms 700us / 270.27 Hz;⑥面板仍须四行,不得擅自增改 UI。
### 1. 「重采样」面板拖不动 + ✕ 关不掉(两个独立缺陷)
- 拖不动(两处):
- `OnLButtonDown` 的 `resampleMode` 分支末尾无条件 `return true`,把标题栏按下也吞了,
而「标题栏 -> `EDGE_PANEL_MOVE`」的判定在面板分支最末尾 => 改为只消费 `insideContent` 内的点击。
- `OnMouseMove` 的 `EDGE_PANEL_MOVE` -> `switch (m_edgePanelDragKind)` 漏 `case RESAMPLE`
=> 位置写进 `edgePanel`、又从 `resamplePanel` 读 => 纹丝不动。
- ✕ 关不掉:`OnLButtonUp` 的 `m_pendingPanelClose` -> `switch (kind)` 漏 `RESAMPLE`
=> 落进 `default: SetLaneEdgeCount(...)`(关掉的是「边沿计数」)。补 `SetLaneResample(lane, false)`。
- 规律:新增 `EdgePanelKind` 时每个 `switch (kind)` 都要补分支。
历史踩坑:`MARK` 漏过一次、`RESAMPLE` 这次漏了两处。以 `kEdgePanelKinds[]` 为准逐个 grep。
### 2. 显示精度:一律用真实值,次级单位/频率保留三位小数
真实现测(MF4-Record.MF4 / m_MOT_Current_DCom[0],8475 采样,6857 个不同间隔):
| 量 | 真实值 | 旧显示(错) | 现行显示(对) |
| --- | --- | --- | --- |
| 最小间隔 | 0.003696631 s | 3ms 700us | **3ms 696.631us** |
| 最小间隔频率 | 270.516586589 Hz | 270.27 Hz | **270.517 Hz** |
| 最大间隔 | 0.016182614 s | 16ms 200us | **16ms 182.614us** |
| 最大间隔频率 | 61.7947 Hz | 61.7284 Hz | **61.795 Hz** |
两个根因:
1. `RoundIntervalForDisplay()` 把间隔舍入到 3 位有效数字(0.003696631 -> 0.0037)
=> 次级单位变成 700us,尾数被抹掉。该函数已删除。
2. 频率用舍入后的 3.70ms 换算 => 270.27 Hz;而输入框用原值 => 270.517 Hz
=> 两处不一致。现在统一用真实间隔换算,天然一致。
修法(`Utilities/Src/Utf8Util.cpp`):
- 新增 `FormatDecimalFixed(value, decimals = 3)`:固定小数位、保留尾随 0、
绝不出现科学计数法(与 `FormatDecimal` 的去尾随 0 语义相反)。
- 新增 `FormatSubUnit(value, maxDecimals)`:带小数才补到 3 位,整数不加小数点
(5.06s -> 5s 60ms,而不是 5s 60.000ms)。
- `FormatDurationCompound` 的两处次级单位(s+ms、ms/us/ns)改用它。
- `FormatFrequency` 改为 `FormatDecimalFixed(scaled, 3)`;若保留 3 位后变成 0.000
(超低频,如 1/3662s ≈ 0.27mHz)则退回 `FormatDecimal`,避免把有意义的值显示成 0。
- `PlotViewInternal.hpp`:删除 `RoundIntervalForDisplay`,`FormatIntervalText` 直接用真实值。
- `PlotViewPaint.cpp`:统计行 `intervalWithFrequency` 与频率输入框都用真实 `interval`。
关键不变式:任何显示路径都不得先舍入再换算(舍入只能发生在最后一步输出文本时,
且间隔与频率必须基于同一个未舍入的原始值)。这正是旧实现两次翻车的同一个坑。
### 3. 派生烘焙接入「公式映射」
`BuildDerivedSignal` / `CloneSignal` 的烘值判定漏了 `HasFormula()`
=> 带公式的信号派生/克隆后公式被静默丢弃。统一为
`HasFormula() || gain != 1 || absolute || mirrorVertical || offsetY != 0`
(与 `ApplyDrawTransform` 的结算顺序同源)。`BuildResampledSignal` 本就全程走 `ApplyDrawTransform`。
### 4. 信号右键菜单:克隆拆成两个语义(均不烘焙采样值)
| 菜单项 | 采样值 | 样式 |
| --- | --- | --- |
| 「克隆该信号」 | 原始数据 | 外观 + 仅时间轴偏移,其余调整复位 |
| 「克隆该信号(带调整)」 | 原始数据 | 外观 + 整套调整(公式/系数/Y 偏移/时间轴偏移/水平与垂直镜像/绝对值) |
新增 `Application::CloneSignalAdjusted`、`CMD_SIGNAL_CLONE_ADJUSTED = 6`、
`kMessageCloneSignalAdjusted = WM_APP + 6`。两者都保留时间轴偏移
(共用同一条文件时间轴,烘进采样值会在越界段写 NaN)。
### 5. 流程教训(用户两次严厉批评,必须遵守)
1. 不要擅自改 UI:用户只要求修数值,却新增了两行统计行 —— 已被明确否决并回退。
面板保持四行(最大间隔 / 最小间隔 / 重采样间隔 / 重采样频率)。
2. 不要无脑跑探针:大段 `Initialize()` + `Render()` 的探针既慢又容易崩,
崩了会经 `MemProbe::OnTerminate()` 弹模态「内存不足」框
(`-fno-exceptions` 下任何分配失败/断言都会走它),用户会以为程序真没内存。
=> 优先写只链接 `Utf8Util.o` 的纯值打印程序(本轮只用了 60 行、秒级验证、零弹框风险)。
3. 先打印数值、再改代码:本轮定位只用了"打印 6 个数字",
比前一轮 300 行 UI 探针快得多也准得多。
4. `.preflight` 已被用户删除,不要再重建测试框架目录。
5. 写中文记忆文件必须走 Python 或编辑工具,不能经 PowerShell 的 `Get-Content`/`WriteAllText`
往返(会按 GBK 解码再编码,中文全毁)。
### 6. 本轮验证(纯值打印,无窗口)
`make release TARGET_FILE_NAME=TargetCheck` 零 warning。格式化输出实测:
```
min interval 0.003696631s -> 3ms 696.631us
frequency 1/0.003696631 -> 270.517 Hz
max interval 0.016182614s -> 16ms 182.614us
frequency 1/0.016182614 -> 61.795 Hz
5.06s -> 5s 60ms (整数次级单位不加小数点)
1h 1min 2s freq -> 0.000273075 Hz (超低频不显示成 0)
5.123ps -> 5.123 ps
```