实验性功能面板完善 + 预览曲线绘制管线性能优化 + UI主题配色 + 绘制效率及部分遗留问题修复 #2

Merged
LuChiChick merged 13 commits from UI_Refactor into main 2026-11-02 11:59:00 +00:00
Owner

主要内容

  • 实验性功能浮动面板:边沿计数改为表格(表头/信号配色行/三列对齐/可滚动),
    中心轴边沿计数恢复可滚动分组列表;面板限高与滚动、组名折起真正收起。
  • 边沿统计可靠性:统计不再卡在"统计中…";鼠标持续移动时面板持续刷新(版本号 + 节流);
    统计进行中加载文件不再被阻塞(SetLoadingBusy)。
  • 预览曲线绘制管线:悬停条目栏时预览曲线高亮丢失修复(改前景绘制);
    稠密区间改逐像素列包络带;新增预览曲线像素缓存层(平移只滚动像素 + 补竖带);
    光标取值胶囊不再逐帧重算曲线;样式补丁不再同步重建网格图层。
  • 主题与交互:4 套主题配色 + 默认 Light、预览曲线连续配色、信号右键菜单精简/克隆信号、
    "显示布局边界"调试层、标签页 DPI 修复、布局边界幽灵元素与穿透修复。
  • 体积与工程化:-fno-exceptions + MemProbe + LTO,产物 909,824 → 814,080 B(低于 1MB 预警线)。

验证

  • 离屏探针全绿:p20 9/9、p21 9/9、p22 14/14、p23 8/8、p25 12/12、p27 8/8。
  • make release / make debug 零告警;极端工况(3×VERY_BIG_DATA、全部信号并一条网格、
    全部实验性功能开启)拖动视界 264ms → 5.9ms,光标拖动 187ms → 20.9ms。
主要内容 * 实验性功能浮动面板:边沿计数改为表格(表头/信号配色行/三列对齐/可滚动), 中心轴边沿计数恢复可滚动分组列表;面板限高与滚动、组名折起真正收起。 * 边沿统计可靠性:统计不再卡在"统计中…";鼠标持续移动时面板持续刷新(版本号 + 节流); 统计进行中加载文件不再被阻塞(SetLoadingBusy)。 * 预览曲线绘制管线:悬停条目栏时预览曲线高亮丢失修复(改前景绘制); 稠密区间改逐像素列包络带;新增预览曲线像素缓存层(平移只滚动像素 + 补竖带); 光标取值胶囊不再逐帧重算曲线;样式补丁不再同步重建网格图层。 * 主题与交互:4 套主题配色 + 默认 Light、预览曲线连续配色、信号右键菜单精简/克隆信号、 "显示布局边界"调试层、标签页 DPI 修复、布局边界幽灵元素与穿透修复。 * 体积与工程化:-fno-exceptions + MemProbe + LTO,产物 909,824 → 814,080 B(低于 1MB 预警线)。 验证 * 离屏探针全绿:p20 9/9、p21 9/9、p22 14/14、p23 8/8、p25 12/12、p27 8/8。 * make release / make debug 零告警;极端工况(3×VERY_BIG_DATA、全部信号并一条网格、 全部实验性功能开启)拖动视界 264ms → 5.9ms,光标拖动 187ms → 20.9ms。
LuChiChick self-assigned this 2026-11-02 11:56:59 +00:00
LuChiChick added 13 commits 2026-11-02 11:56:59 +00:00
## 0. 仓库同步

