修复拆分网格误继承实验性功能 / 阶跃线宽 / 面板拖拽残影 / 自由标记卡顿与交互

- 拆分网格:实验性功能只由原网格的首个信号继承(LaneCarry::owner);
  SplitLane 只让第一份继承;新增 PruneLaneSignalItems 按 lane.signals 过滤四类
  逐信号条目(修复"新网格出现别人的固定点、面板里却查不到")
- 阶跃信号:包络带横向膨胀半径 ceil -> floor(1px 线宽不再把竖直沿撑成 3px)
- 浮动面板:面板位置移出 PanelGlobalKey(拖动单个面板不再重画全部面板),
  新增 vacatedRects 让"上一帧被移动/缩小/关闭的面板腾出的区域"也强制重画
- 自由标记:新增 FreeMarkerContents 内容缓存(键不含视界),大量固定点时拖光标/平移不再逐帧采样;
  透明度滑块与光标模式解耦(始终显示);悬停胶囊恢复不透明(瞄着被遮住的数据点时除外);
  胶囊与面板五列按实测文本撑开,不再省略
- 面板交互:折起指示器与信号列表组名同款三角;GX 徽标/标题栏悬停不改光标、移除徽标气泡;
  标题栏双击滚动到该网格(单击不触发,双击不再折起)
- 验证:新增离屏 .preflight/fixcheck 37/37(三处 A/B 均能如实失败);
  回归 round3check 31/31、round4check 34/34、round5check 5/5、editcheck 20/20、convcheck 18/18;
  make release/debug 零警告
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-21 17:21:09 +08:00
1 parent 3400ac7c29
commit 8f735d51be
8 files changed
+654 -198

No files matched your search