远端 gitea 已把 UI_Refactor 合入 main(PR #1,合并提交 322b658,分支保留)。
main 与 UI_Refactor 均同步到该提交;dev 分支远端从未存在(意外产物),本地已删除。

## 1. 修正上一轮的性能误判(重要)

上一轮报告称「PlotView 基线每帧在 WarmLaneLayersStep 上多花约 2.4ms」。本轮用两级计时查证,
该结论是错的:

  排空前 idle      2.60 ms   ← 含"图层预热队列排空"的瞬时成本
  排空后 idle      0.53 ms   ← 真正的稳态
  强制整帧 idle    0.46 ms

那 2.4ms 是载入 283 个网格后给 16 个网格补建宽基底图层的**瞬时**开销(每帧补 1 个,约 16 帧结束),
属于设计内的预热,不存在每帧白烧的固定成本。

仍修掉顺带发现的一处顺序问题:RollWideBuffer 原来对全部 283 个网格先算 LaneLayerKey()
(逐网格内容哈希 ≈0.7ms/帧)再判断可视性,且离屏网格的"结构不可用"分支会逐帧把它们塞进
待重画队列再被清掉。现在把 LaneVisuallyRelevant 判断提到循环最前面(见函数内注释)。

## 2. 浮动面板被网格边界穿透(层级 + 挖洞 + 裁剪)

根因:PlotView::CollectDebugBoxes 把网格盒子与面板盒子放在同一绘制层,只靠 depth 排序;
而调试线画在所有内容之后,"后画"只决定谁压谁,不能阻止下层组件的线仍然可见。

修复三步:
- 收集面板前 boxes.PushLayer() ⇒ 面板层高于网格层;
- 每个面板 boxes.AddOccluder(layout.panel) ⇒ 绘制更低层盒子时用 Region::Exclude 挖洞;
- boxes.SetClip(layout.panel) ⇒ 面板内部元素(如滚出内容区的行)不再登记到面板外。

## 3. 滚动时面板/菜单边界闪烁或溢出

滚动容器里的条目矩形始终存在,只是绘制时被裁剪掉;调试层读到这些矩形就会给"看不见的内容"画框,
滚动时条目不断进出可视区,框也就不断出现/消失 ⇒ 闪烁 / "边界超出菜单"。

- 面板内容裁到 layout.panel;
- 菜单条目裁到 m_scrollEnabled ? m_scrollViewport : m_rect;二级子菜单条目裁到 m_subRect。

## 4. 顺带小优化

EdgePanelLayoutFor 单次约 2~3µs,283 网格 × 5 类 = 1415 次/帧。现在先看该网格的 5 个面板
open 标志,全关就跳过内层循环。

## 5. 验证

- make release / make debug 零 error 零 warning;TargetCheck.exe 909,824 B(888.5 KiB < 900 KiB)。
- 新增探针 .preflight/lbpanel 10/10:
  panel-open / panel-contents-clipped-to-panel / panel-registered-as-occluder /
  no-lane-bounds-inside-panel(面板内网格层颜色像素=0)/ scrolling-actually-happened
  (真滚动 0.0→1295.1)/ panel-boxes-stable-while-scrolling(11/11 帧签名一致)/
  menu-open-scrollable / no-box-escapes-menu / scrolled-out-items-not-collected。
- 回归:lbcovr 7/7、lbzorder 4/4、lbresid 5/5、lbcheck 21/21、uicheck 69/69。
- rendercheck:8 个场景哈希与上一轮**逐一相同** + FUNC 7/7(本轮不应改变任何正常绘制像素)。

## 6. 文档

Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第十阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-29 第十二轮」的结论修正、浮层三件事、踩坑。
## 为什么

用户 2026-10-29 要求:按既定计划完整实现改进,但
① **不得把编译产物拆成 DLL**(项目定位是单文件轻量工具,最终只能有一个 .exe);
② 异常处理改用**预检验**而不是 try/catch(-fno-exceptions 省体积,且 try/catch 本身开销大);
③ 先删除 Documents 内三份 UI 重构文档再按"计划 / 当前进度 / 最终报告"重建;
④ 顺便审计代码,删除无用/过时注释,修复名实不符;
⑤ 提交到本地仓库,期间不要占用用户的鼠标/键盘/屏幕。

## 改了什么

### 1. 体积(909,824 B -> 788,992 B,-120,832 B,-13.3%)

Makefile 新增三个可覆盖开关(默认全为 1):

- `LAYOUT_BOUNDS`  编入/剔除「显示布局边界」叠加层
- `NO_EXCEPTIONS`  追加 `-fno-exceptions`
- `LTO`            追加 `-flto`(编译 + 链接)

派生:`FUNC_SECTIONS`(LTO 下为空)、`GC_SECTIONS`(`-Wl,--gc-sections`)、
`ASM_LISTING`/`ASM_LISTING_C`(LTO 下为空,因为 lto-wrapper 会丢弃 `-Wa` 清单)。

两个实测复现的坑(已写进 Makefile 注释):

- **切换 LTO 必须 `make clean`**:LTO 与非 LTO 目标文件混链会让 GCC 15.2.0 在
  `binds_to_current_def_p (symtab.cc:2598)` 处 ICE。
- **`-flto` 与 `-ffunction-sections -fdata-sections` 同时打开也 ICE**(干净重编也必崩,
  报错落点是 `DataModel.hpp:393`,但那是 COMDAT 解析落点、不是原因)⇒
  LTO 下 `FUNC_SECTIONS` 置空,并加 `-flto-partition=one`:默认 11 个分区的 LTRANS
  稳定复现,单分区从未复现。**这是稳定性选项,不是性能选项。**

### 2. 关异常必须成对交付:预检验 + 兜底

新增 `Utilities/Inc/MemProbe.hpp` + `Utilities/Src/MemProbe.cpp`:

- `MemAvailablePhysical()`:`GlobalMemoryStatusEx` + 200ms 缓存
- `MemCanAllocate(bytes)`:预留后仍须保有 1/8 可用内存
- `MemReserveIfRoom(container, n)`:含 `capacity()` 早退与乘法溢出保护
- `MemInstallTerminateHandler()`:OOM 时弹一次带建议的模态提示后 `_Exit`(而不是静默消失)

接入:

- `Utilities/Src/DataModel.cpp`:全仓**唯一**的 `try/catch`(`extremes.reserve` 捕
  `std::bad_alloc`)改为 `MemReserveIfRoom(extremes, count / 8)`
- `Components/PlotView/Src/PlotView.cpp`:`AvailablePhysicalBytes()` 原有一份**完整重复**
  的 `GlobalMemoryStatusEx` + 缓存实现 → 转发 `MemAvailablePhysical()`
- `Components/Application/Src/Application.cpp`:`Initialize()` 开头安装 terminate handler

### 3. L1 几何产物层

新增 `Components/Shared/Inc/UiBox.hpp` + `Src/UiBox.cpp`:

- `struct UiBox { rect, clip, depth, layer }` —— 纯值类型,不继承任何东西
  (继承会和 PlotView 4 层位图缓存 / SignalPanel 行位图缓存的失效体系打架)
- `class UiBoxTable : public UiTextRectSink` —— 只累计几何 + 裁剪/层/遮挡,**不含绘制代码**

`UiComponent::CollectDebugBoxes` → **`virtual void BuildBoxes(UiBoxTable&)`**:
组件从"收集布局边界(调试用)"改为"上报几何产物",不再需要知道"调试"这个概念存在。
`UiDebugBounds.hpp/.cpp` 退化为唯一的消费者(配色 + 包角描边 + 遮挡挖洞)。

除宿主 `ApplicationCommands.cpp` 外,所有组件源码现在只包含 `UiBox.hpp`。

### 4. 审计

- `MemProbe` 里"省 40~60KB"的估算 → 改为实测 27,136 B
- `MemProbe.hpp` 的 `<new>`、`MemProbe.cpp` 的 `<cstdio>`:未使用,删除
- `UiDebugPaintBounds(g, rect, depth)`:零调用者,删除(只保留带 clip 的版本)
- `TabBar`/`Toolbar`/`PlotView`/`SignalPanel` 的 `BuildBoxes` 文档"(调试用)"→"几何 box"
- `PlotView::SetForceFullRepaint` 补写"为什么保留而不优化"

### 5. 文档

`Documents/UI_Refactor_Plan.md` / `_Progress.md` / `_Report.md` 先 `git rm` 再按
「计划 / 当前进度 / 最终报告」重建;`Documents/agent_mem.md` 追加第十三轮记录。

## 怎么验证

```
make clean
make release TARGET_FILE_NAME=JustDataInspector -j16   # 零 error / 零 warning,788,992 B
make debug   TARGET_FILE_NAME=JustDataInspectorD -j16  # 零 warning

uicheck 69/69   lbcheck 21/21  lbresid 5/5  lbzorder 4/4  lbcovr 7/7  lbpanel 10/10
rendercheck: 8 个场景哈希与改造前逐一相同 + FUNC 7/7 PASS, exit=0
  A FAED59DF284153DB  B E6443BFE4E3955F8  C 96A0AA8C402F1729  D E3D5DBD998E43720
  E DA0583CB13C26A6F  F FFB822159F604D1D  G 19F8DE8FE3A8DF18  H DBBDDE4CB2C97C92
```

体积溯源(`objdump -h`,基线 → 最终):
`.text` 735,600 → 657,168;`.rdata` 49,184 → 35,016;
`.xdata` 48,132 → 27,512;`.pdata` 35,424 → 28,524;`.rsrc` 20,656 不变。

## 没做什么(有意为之)

- **没有拆 DLL**(用户明确要求单文件)
- **没有改优化等级**(`-Oz` 保持)
- **L2(`UiDamage` 脏矩形 + 独立叠加层)评估后缓做**:受益方只有"调试开关打开时的帧率",
  而正确实现必须把叠加层旧矩形注入底层组件重绘范围,穿透 PlotView 四条快路径 + 两层缓存;
  在用户反复报过"拖拽错位 / 贴图残留"的背景下,风险与收益不成比例。理由写在
  `Documents/UI_Refactor_Plan.md` §5 与 `PlotView::SetForceFullRepaint` 的文档里。
## 为什么

用户 2026-10-29 报告两条功能异常:

1. 给某条信号设置「调整 → 时间轴」偏移后,再点「适应视图」,视界没有跟着平移,
   曲线整体跑到视界之外;
2. 用「均值滤波 / 弱峰调整」的「构建为新信号」派生出来的信号是**空的**(有一个网格但画不出任何东西)。

## 根因

### ① 时间边界没有叠加 offsetTime

采样实际画在 `t + SignalStyle::offsetTime` 处,但"取景用的时间范围"只统计了时间轴的
源时刻下限/上限(`axis.minTime/maxTime`),三处都漏了偏移:

- `TabConfig::ComputeVisibleTimeBounds`(「适应视图」/视图裁剪的时间范围来源)
- `PlotView::CollectFitContributions`(`minTime`/`maxTime` 与单值点的 `pointTime`)
- `PlotView::FitTimeToLane`(网格右键「按网格时间轴展开」)

于是偏移后取景仍停留在原始时间范围。

### ② 派生信号把时间偏移"烘"进采样值

`Application::BuildDerivedSignal` 的 2c 步按 `time[i] - offsetTime` 重采样,把平移写进采样值。
派生信号与源信号**共用同一条文件时间轴**,所以平移出轴范围的那一段只能写成 NaN:
偏移 ≥ 数据跨度时整条信号变成全 NaN(= 空信号),偏移很小时也会在两端留下与实际曲线不符的空洞。

预览曲线本来就用**源样式的 `offsetTime`** 绘制(`PaintDerivedCurve` 里 `TimeToX(time + offsetTime)`),
因此把偏移留在派生信号的样式上,显示与预览一致,且采样数据一点都不会丢。

## 改了什么

1. `Utilities/Src/DataModelTab.cpp`:`ComputeVisibleTimeBounds` 逐信号叠加 `style->offsetTime`
   (`low = min(low, axis.minTime + off)`、`high = max(...)`)。
2. `Components/PlotView/Src/PlotViewFeatures.cpp`:
   - `CollectFitContributions` 的 `minTime/maxTime` 叠加偏移;单值点的 `pointTime` 改为**显示时刻**
     (`time + offsetTime`),视界居中与边界让位都以它为准;
   - `FitTimeToLane` 同样叠加偏移。
3. `Components/Application/Src/ApplicationCommands.cpp`:
   - 删除 2c(重采样烘值),时间偏移改由**新信号样式**表达(3b 变成唯一路径);
   - 同步重写两处注释说明"为什么不再烘值"。
4. 文档:`DataModel.hpp::ComputeVisibleTimeBounds`、`PlotView.hpp::{TimeBounds,FitContribution}`
   的注释更新为"显示时间(含 offsetTime)"。
5. `Documents/agent_mem.md`:追加第十四轮,并把两条旧规则显式标注为**已作废**
   (时间边界只取源时刻 / 派生信号烘值),写明现行做法与理由。

## 副作用(有意为之)

派生信号若源自"带时间轴偏移"的信号,它的「调整」面板里「时间轴」会显示该偏移值。
旧实现为了让面板显示默认值而烘值,代价是数据被裁掉甚至整条变空 —— 这个取舍不可接受。

## 怎么验证

新增离屏探针 `.preflight/derivecheck`(16 条断言,不占鼠标/键盘/屏幕):

```
INFO signals=10 axis=[0.000000, 1.260000] span=1.260000
mean:     values=1009 finiteCount=1009      -> 有数据、范围不越界
weakPeak: values=1009 finiteCount=1009      -> 有数据
offset=0.126000 fitView=[0.126000, 1.386000] (期望 [0.126, 1.386])-> 适应视图跟随偏移
offset-derived:  values=1009 finite=1009    (修复前 908)
huge-offset-derived: values=1009 finite=1009 styleOffset=2.520000 (修复前全 NaN)
16 passed, 0 failed
```

回归(全部通过):

```
make release / make debug                 零 error / 零 warning
uicheck 69/69   lbcheck 21/21   lbresid 5/5   lbzorder 4/4
lbcovr   7/7    lbpanel 10/10   derivecheck 16/16
rendercheck: 8 个场景哈希与上一轮逐一相同 + FUNC 7/7, exit=0
  A FAED59DF284153DB  B E6443BFE4E3955F8  C 96A0AA8C402F1729  D E3D5DBD998E43720
  E DA0583CB13C26A6F  F FFB822159F604D1D  G 19F8DE8FE3A8DF18  H DBBDDE4CB2C97C92
```

产物仍是 **788,992 B**(单一 exe,未拆 DLL,未改优化等级)。
## 为什么

用户 2026-10-29 报告/要求五件事:

1. 派生信号的「时间轴偏移保留在样式上、其它调整烘值」维持现状(确认无需改动);
2. 信号名右键菜单里很多功能已被下方样式自定义区覆盖 ⇒ 只保留「仅显示该信号」,
   并在它**上方**新增「克隆该信号」;
3. 设置菜单新增「主题配色」二级菜单(带 √ 状态),顺序 Light / Solarised Light /
   Dark / Solarised Dark,默认主题维持现状;
4. 布局边界:中键平移视界时浮动面板上的边界**闪烁**;光标取值胶囊的**文字**框会**穿透**
   到浮动面板上;并请核对网格内固定构件(图例 / Y 轴标尺 / GX 标签 / 回到最上方 /
   浮动面板)的层级关系;
5. 单数据点信号在拖动光标时的**吸附**功能消失。

## 根因

### 布局边界层级(两个都很隐蔽)

- **文本层号写死**:文本是在绘制过程中由 `UiTextRectSink` 收集的,而宿主把所有文本都记在
  固定层 2;组件几何层却是"组件表下标"(`PlotView` = 0)。面板层 = PlotView 层 + 1 = 1,
  **低于**文本层 2 ⇒ 面板的遮挡矩形挖不到文本 ⇒ 胶囊文字画在面板表面。
- **面板像素缓存**:面板"先画进图层、命中缓存就贴图",贴图那一帧不画字 ⇒ 文本框凭空消失
  ⇒ 面板上的边界闪烁。
- 顺带发现 `PlotView::BuildBoxes` 用 `PushLayer()` 却**从不复位**,会把宿主后续所有组件的
  层号整体推高。

### 单点信号吸附

`MinorCellSparse` 在"把判定范围收窄到数据实际时间跨度"的分支里有
`if (!(dataMax > dataMin)) return false;`。单个有效采样点时 `dataMax == dataMin`,
于是被判定成"不稀疏" ⇒ 不吸附。

## 改了什么

### 1. 布局边界层级

- `UiTextRectSink` 新增 `OnTextLayer(int)` / `TextLayer()`;新增 `UiSetTextLayer` /
  `UiGetTextLayer`;`UiBoxTable` 实现为改 `m_layer`,并暴露 `Layer()`。
- `Application::Render` 在绘制每个组件前下发 `UiSetTextLayer(ComponentLayer(&component))`,
  浮层用 `OverlayLayer(1/2)`;新增 `ComponentLayer()` / `OverlayLayer()` /
  `kLayersPerComponent = 3`,与 `PaintLayoutBounds` 的层号**完全同源**。
- `PaintLayoutBounds` 改为**显式** `SetLayer(下标 × 3)`(不再依赖 `PushLayer` 累加)。
- `PlotView`:`BuildBoxes` 用 `baseLayer + 1` 放浮动面板(并登记遮挡)、
  `baseLayer + 2` 放绘制在面板之上的滚动条 / 回到最上方 / 输入框,结束**复位**层号;
  `paintOverlays` 同步分两次抬高文本层。
- 面板像素图层新增 `PanelSlot::textRects` + 转发型收集器 `PanelTextCollector`:
  渲染时存文本框,命中缓存贴图时回放 ⇒ 几何与缓存命中无关(消除闪烁)。

### 2. 信号右键菜单 + 克隆

- 菜单只剩:标题、**克隆该信号**、**仅显示该信号**;删除显示/隐藏、显示全部/隐藏全部、
  预设色块行、样式三项,以及对应的 `CMD_SIGNAL_TOGGLE/SHOW_ALL/HIDE_ALL`、
  `CMD_COLOR_BASE`、`CMD_MODE_BASE` 及其分支(死代码)。
- 新增 `CMD_SIGNAL_CLONE`(post `WM_APP + 5`)→ `Application::CloneSignal`:
  把「调整」(系数 / 绝对值 / 镜像 / Y 偏移)烘进采样值、**时间轴偏移保留在样式上**,
  名称前置 `*`,插入到源信号**正下方**,并**保留原外观**(颜色/透明度/线宽/绘制样式)。
- 抽出 `Application::InsertSignalBelowSource(...)` 供派生与克隆共用。

### 3. 主题配色

- `ThemeKind{LIGHT, SOLARIZED_LIGHT, DARK, SOLARIZED_DARK}` + `ThemeFor()` / `ThemeName()`;
  Light 取 VS Code 2026 默认浅色、Dark 取 Dark+、Solarized Dark 取 VS Code solarized dark。
  **默认仍是 Solarised Light**。
- 新增 `CurrentTheme()` / `SetCurrentTheme()`:宿主交出自己的 `m_theme` 地址,
  切换主题只改内容;`UiControls` 里没有 `Theme` 形参的原语改用它(原来硬编码
  `SolarizedLight()`,换主题不生效)。
- `Application::ApplyTheme()`:改配色 → `PlotView::OnThemeChanged()`(作废场景/宽基底/
  装饰/光晕/面板图层)+ `SignalPanel::OnThemeChanged()`(作废行位图)→ 整窗重绘。
- 菜单用 `SUBMENU` + 4 个 `keepOpen` 子项;新增 `PopupMenu::UpdateSubmenuItemLabel()`
  回写勾选,并让子菜单分支尊重 `keepOpen`(原为无条件关闭)。

### 4. 单点信号吸附

`MinorCellSparse` 改为只在 `dataMax < dataMin` 时放弃;相等时按"那一个小格"判定
(`need = 5 > finiteCount = 1` ⇒ 稀疏 ⇒ 吸附恢复)。

### 5. 审计

`Makefile` 的 `LAYOUT_BOUNDS` / `JDI_LAYOUT_BOUNDS` **从未被任何 `#if` 引用**
(`LAYOUT_BOUNDS=0` 产物字节数完全相同),属名实不符的死开关 ⇒ 删除并注明原因。

## 怎么验证

新增两支离屏探针(不占鼠标 / 键盘 / 屏幕):

```
.preflight/round15check  28 passed, 0 failed
  单点吸附 7 条(含"远处不吸附")、面板层级 5 条、面板文本框跨帧与中键平移稳定 2 条、
  克隆 14 条(烘值逐点比对 / 名称前缀 / 偏移与外观保留 / 调整复位)
.preflight/themecheck    17 passed, 0 failed
  4 套主题可读性与可区分、默认 = Solarised Light、逐主题换色且无旧底色残留、
  切回默认逐像素还原(diff = 0)、子菜单 4 项 / 顺序 / 唯一勾选 / 命令切换 / 菜单保持打开
```

回归全绿:

```
uicheck 69/69   lbcheck 21/21  lbresid 5/5   lbzorder 4/4
lbcovr   7/7    lbpanel 10/10  derivecheck 16/16
rendercheck: 8 个场景哈希与上一轮逐一相同 + FUNC 7/7, exit=0
  A FAED59DF284153DB  B E6443BFE4E3955F8  C 96A0AA8C402F1729  D E3D5DBD998E43720
  E DA0583CB13C26A6F  F FFB822159F604D1D  G 19F8DE8FE3A8DF18  H DBBDDE4CB2C97C92
```

`make release` / `make debug` 零 error / 零 warning;产物 **791,552 B**
(单一 exe,未拆 DLL,未改 `-Oz`;离 1MB 预警线约 250 KB)。
① 主题自适应(`Theme.cpp`):新增 `SrgbLuminance` / `ContrastRatio` / `EnsureContrast` /
   `MixToLuminance` / `ThemeLaneBackground`;四套主题的结构色(gridWeak 1.30 / gridStrong 1.65 /
   axisLine 2.10 / borderStrong 1.70)按底色自动校正,Solarised Light 逐位不变;网格叠底改用
   `ThemeLaneBackground()`(4 处),深色主题下网格层与绘图区底色可辨。
② 派生预览曲线(`HighContrastDerived`)重写为**连续**方案:方向沿信号所在侧远离底色,
   目标亮度 = max(与底色对比度 3.2, 信号亮度 ± 0.18),再按亮度平滑混色;调用点传入真实网格底色
   (旧实现默认写死 Solarised Light 底色 ⇒ 深色主题永远选到更暗的那一侧、几乎看不见)。
③ 信号样式区「线宽 / 透明度」两行右侧新增**复位按钮**(`UiPaintResetIcon` 成为唯一实现,
   菜单/弱峰面板/样式区共用同一套笔画);默认线宽统一到 `DefaultSignalLineWidth(finiteCount)`
   (单点信号取 10.0)。按钮右边界与内容列对齐,内容列过窄时整列不显示。
④ 「均值滤波」面板重做:信号色叠底标题栏 + 复选框 + 展开三角 + 「均值点数 / 迭代次数」两行滑块
   (点击数值可改滑块上限、右侧复位)+ 内容区滚动;新增 `MeanFilterItem::{maxPoints,maxIterations,
   collapsed}` 与 `Lane::meanFilterScroll`,缓存键同步补全。
⑤ 浮动面板命中像素缓存时只贴**面板圆角形状之内**的像素(`CreateRoundRectRgn` 裁剪)并按本帧重画投影
   ⇒ 中键移视界后面板周围不再出现一圈旧波形。
⑥ 显示布局边界:每个浮动面板**独占一个绘制层**(按 `m_panelOrder` 递增),绘制侧
   `UiSetTextLayer` 在同一循环内同步推进 ⇒ 下层面板的边框/文字不再画到上层面板上。
⑦ 自由标记:采样候选距离改为**二维欧氏距离**(单点信号不再在整幅宽度内吸住光标),
   纵向光标画在鼠标位置、圆点落在真实采样点(新增 `FreeMarkerProbe::pointX`);
   `MinorCellSparse` 只对胜出信号算一次;折线缓存容量改为 `max(64, 信号数×2)` 且查找改为
   单键哈希 ⇒ MF4 的 285 信号合并网格中 `ProbeFreeMarker` 平均 0.11ms。

验证:`.preflight` 探针重建(`panelcheck` 71/71、`markercheck` 13/13、`ringcheck` 6/6,合计 90 条,
LTO 目标文件上复跑同样全绿);`make release` / `make debug` 零警告;产物 815,104 B。
文档:`agent_mem.md` 第十六轮(含补丁脚本与编码/行尾踩坑)。
1. 「元素超出浮动面板」真根因(用户 2026-10-30 报告的截图)
   - 绘制侧 PaintEdgeCursor 的 itemized 仍是 (centerAxis || meanFilter || weakPeak),
     而布局侧 EdgePanelLayoutFor 早已是 (centerAxis || weakPeak)。
     于是均值滤波面板被旧的「逐条目」分支**再画一遍**(每行名称 + 复选框,且没有任何裁切),
     滚动后那些行的坐标是负值/越界 ⇒ 直接画到标题栏上。
   - 修法:绘制侧与布局侧统一;旧分支的兜底循环跳过条件补 meanFilter。
     底部「构建为新信号」由共享的 if (hasButton) 绘制,不受影响。
   - 顺带删除「只画完全落在内容区里的行」的 fullyInside 门控:真因修掉后
     g.SetClip(layout.content) 已足够,半截行正常显示,滚动观感连续。

2. 条目栏交互(与「自由标记」分组栏一致)
   - 整行可点:复选框区 = 切换是否预览;已启用 = 展开/收起参数行;未启用 = 直接开始预览。
     命中只要求点落在 layout.content 内(不再要求整行在内容区内),因此半截行也能操作。
   - 悬停行 → 对应预览曲线高亮:新增 m_meanRowHoverLane / m_meanRowHoverRef,
     由 OnMouseMove 维护、OnMouseLeave 与 InvalidateDerivedCaches 清除;
     PaintDerivedCurve/PaintMeanFilterCurve/PaintWeakPeakCurve 新增 bool highlight,
     先画 +5px 半透明同色光晕再把线加粗,行头叠一层 accent;悬停态变化会 InvalidateSceneCache。

3. 均值点数下限 1(用户要求:不可以为 0)
   - kMeanFilterPointsMin 0 → 1;输入提交用 centerTarget ? 0 : kMeanFilterPointsMin
     (中心轴边沿计数沿用旧语义 0 = 不平滑);预览与构建请求都再夹一次下限,
     即使条目被外部写成 0,渲染/构建结果与 1 点逐位一致。

4. 网格线颜色成为主题配色的一部分
   - struct Theme 新增 laneBg(网格叠底色);NormalizePlotContrast → FinalizeTheme,
     先解 laneBg = EnsureContrast(plotBg, cardBg, 1.03),再让 gridWeak/gridStrong/
     axisLine/borderStrong 统一对 laneBg 归一(1.30 / 1.70 / 2.10 / 1.70)。
   - 四个主题各自给出显式取值:Dark 的网格线对叠底色原本只有 1.071(几乎不可见)⇒ 现 ≥1.30;
     其它三个主题同步微调,网格可见性一致。ThemeLaneBackground 返回 laneBg。

5. 行三角尺寸统一
   - 新增 kEdgePanelRowTriangleSize = 12 逻辑像素,均值滤波行与自由标记分组栏共用;
     标题栏的 kEdgePanelDisclosureWidth = 20 保持不变。

6. 均值面板滚动条与文本
   - 滚动条必须在 g.Restore(meanSave) 之后绘制(它在内容裁剪区之外,裁剪内画会被整条裁掉)。
   - 滑块标签/数值改用 UiTextClipped(..., layout.content)(GDI 文本不受 GDI+ 裁剪约束)。

验证(全部离屏)
   uicheck 69/69、lbcheck 21/21、lbresid 5/5、lbzorder 4/4、lbcovr 7/7、lbpanel 10/10、
   derivecheck 16/16、round15check 28/28、themecheck 17/17、.preflight/meancheck 17/17;
   .preflight/meanpix:滚到顶/中/底时标题栏与面板外均为 0 像素差异,内容区正常滚动;
   rendercheck:A–H 八个场景哈希与基线完全一致;
   make release 零 warning(805,376 B);make debug 零 warning。

同步文档:Documents/agent_mem.md 追加「2026-10-30 第十七轮」,
并把第十六轮里「均值点数(0…上限)」「命中要求行完全落在内容区里」两条改成现行做法。
1. 组名展开/收起卡顿的真因(用户报告:288 个信号同一网格、双光标/实验性功能开启时,
   点组名展开收起的动画非常卡顿,而网格里没有任何需要重绘的变化)
   - 滚动定时器给面板动画帧做的是整窗失效,WM_PAINT 里"更新区域不在绘图区内 ⇒
     m_chromeDirty = true"这条启发式把整帧判成脏 ⇒ Render 走 fullChrome 分支 ⇒
     标签栏/工具栏/绘图区全部重绘。绘图区走场景缓存很快,但**光标叠加层每帧都要重画**:
     288 个信号的取值胶囊(圆角 + GDI 文本 + 重叠避让)实测 24~46ms/帧。
   - 新增 Application::m_panelAnimationFrame:滚动定时器在只有面板动画时置位,
     WM_PAINT 命中它且没有任何其它消息要求装饰区重绘时不再把整帧判脏 ⇒ 走 panelOnly。
   - 新增 Application::m_plotPaintValid:panelOnly 帧且该标志为真时跳过 PlotView::Paint
     (面板不覆盖绘图区,上一帧像素原样留在后备缓冲区,整窗回送也不会错)。
     作废点:InvalidatePlotRect(签名多收一个 bool&,5 个调用点统一传入)、Relayout、
     EnsureBackBuffer、WM_PAINT 中"更新区域就在绘图区内"、以及"面板动画帧里绘图区的
     滚动/回到最上方动画同时也在跑"(否则会把绘图区动画冻住)。
   - panelOnly 分支画分隔条时把右边界夹到 floor(m_plotRect.X):分隔条是浮点矩形
     (457.5…462.5),GDI+ 会把最后一列按 50% 混进去;整帧重绘时绘图区随后会盖掉它,
     但跳过绘图区的帧会留下**一整列 1px 接缝**(实测 847 像素)。
   - 实测(283 信号同网格 + 双光标):动画帧 34~43ms → 1.6~2.1ms。

2. 均值滤波面板
   - **未勾选的条目栏点击完全无响应**:未勾选时点名称区域不再自动勾选、也不展开/收起,
     只能点右端复选框(点击仍被消费,不穿透)。
   - **滚动时参数滑块不再消失**:原实现按条目栏矩形判断整行可见性,条目栏滚出内容区后
     整行(含两行参数滑块)被跳过。现在按整行纵向范围(条目栏 ∪ 两行轨道/数值/复位)判定。

3. 信号列表:行集合与折叠状态自愈(用户报告:开自由标记并标记点后,点 MF4 组名,
   三角显示已展开但下属 283 条信号行一条都不出现)
   - 新增 SignalPanel::RowsSignature()(FNV 混合 分组集合/每组信号数/是否折起/是否有动画),
     BuildRows 记录它,EnsureRows 每帧 O(分组数) 校验,不一致就地重建。
     这样任何"改了折叠状态却忘记置 m_rowsDirty"的路径都不会再让行集合与折叠状态脱节。

4. 验证(全部离屏,不占鼠标/键盘/屏幕)
   uicheck 69 / lbcheck 21 / lbresid 5 / lbzorder 4 / lbcovr 7 / lbpanel 10 /
   derivecheck 16 / round15check 28 / themecheck 17 / meancheck 21 全绿;
   新增 .preflight/panelbug 14/14(外部改折叠状态→行集合自愈、动画中途改状态→位移清零、
   未勾选行点击无响应、条目栏滚出后滑块仍绘制);
   新增 .preflight/panelperf 12/12(动画帧 <16ms、且跳过绘图区后该区域像素与紧接的
   整幅重绘逐像素完全相同);
   rendercheck A–H 8 个场景哈希与基线一致;make release / make debug 零 warning
   (805,888 B)。

同步文档:Documents/agent_mem.md 追加「2026-10-30 第十八轮」,
并把第十七轮里"尚未启用 → 直接开始预览"改标注为已推翻。
一、卡顿真因(实测 29.8ms/帧 → 6.6ms/帧)
1. 中心轴面板高度没有上限:288 个信号时面板高 9996px(绘图区只有 1141px)
   ⇒ PanelRectInsideLayer 恒为假 ⇒ 面板像素缓存一次都存不进去 ⇒ 每帧全量重画。
2. PanelGlobalKey 混入鼠标驱动状态(m_hoverSignal / m_hoverLane / m_hoverCursor /
   CursorTime / EdgeRangeMin/Max)⇒ 鼠标一动键就变 ⇒ 所有面板每帧重画;
   PanelSlotKey 里还会为每个信号调一次 EdgeCenterCountsFor(单帧 288 次查询)。
3. 缓存命中路径的投影(与面板同大的半透明抗锯齿圆角矩形)每帧重画,GDI+ 实测 > 20ms。

修法:
* 新增 PlotView::InputIsInteractive()(拖拽中 / 鼠标 140ms 内移动过)。交互期间冻结
  全部鼠标驱动项与统计数字、跳过投影、统计预算降到 8 万(静止时 40 万)。
  m_meanRowHoverLane/Ref 例外 —— 它直接改面板像素且只在进出某行时变化。
* kEdgeScanPairsPerFrame 由 1,200,000 降为 400,000。
* 中心轴面板重做为可滚动列表 ⇒ 面板高度被绘图区夹住、缓存可用、只画可见行。

二、「中心轴边沿计数」面板重做(与「均值滤波」同风格)
条目栏为信号色叠底的可折叠栏 + 右端复选框;栏下同一行显示「上升沿 / 下降沿」;
再下面两行滑块(均值点数 / 迭代次数,标签不带 X/N),数值可点击输入上限、
右侧复位按钮;整个面板可滚动;未勾选时条目栏不可展开。
新增 Lane::edgeCenterScroll + SetLaneEdgeCenterScroll;交互复用 MEAN_* 拖拽模式 +
m_meanDragCenter / m_editMeanCenter 标志分派;光标形状与均值滤波一致。

三、标签页
* 唯一标签页点 ✕ 不再弹警告:直接 erase 后 NewTab(true),名字必然为「视图 1」。
* 新建标签页(含手动新建)默认:组全部折起、无选中信号(清空全局 Selected)、
  样式区无目标。

四、验证
.preflight/bugcheck.exe 59/59(A1 空闲 0.7ms、A2 平移 2.5ms、A3 双光标 2.4ms、
A4 自由标记 5.2ms、A5 中心轴拖动 6.6ms;B3 真实 WM_TIMER+WM_PAINT 路径;
B5 快速连点与动画反向;C 中心轴面板交互;D 标签页默认状态)
panelcheck 71/71、markercheck 13/13、ringcheck 6/6
make release / make debug 零 warning
补充离屏探针段并回填文档:
* A2b:左键拖动视界(RUBBER_SELECT 叠加层每帧移动)0.86ms/帧,最坏 1.39ms。
* C 段新增「点击滑块数值 → 进入上限编辑」断言:m_editTarget == MEAN_POINTS_MAX、
  m_editMeanCenter == true、输入 100 后 maxPoints 变为 100。
* E 段:完整复现用户场景(VERY_BIG_DATA.CSV + MF4-Record.MF4 合并成一个网格 +
  自由标记含跟随光标开启),用真实 WM_LBUTTONDOWN/UP + WM_TIMER + WM_PAINT 点组名展开:
  109 帧、平均 2.75ms/帧、最坏 16.8ms、仅 1 帧超 16ms;每一帧三角状态与行集合一致;
  列表墨迹 7922 → 70037 → 7922。

bugcheck 73/73;panelcheck 71/71;markercheck 13/13;ringcheck 6/6;
make release / make debug 零 warning。
一、浮动面板(用户报告:下方空白、滚动时画到面板外、未改造的面板让一切交互卡顿)
1. ⭐ 根因:旧「逐条目」排布没有高度上限 —— 288 条信号时面板 13010px(绘图区 846px)
   ⇒ PanelRectInsideLayer 恒假 ⇒ 面板像素缓存一次都存不进去 ⇒ 每帧整块重画。
   现在 5 个面板全部限高 + 可滚动,旧的 itemized 布局/绘制/交互整体删除。
2. 无按钮面板(中心轴边沿计数)不再预留「按钮行 + pad」⇒ 底部空白 55px → 8.8px(1 个 pad)。
3. ⚠️ 统计文字改用 UiTextClipped:GDI 文本不受 GDI+ SetClip 约束,滚动时画到标题栏之外。
4. 「弱峰调整」改造为与均值滤波同款的分组列表(条目栏 + 右侧复选框 + 展开后的两行滑块
   + 复位按钮 + 底部「构建为新信号」);新增 Lane::weakPeakScroll / SetLaneWeakPeakScroll,
   WeakPeakItem 增加 collapsed。
5. 「边沿计数」保留两行排版,但内容区同样限高、可滚动(新增 Lane::edgeCountScroll)。
6. 鼠标指针:列表式面板的条目栏**只有已勾选**才显示手型(未勾选点击无效果,保持箭头);
   参数行控件只在参数行展开时给手型 / 左右箭头。
7. 内部标志 m_meanDragCenter / m_editMeanCenter(bool)改为 m_meanDragKind / m_editMeanKind
   (EdgePanelKind),以便 MEAN / CENTER / PEAK / COUNT 共用同一套滚动与滑块拖拽。

二、边沿统计“全部统计中…且永不更新”(288 信号合并到同一网格)
1. ⭐ 根因:累积状态上限固定 64 < 信号数 288 ⇒ 新建状态总淘汰未完成的 ⇒ 没有任何状态能扫完。
   新增 EdgeScanStateLimit() = max(64, 全部信号数 × 2 + 16)。
2. 去掉每状态 60000 的固定配额下限(状态一多就把整帧预算耗光),改为 budget / 未完成状态数;
   分母只数未完成状态,短信号扫完后立刻把预算让给长信号。

三、组名折起后“三角已收起但信号行仍在、仍可点选”
1. ⭐ 根因:RequestEdgeScanFrame() 在每个未完成状态里各 Post 一次(单帧最多 288 次),
   把优先级最低的 WM_TIMER 饿死 ⇒ AdvanceGroupAnimations 永不执行 ⇒ 折起动画永不结束。
   修法:每帧最多 Post 一次(m_edgeScanRequestFrame)。
2. 另一道保险:SignalPanel::EnsureRows() 开头推进一次动画 —— 动画进度只依赖挂钟时间,
   不依赖消息能否按时到达。

四、验证(离屏探针 .preflight/p20,全部 SW_HIDE + 合成消息,不占用鼠标/键盘/屏幕)
  p20.exe  9/9:面板限高且像素缓存真的写进去了、288 个统计全部 complete、中心轴面板底部留白、
               组名折起后行集合无该组信号、两组都折起后 signalRows == 0(真实消息 + WM_TIMER)。
  p20c.exe 11/11:弱峰面板限高/可滚动/缓存命中、展开与收起、滚动生效、条目栏手型规则、
               中心轴滚动时标题栏 0 像素变化(A/B 关掉裁剪 ⇒ 805px)、288 信号空闲帧 2.6ms。
  make release / make debug 零 warning。
一、预览曲线悬停高亮(只开弱峰+中心轴时无反应;三个全开时总亮均值曲线)
   根因:PaintLaneDerivedOverlays 里只有 MEAN 分支传 highlight,且悬停状态没有记录面板种类。
   修法:新增 m_meanRowHoverKind,高亮条件 = 悬停行 == 本行 && 面板种类 == 本曲线来源;
         条目栏强调色同样按种类门控;悬停种类进入面板缓存键。

二、统计数字松手后停在“统计中…”
   根因:交互期间面板缓存键被冻结(为拖动跟手),冻结期满后没有重绘请求(鼠标已停、
         统计也已完成),最后一次绘制仍是冻结帧。
   修法:记录 m_panelKeysFrozen 并公开 PanelRefreshPending();Render 末尾把它并入
         滚动动画定时器的武装条件 ⇒ 空闲后自动补一帧真实数字,随后停表。

三、「中心轴边沿计数」面板改为表格
   表头固定:信号名 / 上升沿计数 / 下降沿计数;每行底色 = 该信号配色(未勾选的行普通底色),
   三列与表头严格对齐,内容可滚动,单击行展开两行参数滑块。
   - EdgePanelLayout 新增 tableHeader / tableColumns[3];EdgePanelLayoutFor 新增 centerTable 分支;
   - 新增 EdgeCenterTableHeader() / EdgeCountCellText();EnsureEdgeCenterItem 默认 collapsed;
   - PanelSlotKey 的 CENTER 分支改为按 item 判断并混合计数(表格已没有 rowStats)。

四、默认主题改为 Light(Application、ThemeFor/ThemeName 默认分支、CurrentTheme 兜底)。

五、标签页文本在 DPI 切换后被省略
   根因:标签宽度恰好等于“内边距+文本+间距+关闭按钮”,无任何余量;TabItemWidget 缓存了
         Configure 时的 scale 并用它反推可用文本宽度,而 TabBar::SetScale 不重建控件树
         ⇒ 125%→100% 后可用宽度少约 4.5px ⇒ 吃掉末字。
   修法:SetScale 变化时重建控件树;并把文本可用矩形改由 ArrangeWidgets 统一下发
         (SetState 增加 textRect 参数),绘制侧不再自行反推。

六、布局边界里的幽灵元素
   - 新增 SignalPanel::HasStyleTarget();BuildBoxes 与 QueryCursor 在无样式目标时
     不再上报/响应色块、样式、调整、线宽、透明度等控件;
   - Paint 的“无网格”分支显式清空 m_legendHits / m_cursorLabelRects / m_cursorSpanRect /
     m_freeMarkerHits / m_panelSlots / m_panelOrder(关闭整个分组后不再残留边界框);
   - 浮动面板之上的滚动条 / 回到最上方 / 内联输入框改为按可见性上报几何。

验证(离屏探针,SW_HIDE + 合成消息,不占用鼠标键盘屏幕)
  .preflight/p21.exe 9/9   .preflight/p22.exe 14/14   .preflight/p23.exe 7/7
  .preflight/p20 系列(9/11/5)全部回归通过
  make release / make debug 零 warning;Release 813,568 B
1) 表格改造归位(上一轮误做在中心轴上)
- 「边沿计数」改为表格:表头(信号名/上升沿计数/下降沿计数)、每信号一行、
  行底色取信号配色、三列对齐、内容可滚动(Lane::edgeCountScroll)。
- 「中心轴边沿计数」恢复为可滚动分组列表(条目栏 + 复选框 + 展开后统计行 + 两行滑块),
  Lane::EnsureEdgeCenterItem 恢复默认展开。
- EdgePanelWidth / EdgePanelLayoutFor / PaintEdgeCursor 统一 listStyle / countTable 判定;
  EdgeCenterTableHeader 更名为 EdgeCountTableHeader。

2) 鼠标持续移动时统计面板不更新
- 根因:交互态(拖拽或 140ms 内移动)把统计数字整段从面板缓存键中跳过,
  只要鼠标不停移动就永远命中旧缓存。
- 修法:扫描推进/完成时自增 m_edgeScanRevision,Paint 开头转为 m_panelStatsVersion
  (非交互帧立即同步,交互帧按 kEdgeScanStatsRefreshFrames=4 节流),
  PanelSlotKey 对 COUNT/CENTER 仅混入该版本号(O(1),不再逐信号查询)。

3) 统计进行中无法加载文件
- 新增 PlotView::SetLoadingBusy:加载期间统计时间预算归零、跳过浮动面板绘制、
  不再自请求扫描帧;Application::LoadFileAsync 进出各设置一次。实测加载 22~31ms。

4) 统计提速
- 扫描预算由固定采样对改为 QPC 时间片(非交互 6ms / 交互 1.5ms,每块检查超时),
  采样对配额改为按未完成状态数公平分摊并把上限提到 8M(交互 2M)。