+138
View File
@@ -3015,6 +3015,8 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
| 2026-10-21 | DeepSeek-V4.1-Flash(**本机**) | **「自由标记」体验重做 + 全部实验性面板可折起(详见 §12 末尾「2026-10-21 第二轮」与 §11.2 第 194–201 条)**。① **网格内固定点**:取值胶囊互相重叠时做**纵向避让**;✕ 改为**与胶囊同高的正方形**;**删除纵向半透明竖线**;新增「标记透明度」(0–100%,作用于本网格全部固定点:圆点 / 胶囊 / 文字 / ✕);⚠️ **GDI `DrawTextW` 忽略 alpha**,因此半透明时文字改走 **GDI+ `DrawString`**(不透明度 100% 时仍走 `UiText`,像素零变化),实测 0% 时胶囊区域与“无标记”帧逐像素相同(1433→**0**)。② **面板布局重做**:每个有固定点的信号一个**信号色托底分组栏**(三角 + 名称 + `N 标记点` + ✕,文字/三角/✕ 一律用 `UiContrastInk` 自适应对比色,点击分组栏收起该组);条目改成**五列**(取值 / 枚举 / 增量 / 时刻 / 间隔),首项增列与Δt列显示 `N/A`,**整个面板没有枚举文本时枚举列整列缺省**。③ **内容滚动**:内容超高时右侧出现滚动条(分组栏与条目一起滚动),滚轮在内容区翻页;底部按钮与透明度滑块**不参与滚动**。④ **面板折起**:`LanePanelState` 新增 `collapsed`,标题栏 GX 徽标左侧新增「三角 + 横线」指示器(`kEdgePanelDisclosureWidth`),单击折起/展开、双击标题栏切换;折起后只剩标题栏(五类面板全部支持)。验证:新增离屏 `.preflight/perf/mark2check.exe`(**69/69**,含 A/B:恢复竖线 ⇒ 1 项失败;禁用避让 ⇒ 2 项失败);回归 `datacheck` 69/69、`verify` 49/49、`panelanim` 158/158、`errcheck` 19/19;`make release`/`make debug` 零警告;`TargetCheck.exe` 866,816 字节。 |
| 2026-10-21 | DeepSeek-V4.1-Flash(**本机**) | **四处 BUG 修复 + 两处交互改进(详见 §12 末尾「2026-10-21 第三轮」与 §11.2 第 203–212 条)**。① **合并 → 拆分会把一个网格的实验性功能复制到每个拆分结果**(用户:第二个网格也自动启用了功能;「自由标记」还会在新网格里画出**别的信号的固定点**、而面板里查不到):`LaneCarry` 新增 `owner`(只有原网格**首个信号**继承功能状态),`SplitLane` 只让第一份继承;新增 `dmdetail::PruneLaneSignalItems` 在每次重建/拆分后按 `lane.signals` 过滤四类逐信号条目。② **阶跃信号的竖直沿比水平段粗**:`EmitEnvelopeBand` 的横向膨胀半径由 `ceil` 改 `floor`(1px 线宽不再膨胀 1 列)。③ **拖动浮动面板时下方另一个面板出现拖拽残影**:面板位置从 `PanelGlobalKey` 移除(曾让拖动任一面板都重画全部面板,而 MARK 面板的位置漏混 ⇒ 残影),并新增 `vacatedRects`(上一帧被移动/缩小/关闭面板腾出的矩形 → 与之相交的面板强制重画)。④ **大量固定点时拖全局光标/平移卡顿**:新增 `FreeMarkerContents` 内容缓存(取值 + 五列文本 + 信号色,键**不含视界**)。⑤ **面板外观/交互统一**:折起指示器改成与信号列表组名同款三角(去掉横线);GX 徽标与标题栏悬停**不改光标**、徽标气泡移除;标题栏**双击**滚动到该网格(单击不触发,折起只走三角)。⑥ **自由标记**:「标记透明度」滑块与光标模式解耦(始终显示);悬停胶囊恢复不透明(探针瞄着被遮住的数据点时除外);胶囊与面板五列**不再省略**(列宽按实测内容撑开)。验证:新增离屏 `.preflight/fixcheck` **37/37**(三处 A/B:恢复 `ceil` ⇒ 线宽断言失败;恢复 `owner`/`PruneLaneSignalItems` ⇒ 归属断言失败;禁用 `vacatedRects` ⇒ 残影 111px);回归 `round3check` 31/31(更新一条被用户推翻的旧断言)、`round4check` 34/34、`round5check` 5/5、`editcheck` 20/20、`convcheck` 18/18;`make release`/`make debug` 零警告;`TargetCheck.exe` **852,480 字节(832.5 KiB)**。 |
## 11. 跨设备协作须知(起源设备视角)
> **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。
@@ -3948,6 +3950,47 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
202. ⚠️ **固定点取值胶囊的重叠避让只在绘制侧做**(`PaintFreeMarker` 内按已放置矩形纵向错开),
命中矩形(`m_freeMarkerHits`)由绘制时登记 ⇒ 自动跟随避让后的位置,**不要**在命中侧重算。
203. ⚠️⚠️ **实验性功能是「网格级」开关,但只能由该网格的*首个信号*继承**
(`LaneCarry::owner` + `RebuildLanes` + `SplitLane`):`applyCarry` 对
`!carry.matched || !carry.owner` 直接 **return**(保持 `Lane` 默认值 = 功能全关)。
否则「合并网格 → 拆分网格」会把一个网格的功能(以及它的逐信号条目)复制到**每一个**
拆出来的网格上(用户 2026-10-21 报告的“第二个网格也自动启用了实验性功能”)。
204. ⚠️⚠️ **任何“按信号保存的条目”在网格重建/拆分后必须按 `lane.signals` 过滤**
(`dmdetail::PruneLaneSignalItems`:`meanFilterItems` / `edgeCenterItems` / `weakPeakItems` /
`freeMarkerItems` / `freeMarkerCollapsedGroups`)。不过滤的症状:拆分后的网格里**画着别的信号的
固定点**(“野生的镜像标记点”),而浮动面板里又查不到它们(面板只列本网格的信号)。
205. ⚠️⚠️ **包络带的横向(线宽)膨胀半径必须 `floor`、不能 `ceil`**
(`EmitEnvelopeBand` 的 `radius = floor(halfWidth / step)`):`ceil` 会让 1px 线宽
(`halfWidth ≈ 0.5–0.7` 设备像素)也膨胀 1 列 ⇒ 阶跃/方波的**竖直沿 3 列宽(≈3px)**
而水平段只有 ~1.25px,用户看到“竖直跳变的线宽明显大于水平持续段”;
放大到走逐点描边路径后又正常(那条路径线宽天然一致)。修复后实测 1px 线宽:水平 1px / 竖直 1px。
206. ⚠️⚠️ **面板像素缓存:面板位置绝不要混进 `PanelGlobalKey`**。它只影响该面板自己
(`PanelSlotKey` 已混 `layout.panel/title/...`)。曾把 4 类面板的 x/y 混进全局键 ⇒
拖动任一面板都会让**所有**面板重画(15 个 ≈33ms/帧),而「自由标记」面板的位置偏偏漏混
⇒ 拖它时其它面板贴旧像素,正好在下方留**拖拽残影**。
207. ⚠️⚠️ **面板“腾出的区域”必须算脏区**(`PaintEdgeCursor` 的 `vacatedRects`):
上一帧缓存过的面板若本帧移动 / 缩小 / 关闭,它腾出的矩形仍留在**其它面板**的缓存像素里,
只靠“新矩形与谁相交”的级联是漏的(新矩形可能已不与任何人相交)。做法:绘制前预扫描
`m_panelSlots`,与本次 `EdgePanelLayoutFor` 的缓存矩形比对,不相等的旧矩形一律进
`vacatedRects`,与它相交的面板全部强制重画。
208. ⚠️ **`DoubleClickIsPlainPress` 对浮动面板默认返回 true(当作普通按下);例外是标题栏**
(排除 ✕ / GX 徽标 / 折起三角):标题栏双击有“把所属网格滚动到视界内”的语义(与单击 GX 徽标一致),
单击标题栏**不**触发联动(只是准备拖面板),折起/展开只走左侧三角。
209. ⚠️ **面板标题栏悬停保持箭头光标**(只有 `EDGE_PANEL_MOVE` 拖拽中才是 `IDC_SIZEALL`);
GX 徽标悬停同样**不改光标、不弹提示气泡**(高亮保留)。
210. ⚠️⚠️ **「自由标记」的内容(取值 / 五列文本 / 信号色)必须缓存且键**不含视界**:
`PlotView::FreeMarkerContents`(`mutable m_freeMarkerContent`)只混入条目、信号样式、
数据地址与长度。大量固定点时逐帧 `SampleDisplayedSignalAt` + 拼字符串是“拖全局光标 / 平移变卡”
的主因;平移视界、拖光标时缓存必须命中(键里加 ViewMin/ViewMax 就等于没缓存)。
211. ⚠️ **「标记透明度」滑块与光标模式按钮解耦**:滑块行**始终**显示在按钮上方
(`kFreeMarkerSliderRowHeight`,面板高度算式同步);鼠标悬停在某个取值胶囊上时该胶囊恢复
全不透明,但若探针正瞄着**被该胶囊遮住的数据点**(`m_freeMarker.probe` 落在胶囊内)则不恢复
—— 否则无法看出自己瞄到了哪个采样点。
212. ⚠️⚠️ **面板里的数据列不得出现省略号**:五列宽度取「缺省列宽」与
`Lane::freeMarkerColumnWidths`(绘制该面板时把真实文本量一遍存回 `Lane`)的**较大者**,
面板宽度随之撑开;`DT_END_ELLIPSIS` 对“刚好装满”的文本仍可能截断,因此每列额外留 2px 余量。
网格内取值胶囊同理(宽度额外留 12px 余量)。
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
- **不要占用用户的鼠标 / 键盘 / 屏幕**:优先 `.preflight/uitest`(离屏 harness:`SW_HIDE` 窗口 + 直接注入消息 + `BitBlt` 从 `m_backDc` 截图)或 `.preflight/perf`(离屏计时 / 逐像素对比)。
@@ -7429,3 +7472,98 @@ DG3 data=0 cycle=0 (空组,一个字符串通道)
---
## 本轮(2026-10-21 第三轮,本机):拆分网格功能归属 + 阶跃线宽 + 面板残影 + 自由标记性能/交互
> 用户需求(逐条):
> ① **BUG**:两个信号独立网格 → 给其中一个开实验性功能 → 「合并网格」→「拆分网格」后**第二个网格也
> 开启了实验性功能**;「自由标记」更严重:合并再拆分后第二个网格上出现**野生的固定点**
> (第一个网格标记点的镜像),但新网格的浮动面板里又没有这些点的信息。
> ② **BUG**:`TestData/SCATTER_TEST.csv` 的 step 信号在默认 1px 线宽下,**垂直阶跃段的线宽明显
> 大于水平持续段**;把跳变时刻放大后线宽又正常。
> ③ **BUG**:拖动一个浮动面板经过另一个浮动面板时,下方/其它面板上留下**拖拽残影**。
> ④ **BUG**:自由标记打上大量固定点后,左右拖动全局时间光标变卡(移动视界/滚动网格偶发卡顿)。
> ⑤ **改进**:所有浮动面板的折起指示器与信号列表组名的折叠三角**同款**;GX 徽标悬停**不改光标**、
> 不显示"是否在视界内"的浮动提示;标题栏**双击**把目标网格滚动到视界内(单击不触发);
> 标题栏悬停不改光标,只有按下左键拖动时才是十字箭头。
> ⑥ **改进**:标记透明度独立于光标模式按钮(始终显示);悬停某个固定点的数值显示时该显示恢复全不透明
> (但若鼠标正瞄准被它遮住的数据点则不恢复);固定点数值与面板数据列**不出现省略号**。
### A. 拆分网格:实验性功能只属于"首个信号"(§11.2 第 203–204 条)
- 根因两处:`RebuildLanes` 把"开着"的面板状态**按信号逐个**继承(`inheritedCarry(ref)`),而合并后的
网格里每条信号都指向同一份状态 ⇒ 拆回每信号一格时**每一格**都带着功能;`SplitLane` 则直接把
源网格的 `*.open` 复制给每一份。
- 修法:`LaneCarry` 新增 `owner`,只有 `lane.signals.front()` 为 true;`applyCarry` 对
`!carry.matched || !carry.owner` **直接 return**(保持默认 = 功能全关)。`SplitLane` 只让
`signalSlot == 0` 的那一份继承(并把 `freeMarker / freeMarkerCursor / freeMarkerPanel /
freeMarkerOpacity` 一并补上——旧实现漏了自由标记)。
- "野生标记点"的真正来源:条目是**按网格整体**携带的,拆分后每份都拿到了全部 `freeMarkerItems`。
新增 `dmdetail::PruneLaneSignalItems`(按 `lane.signals` 过滤四类逐信号条目 + 收起分组),
在 `RebuildLanes` 末尾与 `SplitLane` 的每一份上都调用。
### B. 阶跃信号的竖直沿比水平段粗(§11.2 第 205 条)
- 仅发生在**包络带路径**(可见采样数 > 绘图区宽度)。根因:横向膨胀半径用
`ceil(halfWidth / step)`,而 1px 线宽的 `strokeHalf = 1 × m_scale × 0.5 ≈ 0.625` 设备像素
⇒ `ceil` 得到 1 列 ⇒ 竖直沿从"1 个像素列"变成 3 列(≈3px),水平段却只有 ~1.25px。
放大到走逐点描边路径(线宽天然一致)后就"正常"了——与用户描述完全吻合。
- 修法:`radius = floor(halfWidth / step)`(1px 不膨胀;≥2 列宽仍按整数列膨胀,10px 仍被撑粗)。
实测(125% 缩放、1px 线宽):**水平 1px / 竖直 1px**(改前 1 / 2)。
### C. 面板拖拽残影(§11.2 第 206–207 条)
- 根因链:`PanelGlobalKey` 里混了 4 类面板的 `x/y`(拖动任一面板 ⇒ 所有面板重画,15 个 ≈33ms/帧),
但**漏了 `freeMarkerPanel`**;于是拖「自由标记」面板时其它面板照旧贴旧像素,而它们上一帧的缓存里
含被拖面板的图像 ⇒ 残影。另外只靠"新矩形与谁相交"的级联重画是漏的:面板可能已移到**不与任何
面板相交**的位置,而别处的缓存像素里还留着它的旧图像。
- 修法两条:① 面板位置**从 `PanelGlobalKey` 移除**(它只影响自己,`PanelSlotKey` 已含
`layout.panel`),只保留 `open / collapsed` 与 `edgeValue`;② 绘制前预扫描 `m_panelSlots`,
与本帧布局的缓存矩形比较,不相等(或被移除)的旧矩形进 `vacatedRects`,任何与之相交的面板强制重画。
- 附带收益:拖动单个面板不再重画全部面板(原来 15 个 ≈33ms/帧)。
### D. 大量固定点时的卡顿(§11.2 第 210 条)
- 每个固定点每帧都要 `ResolveSignal` ×2 + `SampleDisplayedSignalAt` ×2 + 拼 5 段文本 + 量宽度,
而拖光标/平移时这些值**根本没变**。新增 `PlotView::FreeMarkerContents(laneIndex)`(`mutable` 缓存),
条目 `FreeMarkerContentEntry{capsuleText, valueText, enumText, deltaText, timeText,
deltaTimeText, color, value, valid}`;键只混"条目 + 样式 + 数据地址/长度",**不含视界与鼠标**。
网格内胶囊与面板五列共用它(`FreeMarkerLabelRects` 也改读缓存)。
### E. 面板外观 / 交互(§11.2 第 208–209 条)
- 折起指示器:改成与 `SignalPanel` 组名**完全相同**的 `UiDrawTriangleIcon`(12 逻辑像素、悬停变 accent),
去掉三角后方那条短横线。
- `QueryCursor`:GX 徽标与标题栏悬停都返回 `nullptr`(箭头);`EDGE_PANEL_MOVE` 拖拽中仍是 `IDC_SIZEALL`。
- 删除 `PaintPanelBadgeTooltip`(含声明)——徽标只保留高亮,不再弹"已在视界内 / 点击滚动到该网格"。
- `OnDoubleClick`:标题栏(排除 ✕ / 徽标 / 三角)改成 `ScrollLaneIntoView` + 提示;
`DoubleClickIsPlainPress` 对面板默认 `true`,**只有标题栏**返回 false(否则双击永远到不了 `OnDoubleClick`)。
### F. 自由标记:透明度 / 悬停恢复 / 不省略(§11.2 第 211–212 条)
- 「标记透明度」滑块行**始终**显示(面板高度算式同步去掉 `freeMarkerCursor` 条件)。
- 悬停取值胶囊 ⇒ 该胶囊 `alpha = 1`;例外:`m_freeMarker.valid` 且其 `(x, y)` 落在胶囊矩形内
(正瞄着被遮住的数据点)时保持原透明度。
- 不省略:`Lane::freeMarkerColumnWidths[5]`(绘制该面板时按真实文本量宽度存回 `Lane`),列宽取
「缺省列宽」与它的大者、面板宽度随之撑开、每列多留 2px;胶囊宽度余量 10 → 12px。
### G. 验证(全离屏,未占用鼠标/键盘/屏幕)
- 新增 `.preflight/fixcheck`(`build.ps1` + `main.cpp`)**37/37**:
[A] 合并→拆分的功能归属 + "没有别人的固定点";[B] 阶跃线宽(水平 vs 竖直,跳过图例色块);
[D/E] 内容缓存命中(平移视界键不变 / 改样式键失效)、滑块始终存在且在按钮上方、
列宽 ≥ 实测文本、悬停恢复不透明、探针遮挡时保持半透明;
[C] 面板残影(拖动中的一帧 vs 作废面板图层后的重画,逐像素);
[F] 徽标/标题光标、单击不滚动、双击滚动、双击不折起。
- **A/B(判别力已证)**:`ceil` 还原 ⇒ 竖直 2px vs 水平 1px ⇒ 1 项 FAIL;`carry.owner` 还原 ⇒
功能归属断言 FAIL;禁用 `vacatedRects` ⇒ 残影 111 像素 ⇒ FAIL。恢复后 37/37。
- 回归:`round3check` **31/31**(其中「徽标显示手型光标」是用户本轮明确推翻的旧规则,已改为箭头)、
`round4check` 34/34、`round5check` 5/5(1px→2px、10px→26px 仍成立)、`editcheck` 20/20、
`convcheck` 18/18。
- `make release` / `make debug` **零警告**;`TargetCheck.exe` **852,480 字节(832.5 KiB)**。
- ⚠️ 探针坑:`Grab` 用 `GetDIBits(app.m_backDc, app.m_backBitmap, …)`;残影只存在于"另一块面板的
**缓存矩形**"里,比较区域要取两个缓存矩形的交集(面板主体不透明会盖住大半残影);直接改
`lane.*Panel.x/y` 就等价于"拖动中的某一帧"(走鼠标注入会先 `BringPanelToFront` 并改全局键,
那一帧所有面板都重画,反而看不到残影)。