- 真实 MF4-Record.MF4(283 信号)全部统计状态在 1 帧内完成;悬停+移动帧中位 2~3ms。

验证:离屏探针 p20/p20c/p20d/p21/p22/p23/p25 全绿(新增 p25 12 项);
make release / make debug 零 warning;Release 808,960 B。共享规则已同步到 Documents/agent_mem.md。
一、悬停高亮回归修复
- 根因:高亮画在预览曲线绘制路径里,而该路径在命中静态场景缓存时完全不会被调用
  (悬停态又不参与 SceneContentHash),因此缓存命中帧永远看不到高亮。
- 修法:拆出 PaintDerivedCurvesFor(hoverOnly) 两态,新增 PaintDerivedHoverHighlight,
  在 paintOverlays() 中紧跟 PaintHoverHighlight 以前景方式绘制(面板之下、光标之上)。
  高亮仍按面板种类(均值滤波/中心轴/弱峰调整)分别判定。

二、预览曲线绘制重写(单条 15ms → 1.7ms)
- 稠密区间改用与波形基底同一套「逐像素列 min/max 包络带」+ 一次 FillPath;
  旧抽稀折线在稠密视界下每段都是竖直满幅,GDI+ 描边代价正比于覆盖像素数
  (3200 顶点满幅锯齿抗锯齿 128ms / 无抗锯齿 31ms,包络带 1.9ms)。
- 列密度自适应:中等密度 2px/列,极密(>8 采样/像素)4px/列。
- 稀疏区间保持逐点折线;附带修复「等间隔抽样漏掉尖峰」。

三、预览曲线像素缓存层(Picture cache + pan roll)
- 45 条曲线画进带 alpha 的 32bpp DIB,正常帧只做一次 AlphaBlend(1~2ms)。
- 纯平移时整行 memmove 一个 dx 并只重画新露出的竖带(与宽缓冲区滚动同构),
  因此拖动视界的每帧代价与信号数无关。
- 内容指纹 = SceneContentHash(含样式/派生参数/网格几何) ⊕ 曲线发布序号;高亮不进指纹。

四、其他热点
- 光标取值胶囊不再逐帧重算并发布 45 条曲线(CurveSpanFor 新增 computeIfMissing),
  只取已覆盖的缓存曲线,未命中退到最新曲线或本帧不标注。
- 样式补丁路径不再同步重建网格图层(宽基底 2 倍宽 × 2 倍高,实测单次 2.2 秒),
  且开启派生功能时直接走整幅重绘(补丁会覆盖缓存中已烘入的曲线像素)。

实测(15 信号合并一条网格 + 全部实验性功能,1600x900 离屏探针):
拖动视界 264→5.9ms,双光标拖动 187→20.9ms,弱峰滑块 555→89ms,
改信号颜色 2661→200ms,悬停面板行 225→38ms,深放大拖动 207→23.8ms。
全屏 1920x1080:拖动视界 9.3ms。

验证:新增离屏探针 p27 8/8(悬停高亮在缓存命中帧可见、平移/缩放/改颜色后的叠加层
与「完全重画」参考帧逐像素一致 diff=0);p20 9/9、p21 9/9、p22 14/14、p23 8/8、p25 12/12;
make release / make debug 零 warning;Release 814,080 B。规则已同步到 Documents/agent_mem.md。
LuChiChick merged commit 30e9d65c30 into main 2026-11-02 11:59:00 +00:00
LuChiChick deleted branch UI_Refactor 2026-11-02 11:59:09 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LuChiChick/Just-DataInspector#2