边沿计数改用「绘制值」+ 派生信号烘入时间偏移 + 图例托底统一为 GX 徽标款 + 调整二级面板向上展开
一、两个「边沿计数」按「画出来的曲线」判定(原先按原始采样值 → 应用过「调整」的信号恒为 0)
- 用户复述的权威定义:设定一个横向(Y 轴)光标,信号绘制出来的曲线从下往上穿过该幅值 = 上升沿,
从上往下穿过 = 下降沿。
- 症状:VERY_BIG_DATA 的「编码器B相」设系数 50 + 时间轴偏移 -20s,把光标拖到画面中间,
很规整的方波却统计出 0/0。
- 根因:EdgeCountsFor / EdgeCenterCountsFor 只比较 signal.values 的原始采样值,而横向光标读的是
屏幕上的值(已乘系数、已加偏移)→ 原始值域与阈值完全不相交。
- 修法:判定值一律取 ApplyDrawTransform(style, values[SourceIndexFor(style, count, index)]);
* 中心轴两侧必须套用同一套变换(drawn - drawnCenter);
* 水平镜像时曲线数组按源下标存储、而统计区间是显示下标 → 请求曲线窗口前必须把
[needFirst, needEnd) 映射为 [count-needEnd, count-needFirst);
* 缓存必须带样式指纹:EdgeCountEntry / EdgeCenterCountEntry 新增 styleKey
(EdgeStyleKey():gain/offsetY 按 1e6 定点 + absolute/mirrorVertical/mirrorHorizontal 位),
否则改完「调整」仍会命中变换前的旧计数。
二、时间区间之外的采样不得计入(区间边界点)
- 症状:A 在阈值以下、B 超过阈值;双光标下一个光标正好在 B 点,旧实现把 B 算成一个上升沿。
- 根因:EdgeIndexRange 在左端多取了一个采样点(if (begin > 0) --begin;)。
- 修法:删掉该外扩,收紧为「只有两个端点都落在 [begin, end) 内的采样对才计入」
(begin = lower_bound(rangeMin)、end = upper_bound(rangeMax),调用方从 begin+1 枚举到 end-1)。
- 结果:把区间起点从 times[crossing-1] 移到 times[crossing] 恰好少一个上升沿。
三、派生信号把 offsetTime 也烘进采样值(推翻 §9.19 的「唯一例外」)
- 用户要求:新信号默认不启用任何「调整」时就必须与网格里的预览逐点重合。
- 修法(Application::BuildDerivedSignal,在「值变换 + 水平镜像反序」之后追加一步):
新信号第 i 个采样 = 已变换的曲线在 time[i] - offsetTime 处的取值(取最近的采样点);
单调时间轴用只前进的游标(整段 O(n));平移后越界的采样置 NaN(与预览一致);
新信号样式全部保持默认(gain=1 / absolute=false / 两个镜像=false / offsetY=0 / offsetTime=0)。
- 退化路径:时间轴非单调或时间列缺失时无法按下标推进取样,退回把 offsetTime 带在样式上
(两条路径的显示效果相同)。
四、图例托底统一为 GX 编号徽标同款 + 新增更小字体 uiTiny
- 托底高度 = kLaneBadgeHeight、上下边界与 LaneBadgeRect() 完全一致、圆角 = 高度的一半(胶囊形)、
配色与描边与徽标相同(选中态 accentSoft/accent)、水平内边距 = 胶囊半径。
- 文本改用新增字体 UiFonts::uiTiny(10 逻辑 px):原 uiSmall(12px) 几乎顶满 17px 高的胶囊。
- LegendItem 拆出 plateWidth(托底宽 = 内容宽 + 2×内边距),条目步进 = plateWidth + kLegendItemGap;
kLegendPlateRadius 已删除(半径一律由徽标高度推出)。
五、「系数/偏移」「镜像」二级面板改为向上展开
- PopupMenu::Open(..., bool expandUp = false):true 时 (x, y) 是菜单的左下角、菜单向上展开;
BuildLayout 增加同参数并记录 m_buildExpandUp(RebuildLayout 必须复用,否则数值回写触发的重排
会让面板跳回向下展开)。
- SignalPanel::OpenGainMenu / OpenMirrorMenu:锚点 = (button.X, button.Y - kAdjustMenuGap),
即「面板左下角对准按钮左上角、再略微上去一点」(kAdjustMenuGap = 4 逻辑像素)。
六、验证(全部离屏,不占用用户的鼠标 / 键盘 / 屏幕)
- .preflight/uitest 新增 [AR]:
* 边沿计数 plain / gain=50 / offsetY / mirrorVertical / mirrorHorizontal 逐个与「独立参考实现」
逐位一致;offsetY 用例给出 A/B:按原始采样值算会得到 7/6 个边沿(正是用户报告的缺陷);
* 区间边界:起点从 crossing-1 移到 crossing 恰好少一个上升沿;
* 中心轴:absolute + offsetY 的样式下与参考实现逐位一致(32234 个边沿量级);
* 派生信号:无偏移时全部 128 万采样逐点等于 transform(滤波);带 +10 个平均采样周期的偏移时,
抽样点等于 transform(滤波) 在 time[i] - offsetTime(最近采样)处的取值;样式全默认;
* 图例托底:height == 徽标高度、Y == 徽标 Y、radius == height/2、色块与文本都在托底之内;
* 调整面板:菜单左边界 == 按钮左边界、菜单底 == 按钮顶 - 4*scale、且不覆盖按钮(两个按钮各测一次);
* GX 拖拽让位:±0.6H 各让位 1 格(目标 0 / 3),±0.4H 与恰好 ±0.5H 都不让位(严格「超过」)。
- 同步修正 harness 里两处「旧规则」断言(否则必然 FAIL 并误导后来者):
[AP] 「派生信号继承调整变换」→ 「样式默认 + 采样值按 time[i]-offsetTime 烘焙」;
[AN] 「图例带无托底(网格线可透出)」→ 「装饰矩形逐块、无一块跨越整条带、未被托底覆盖的列
仍能看到底层内容」。
- 合计 722/722 通过;make release 零警告(本机 GCC 15.2.0 UCRT;Target.exe 被占用时链接会报
Permission denied,已用临时 -o 名字验证链接正确性)。
规则同步:Documents/agent_mem.md 新增 §9.21,§9.19 的「offsetTime 仍需继承」标注为已作废,
新增 §11.2 第 21–25 条硬约束,并补变更日志一行。
This commit is contained in:
1 parent
1b3ab4ac09
commit
a2a38236fe
9 files changed
+434
-118
No files matched your search
+129
-9
@@ -735,11 +735,14 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
3. **水平镜像 = 数据反序**:把派生采样数组整体反转(`ApplyDrawTransform` 是逐点函数,
|
||||
"先变换再反转"与"先反转再变换"等价);
|
||||
4. 新信号样式一律**保持默认**(`gain=1 / absolute=false / 两个镜像=false / offsetY=0`)。
|
||||
- ⚠️ **唯一例外:时间轴偏移 `offsetTime` 仍由样式携带**——它改变的是"绘制在哪个时刻",
|
||||
而派生信号与源信号**共用同一个文件时间轴**,无法把时间平移烘进采样数据;
|
||||
若不继承,新信号会相对预览整体横向错位。已在该函数注释中写明。
|
||||
- 验证(本轮 `verify` harness):`derived.values[i] == ApplyDrawTransform(tf, float(source[count-1-i]))`
|
||||
逐点一致;新样式各项均为默认;`offsetTime` 保留。
|
||||
- ⚠️⚠️ **【已作废,见 §9.21 第三节】原"唯一例外:时间轴偏移 `offsetTime` 仍由样式携带"** ——
|
||||
用户明确要求"新信号**默认不启用任何「调整」**就必须与预览逐点重合",因此 `offsetTime` 现在也
|
||||
**按 `time[i] - offsetTime` 重采样烘进采样值**(新信号 `offsetTime` 同样保持 0);
|
||||
只有"无法烘值"的退化情形(时间轴非单调 / 时间列缺失)才退回把 `offsetTime` 带在样式上。
|
||||
仅作历史保留:~~它改变的是"绘制在哪个时刻",而派生信号与源信号共用同一个文件时间轴,
|
||||
无法把时间平移烘进采样数据;若不继承,新信号会相对预览整体横向错位。~~
|
||||
- 验证(`1b3ab4a` 轮 `verify` harness):`derived.values[i] == ApplyDrawTransform(tf, float(source[count-1-i]))`
|
||||
逐点一致;新样式各项均为默认。
|
||||
|
||||
#### 四、网格 GX 拖拽让位阈值 + 徽标悬停
|
||||
|
||||
@@ -897,6 +900,104 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
|
||||
---
|
||||
|
||||
### 9.21 边沿计数改用「绘制值」+ 派生信号烘入时间偏移 + 图例托底统一为 GX 徽标款 + 调整二级面板向上展开(本轮)
|
||||
|
||||
> 接手背景:基于 `1b3ab4a`。本轮修的四处都是**交互/统计语义**问题,另有**一处推翻了 §9.19 的结论**
|
||||
>("派生信号仅 `offsetTime` 仍需继承" —— **已作废**,见下)。
|
||||
|
||||
#### 一、两个「边沿计数」必须按**画出来的曲线**判定(原实现按原始采样值 → 恒为 0)
|
||||
|
||||
- **定义(用户明确复述,作为权威)**:设定一个横向(Y 轴)光标,信号**绘制出来的曲线**
|
||||
从**下往上**穿过该幅值 = **上升沿**;从**上往下**穿过 = **下降沿**。
|
||||
- **症状**:对应用过「调整」的曲线统计恒为 0。例:`VERY_BIG_DATA.CSV` 的「编码器B相」设
|
||||
系数 50、时间轴偏移 -20s,把横向光标拖到画面中间(方波很规整),**上升沿/下降沿都是 0**。
|
||||
- **根因**:`EdgeCountsFor` / `EdgeCenterCountsFor` 只比较 **`signal.values` 的原始采样值**,
|
||||
而横向光标读的是**屏幕上的值**(已乘系数、已加偏移)→ 原始值域与阈值完全不相交。
|
||||
- **修法**:判定值一律取**绘制值**
|
||||
`ApplyDrawTransform(style, values[SourceIndexFor(style, count, index)])`;
|
||||
* 中心轴版同构:两侧都套用同一套变换(`drawn - drawnCenter`),否则比较的不是屏幕上的两条曲线;
|
||||
* **水平镜像**时曲线数组按**源下标**存储、而区间是**显示下标** → 请求曲线窗口前必须把
|
||||
`[needFirst, needEnd)` 映射为 `[count-needEnd, count-needFirst)`,否则取到完全无关的一段。
|
||||
- ⚠️ **缓存必须带样式指纹**:`EdgeCountEntry` / `EdgeCenterCountEntry` 新增 `styleKey`
|
||||
(`EdgeStyleKey()`:`gain`/`offsetY` 按 1e6 定点 + `absolute`/`mirrorVertical`/`mirrorHorizontal` 位),
|
||||
否则改完「调整」后仍会命中变换前的旧计数。
|
||||
|
||||
#### 二、时间区间**之外**的采样不得计入(区间边界点)
|
||||
|
||||
- **症状(用户报告)**:A 在阈值以下、B 超过阈值、C 比 B 高;双光标下**一个光标正好在 B 点**、
|
||||
另一个在 C 之后 → 计数区间应是"B 之后",**B 本身不该算作一个上升沿**,但旧实现把它算进去了。
|
||||
- **根因**:`EdgeIndexRange`(时间轴单调时的下标区间)在**左端多取了一个采样点**
|
||||
(`if (begin > 0) --begin;`,注释写着"使跨越区间边界的边沿也能被计入")→ 采样对 `(A, B)`
|
||||
的两个端点里有一个在选区之外,却被计入。
|
||||
- **修法**:**删掉该外扩**,并把语义收紧为「**只有两个端点都落在 `[begin, end)` 内的采样对**
|
||||
才计入」(`begin = lower_bound(rangeMin)`、`end = upper_bound(rangeMax)`,调用方从 `begin + 1`
|
||||
枚举到 `end - 1`;增量路径的差集公式不变)。非单调时间轴的逐点 NaN 过滤本来就是这个语义。
|
||||
- 结果:把区间起点从 `times[crossing-1]` 移到 `times[crossing]` 恰好**少一个上升沿**(已断言)。
|
||||
|
||||
#### 三、派生信号把 `offsetTime` **也烘进采样值**(**推翻 §9.19 的"仍需继承"**)
|
||||
|
||||
- **用户要求原文**:"派生信号与显示的预览信号需要相同,且烘焙出来的新信号**默认不启用任何「调整」**
|
||||
时就应该与预览时相同"。→ §9.19 写的"时间轴偏移是唯一的例外,仍由新信号的样式携带"**已作废**。
|
||||
- **修法**(`Application::BuildDerivedSignal`,在原有「值变换 + 水平镜像反序」之后追加一步):
|
||||
* 新信号第 i 个采样 = **预览曲线在 `time[i]` 时刻显示的值**
|
||||
= 已变换的曲线在 `time[i] - offsetTime` 处的取值(**取最近的采样点**);
|
||||
* 单调时间轴的 `target = time[i] - offsetTime` 随 i 单调不减 → 用**只前进的游标**,整段 O(n);
|
||||
* 平移后越界的采样置 **NaN**(与预览"该处没有曲线"一致);
|
||||
* 新信号样式**全部保持默认**(`gain=1 / absolute=false / 两个镜像=false / offsetY=0 / offsetTime=0`)。
|
||||
- ⚠️ **退化路径**:时间轴非单调或时间列缺失时无法按下标推进取样 → 退回"把 `offsetTime` 带在
|
||||
新信号样式上"(两条路径的**显示效果相同**,只是一条把偏移留在样式里)。
|
||||
- 代价:整段重采样需要一份等长临时缓冲(千万级采样 ≈ 数十 MB),用完即释放;属一次性构建操作。
|
||||
|
||||
#### 四、图例托底统一为 **GX 编号徽标同款**(并新增更小的 `uiTiny` 字体)
|
||||
|
||||
- **用户要求**:"修改图例的托底为 GX 编号同款,大概可以直接复用 GX 编号的样式,只不过「GX」部分
|
||||
变成图例色块和文本;注意现在的图例文本高度太大了可能放不下 GX 标签同款的托底,这个需要调整
|
||||
字符大小,并且左右边距和上下边距与 GX 标签的样式相同"。
|
||||
- **实现**(`PaintLaneLegend`):
|
||||
* 托底**高度 = `kLaneBadgeHeight`**、上下边界与 `LaneBadgeRect()` **完全一致**(同一 legendBand 内垂直居中);
|
||||
* 圆角 = **高度的一半**(胶囊形,与徽标的 `badgeHeight * 0.5f` 一致);配色/描边与徽标相同
|
||||
(`cardBg`/`border`,选中态 `accentSoft`/`accent`);
|
||||
* **水平内边距 = 胶囊半径**(小于半径时内容会溢出圆角之外);
|
||||
* 文本改用**新增字体 `UiFonts::uiTiny`(10 逻辑 px)**:原 `uiSmall`(12px)几乎顶满 17px 高的胶囊;
|
||||
* `LegendItem` 拆出 `plateWidth`(= 内容宽 + 2×内边距),条目步进 = `plateWidth + kLegendItemGap`;
|
||||
`kLegendPlateRadius` **已删除**(半径一律由徽标高度推出)。
|
||||
- ⚠️ `LegendHit.rect` **同时是命中矩形与悬停重绘的托底** —— 看到的必须严格等于点得到的;
|
||||
`LegendHit` 里保存的 `swatch/text/label/颜色/radius` 也必须与绘制用**同一套**(悬停时在不透明托底上原样重绘)。
|
||||
|
||||
#### 五、「系数/偏移」「镜像」二级面板改为**向上展开**
|
||||
|
||||
- `PopupMenu::Open(scope, bounds, x, y, items, bool expandUp = false)` 新增末位参数:
|
||||
`expandUp = true` 时 `(x, y)` 是菜单的**左下角**、菜单**向上展开**(默认仍是原来的"向右下展开、
|
||||
`(x, y)` = 左上角");`BuildLayout` 增加同参数并记录 `m_buildExpandUp`(`RebuildLayout` 复用它,
|
||||
否则数值回写导致的重新排布会让面板跳回向下展开)。
|
||||
- `SignalPanel::OpenGainMenu` / `OpenMirrorMenu`:锚点 = `(button.X, button.Y - kAdjustMenuGap)`
|
||||
(新增常量 `kAdjustMenuGap = 4` 逻辑像素)→ **面板左下角对准按钮左上角、再略微上去一点**;
|
||||
超出窗口时仍会向左翻转并做边界收敛。
|
||||
|
||||
#### 六、验证(全部离屏,不占用鼠标/键盘/屏幕)
|
||||
|
||||
- `.preflight/uitest`(窗口 `SW_HIDE` + 直接注入消息 + 从 `m_backDc` 截图)新增 `[AR]`:
|
||||
* 边沿计数:`plain` / `gain=50`(阈值在**绘制刻度**上)/ `offsetY`(整条曲线被抬到光标之上 → 0 个边沿)/
|
||||
`mirrorVertical` / `mirrorHorizontal` **逐个与独立参考实现(按定义逐点重算)逐位一致**;
|
||||
`offsetY` 用例同时给出 A/B:**按原始采样值算会得到 7/6 个边沿**(正是用户报的缺陷);
|
||||
* 区间边界:把区间起点从 `crossing-1` 移到 `crossing` **恰好少一个上升沿**,且与参考实现一致;
|
||||
* 中心轴:`absolute + offsetY` 的样式下与参考实现**逐位一致**(32234 个边沿量级);
|
||||
* 派生信号:无偏移时**全部 128 万采样**逐点等于 `transform(滤波)`;带 +10 个平均采样周期的偏移时,
|
||||
抽样点等于 `transform(滤波)` 在 `time[i] - offsetTime`(最近采样)处的取值;**样式全默认**;
|
||||
* 图例托底:`height == 徽标高度`、`Y == 徽标 Y`、`radius == height/2`、色块/文本都在托底之内;
|
||||
* 调整面板:菜单左边界 == 按钮左边界、`菜单底 == 按钮顶 - 4*scale`、且**不覆盖按钮**(两个按钮各测一次);
|
||||
* GX 拖拽:`±0.6H` 分别让位 1 格(目标 0 / 3),`±0.4H` 与**恰好 ±0.5H 都不让位**(严格"超过")。
|
||||
- ⚠️ **同时把 harness 里两处"旧规则"断言改成新规则**(否则它们必然 FAIL,且会误导后来者):
|
||||
`[AP]` 原断言"派生信号继承调整变换(与预览一致)"→ 改为「样式默认 + 采样值按 `time[i]-offsetTime` 烘焙」;
|
||||
`[AN]` 原断言"图例带无托底(网格线可透出)"→ 改为「装饰矩形**逐块**、无一块跨越整条带、
|
||||
未被托底覆盖的列仍能看到底层内容(非网格底色像素 > 0)」。
|
||||
- 合计 **722/722 通过**;`make release` 零警告。
|
||||
- ⚠️ **本机提交时 `Target.exe` 常被用户占用**(链接报 `cannot open output file ... Permission denied`):
|
||||
编译不受影响,**不要 `make clean`**、也不要杀用户进程;用临时 `-o Build/Release/Target_check.exe`
|
||||
手工链接一次即可验证链接正确性。
|
||||
|
||||
---
|
||||
|
||||
## 10. 变更日志(本文件维护记录)
|
||||
|
||||
| 日期 | 设备/模型 | 摘要 |
|
||||
@@ -929,7 +1030,8 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
| 2026-10-13 | DeepSeek-V4.1-Flash(**另一台设备**) | **交互流畅性三项修复**(详见 §9.17):① **光标不再跟随视界**——删除 `ClampCursorsAfterPan()` 及其在平移路径(中键/左键拖拽、Shift+滚轮、方向键)的三处调用,视图变化时光标时间位置**完全不变**(可滑出视口),`ClampCursorToView()` 只在拖拽光标时使用(mode 14:平移 20 帧后 `CursorTime` 不变且 `offViewport=1`)。② **缩放首帧不再卡顿**——根因是跨度变化时 `Paint` 会**同步重建全部 5 个网格图层**(实测 70~80ms)+ 随后整幅结算(40ms)=单次缩放 ~110ms 卡顿;修法:`BlitWideBuffer` 对跨度过期的图层改用**旧像素 `StretchBlt` 拉伸占位**(不再跳过导致空白)、`Paint` 只在"图层数量不符"时同步重建(新增 `LayersAllocated()`)、`RenderPendingLaneRebuilds` 允许按新跨度重建、且**只在视界稳定后**才花预算精细重绘(`viewStopped` 门控);实测首帧 **70~80ms → ~10ms**。保留"结算"帧(实测宽基底路径与整幅重绘有 17 万+ 像素列相位差,非同源)。③ **超大信号拖预览滑块**——量化发现开销**不在绘制**(`PaintLaneDerivedOverlays` 仅 0.375ms),而在"参数每帧变化"触发的**整条信号**(2576 万采样)预览重算调度;新增 `kHugeSignalPreviewSamples=4000000`,超大信号拖拽期间不再每帧排队重算(15.0→13.7ms,**剩余 ~8ms 根因未完全定位**)。验证:`pancheck` 7 场景全 `dy=0`、`zoomcheck` mode 0/13/14、`make clean && make all` 零警告。 |
|
||||
| 2026-10-13 | DeepSeek-V4.1-Flash(**另一台设备**) | **深放大中键拖拽卡顿(根因=GDI+ 抗锯齿"满幅锯齿"病态)+ 横向补条裁剪 + 并行网格图层(B/C,详见 §9.18)**。① **B 拆解**:该视界(`[10.2719,10.2831]s`)下 `RollWideBuffer` 补条重绘 **13.08ms**,其余 ≤0.8ms —— 瓶颈全在补条重绘。② **补条裁剪**:横向补条只覆盖窄竖带,却按整条宽窗口构建/描边整段折线;改为按条带 x 范围裁剪采样区间(旁路折线缓存),`RollWideBuffer` **13.08ms → 1.73ms**,拖拽整帧 13~24ms → ~3ms,且**逐像素 0 差异**。③ **病态根因**:拖拽超出预渲染余量时走整块重画,**整宽单网格渲染 655ms** —— 同 4481 点 `DrawLines` 实测 **AA 694ms / 无 AA 29ms(23 倍)**(编码器方波在 1~2 采样/像素形成每列整幅跳变的锯齿,原实现此时既不走包络也不关 AA);修法:**包络带阈值从 2 采样/像素降到 1**,并扩展"梳状(关 AA)"判据(多数列列内跨幅 >0.5×网格高 / 相邻列整幅跳变)——**几何一字未改**;整宽渲染 **655→47.7ms**、补条 6.9→2.4ms、拖拽 200 帧 **avg 4.5 / max 5.8ms / 0 帧 >8ms**。④ **C(用户解除多线程限制)**:实现 `RebuildLaneLayersParallel` —— 每线程一个**隔离 `PlotView`**(自有 `m_workerTab`/`m_area`/`m_plotRect`/`m_lanes`)渲染不同网格,主线程 join 后按真实下标重算 key;并行 vs 顺序**逐像素 0 差异**;⚠️ 踩坑:`BuildLaneVisuals` 在 `m_tab==nullptr` 时清空 `m_lanes`,故**必须给 worker 一个非空的自有 tab**。**诚实结论:并行仅 1.03x**(同机 4 线程 CPU 真并行 3.98x → GDI+ 光栅化被串行化),故**并行对光栅化无收益**,仍保持"视界停后每帧补 1 个网格"。验证:`dragperf` mode 0/1/3/5/6/8/9、`pancheck` 全 `dy=0`、`zoomcheck` 无回归、`make clean && make all` 零警告。 |
|
||||
| 2026-10-14 | DeepSeek-V4.1-Flash | **装饰层改为带 alpha 的独立图层(消除 GX/图例外围"直角白边")+ 图例圆角托底与悬停/选中配色统一 + 预览曲线内存释放(详见 §9.20)**。① **白边根因**:装饰层原为"从后备缓冲区抓取一块**不透明矩形**",圆角之外(属于波形)的像素被一起烘进缓存,为让其跨视界可复用又在圆角外铺了直角不透明底色 ⇒ 密集波形上就是一圈直角白边。**修法**:`m_decorDc` 改 **32bpp DIBSection**、新增 `RenderDecorCache()` 把装饰**单独绘制**进全透明位图(不再抓取)、`BlitDecorCache()` 改用 **`AlphaBlend`**(`AC_SRC_ALPHA`,`GetProcAddress(gdi32!GdiAlphaBlend)` 解析、不新增链接依赖)⇒ 圆角外透出**当前**波形;并做 **alpha 修补**(GDI 文本会把写入像素的 alpha 清 0,不修补文字会变透明洞);删除全部外扩直角底色。② **图例**:逐条目圆角托底(与 GX 徽标同风格),**选中态 = 悬停态配色**(`accentSoft`+`accent`+`textStrong`),悬停在不透明托底上重绘色块/文字并保留曲线光晕;`LegendHit` 新增 `radius`;补齐「+N」/「(无可见信号)」提示的装饰矩形。③ **内存**:`thread_local` 工作缓冲**保留容量封顶**(`kReuseBufferMaxElements` + `ReuseBufferScope`,超大计算用完即释放)+ 关闭最后一个预览功能时 `TrimDerivedCurveDataIfIdle()`(`PauseDerivedAsync` 让线程退出、thread_local 析构归还内存)—— 实测 1792MB → **关闭后 1203MB**(修复前不下降)。④ 性能:`BlitDecorCache` **0.19ms/帧**、`RenderDecorCache` 2.4ms(仅内容变化时);`zoomcheck` mode 13 收敛帧与整幅重绘**逐像素 0 差异**(此前 3503)。验证:离屏 `verify.exe` **49/49 通过**、`make release` 零警告。 |
|
||||
| 2026-10-14 | DeepSeek-V4.1-Flash | **修复远端「调整」功能引入的性能回归 + 根治首帧卡顿 + 图例交互/托底重做(详见 §9.19)**。① **8 倍回归**:远端把 `DataSet::Style(ref)` 写进了 `AutoRangeLaneToView` 的逐采样循环(亿级解析调用)→ 工具栏「Y 轴自适应」在全视图下 **1200ms**(旧提交 147ms);修法:样式指针提循环外 + **整段自适应取已缓存 min/max(O(1),结果逐位一致)** + 无变换走紧凑循环 → **0.02ms**。② **首帧卡顿根治**:加载后宽基底图层从未分配,用户第一次平移/缩放时 `Paint` 在那一帧**同步重建全部 5 个网格图层**(首帧 **64.3 → 13.0ms**);修法:新增 `WarmLaneLayersStep()`,在**命中静态场景缓存**(视界未变、非拖拽)时用预算机制**每帧至多补 1 个网格**,把整批重建摊到静止帧。③ **派生信号把「调整」烘进数据**(不再继承样式指纹),仅 `offsetTime` 因共用文件时间轴仍需继承。④ **GX 拖拽让位判据改为鼠标指针**(鼠标越过相邻网格中心水平线即让位),并给 GX 徽标加悬停高亮(叠加层 + `IDC_HAND`,与信号列表 GX 徽标一致)。⑤ **图例命中矩形在装饰缓存路径下必须重建**(原先命中缓存时提前 return → `m_legendHits` 被清空且不重建 → 中键平移后图例悬停/点选全失效)。⑥ **每个图例条目加圆角托底 + 装饰矩形改为逐块 + 托底不透明铺满外接矩形** → 消除「GX/图例周边旧波形像素残留」(逐像素验证装饰矩形内 **0 差异**);悬停高亮为不透明托底并**重绘色块与文字**(否则内容被盖住)。验证:离屏 Application harness **40/40 通过**,`zoomcheck` mode 20–26 量化,`make release` 零警告。 |
|
||||
| 2026-10-14 | DeepSeek-V4.1-Flash | **修复远端「调整」功能引入的性能回归 + 根治首帧卡顿 + 图例交互/托底重做(详见 §9.19)**。① **8 倍回归**:远端把 `DataSet::Style(ref)` 写进了 `AutoRangeLaneToView` 的逐采样循环(亿级解析调用)→ 工具栏「Y 轴自适应」在全视图下 **1200ms**(旧提交 147ms);修法:样式指针提循环外 + **整段自适应取已缓存 min/max(O(1),结果逐位一致)** + 无变换走紧凑循环 → **0.02ms**。② **首帧卡顿根治**:加载后宽基底图层从未分配,用户第一次平移/缩放时 `Paint` 在那一帧**同步重建全部 5 个网格图层**(首帧 **64.3 → 13.0ms**);修法:新增 `WarmLaneLayersStep()`,在**命中静态场景缓存**(视界未变、非拖拽)时用预算机制**每帧至多补 1 个网格**,把整批重建摊到静止帧。③ **派生信号把「调整」烘进数据**(不再继承样式指纹);⚠️ 当时写的“仅 `offsetTime` 因共用文件时间轴仍需继承”**已被 §9.21 推翻**(现在 `offsetTime` 也按 `time[i]-offsetTime` 重采样烘进采样值)。④ **GX 拖拽让位判据改为鼠标指针**(鼠标越过相邻网格中心水平线即让位),并给 GX 徽标加悬停高亮(叠加层 + `IDC_HAND`,与信号列表 GX 徽标一致)。⑤ **图例命中矩形在装饰缓存路径下必须重建**(原先命中缓存时提前 return → `m_legendHits` 被清空且不重建 → 中键平移后图例悬停/点选全失效)。⑥ **每个图例条目加圆角托底 + 装饰矩形改为逐块 + 托底不透明铺满外接矩形** → 消除「GX/图例周边旧波形像素残留」(逐像素验证装饰矩形内 **0 差异**);悬停高亮为不透明托底并**重绘色块与文字**(否则内容被盖住)。验证:离屏 Application harness **40/40 通过**,`zoomcheck` mode 20–26 量化,`make release` 零警告。 |
|
||||
| 2026-10-14 | DeepSeek-V4.1-Flash(**起源设备**) | **边沿计数改用「绘制值」+ 派生信号烘入时间偏移 + 图例托底统一为 GX 徽标款 + 调整二级面板向上展开(详见 §9.21)**。① **边沿计数恒为 0**:`EdgeCountsFor`/`EdgeCenterCountsFor` 只比原始采样值,而阈值是屏幕上的值 → 「系数 50」的信号统计恒为 0;修法:判定值改为 `ApplyDrawTransform(style, values[SourceIndexFor(...)])`(中心轴两侧同套变换;水平镜像时曲线窗口也要先映射下标);缓存新增 **`styleKey`**(否则改完「调整」仍命中旧计数)。② **区间边界点被误计**:`EdgeIndexRange` 左端“多取一个采样点”的外扩**已删除**,收紧为“**两个端点都在区间内**才计入”(光标恰落在越过阈值的点 B 上时 B 不再被算作上升沿)。③ **`offsetTime` 也烘进派生采样值**(**推翻 §9.19 的“唯一例外”**):按 `time[i]-offsetTime` 取最近采样点重采样(单调时间轴用只前进的游标,O(n);越界置 NaN),新信号样式**全默认**;非单调时间轴退回到把 `offsetTime` 带在样式上。④ **图例托底 = GX 徽标同款**(同高 / 圆角 = 高度一半 / 同配色 / 水平内边距 = 胶囊半径),文本改用新增字体 **`uiTiny`(10 逻辑 px)**;`kLegendPlateRadius` 已删。⑤ **`PopupMenu::Open(..., expandUp=true)`**(锤点为**左下角**、向上展开,`m_buildExpandUp` 供 `RebuildLayout` 复用)+ 「系数/偏移」「镜像」面板锤在按钮左上角再上移 `kAdjustMenuGap`。验证:`.preflight/uitest` 新增 `[AR]`(五组断言全部与**独立参考实现**逐位比对,含 A/B:“按原始采样值算会得到 7/6 个边沿”);**并同步修正 harness 里两处旧规则断言**(`[AP]` 派生信号继承 → 默认样式 + 烘值;`[AN]` 图例带无托底 → 装饰矩形逐块且暴露列可见底层内容);合计 **722/722 通过**,`make release` 零警告。 |
|
||||
|
||||
---
|
||||
|
||||
@@ -978,11 +1080,12 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
(**包括装饰缓存命中、提前 return 的那条**)都必须**先重建命中矩形**再决定是否跳过绘制 ——
|
||||
否则清空后不再填充,图例的悬停高亮 / 点击选中 / 双击改名会**永久失效**
|
||||
(用户报告"用中键平移过视界后图例点不动")。同理 `m_editFieldRect` 也必须在几何段设置。
|
||||
18. ⚠️ **装饰缓存的像素必须与时间视界无关**:`CaptureDecorCache` 的捕获边界由 `floor/ceil(rect)` 得出,
|
||||
因此每块装饰(GX 徽标 / 图例托底 / Y 轴胶囊)都必须**先用不透明底色铺满"外接整数像素再外扩 1px"的矩形**
|
||||
再画圆角。否则圆角之外的那几个像素仍是波形 → 平移贴回时表现为"GX/图例周边旧波形像素残留"。
|
||||
18. ⚠️ **装饰缓存的像素必须与时间视界无关**:装饰位图的贴回边界由 `floor/ceil(rect)` 得出,
|
||||
因此每块装饰(GX 徽标 / 图例托底 / Y 轴胶囊)都必须画成**不透明的圆角形状本身**
|
||||
(`RenderDecorCache` + `AlphaBlend` 方案下圆角之外**保持全透明**,不要再用外扩的直角不透明底色)。
|
||||
装饰矩形也必须**逐块**(禁止覆盖整条图例带)。验证方法:宽基底实时帧 vs 同视界从零重绘,
|
||||
**装饰矩形内必须 0 差异**。
|
||||
(历史:早期实现是 `CaptureDecorCache` 从后备缓冲区抓不透明矩形 —— 该函数**已删除**。)
|
||||
19. ⚠️ **逐采样循环里禁止调用 `DataSet::Style(ref)` / `Resolve()` 等按下标解析的函数**:
|
||||
`AutoRangeLaneToView` 曾因此把全段自适应从 147ms 劣化到 **1200ms(8 倍)**。
|
||||
一律把指针提到循环外。整段(`begin==0 && end==count`)且无变换/无水平镜像时,
|
||||
@@ -990,6 +1093,23 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
20. ⚠️ **"整批图层重建"绝不允许发生在交互帧里**:新增 `WarmLaneLayersStep()`,
|
||||
在**命中静态场景缓存**(视界未变、非拖拽)时用既有预算机制**每帧至多补 1 个网格**并自请求下一帧。
|
||||
否则"加载后的第一次平移 / 选区放大"那一帧会同步重建全部网格图层(首帧 64ms → 预热后 13ms)。
|
||||
21. ⚠️ **派生信号必须"烘值 + 默认样式"**(推翻 §9.19 的"`offsetTime` 仍需继承",见 §9.21):
|
||||
「调整」的**全部**项(含 `offsetTime`)都写进派生采样值,新信号样式一律默认;
|
||||
否则「调整」面板会带着一整套继承来的指纹,或新信号与预览横向错位。
|
||||
只有"无法烘值"的退化情形(时间轴非单调 / 时间列缺失)才退回把 `offsetTime` 带在样式上。
|
||||
22. ⚠️ **一切"按屏幕上的值"做的统计/判定都必须用 `ApplyDrawTransform` 之后的绘制值**:
|
||||
横向光标阈值、边沿计数(`EdgeCountsFor` / `EdgeCenterCountsFor`)、Y 轴自适应都属此类。
|
||||
用原始采样值判定会得到"应用过「调整」的信号统计恒为 0"这类完全错误的结论。
|
||||
这类**缓存**的键必须带**样式指纹**(`EdgeStyleKey()`),否则改完「调整」仍命中旧结果。
|
||||
23. ⚠️ **限定统计区间时,"区间之外"的采样一律不参与**:只有**两个端点都在区间内**的采样对才计入。
|
||||
不要在区间左端"多取一个采样点"来照顾跨边界的边沿 —— 那会把选区之外的那一段也算进来
|
||||
(用户报告:"光标正好落在越过阈值的点 B 上时,B 被错认成上升沿")。
|
||||
24. ⚠️ **图例托底 = GX 编号徽标同款**(同高 / 圆角 = 高度的一半 / 同配色 / 水平内边距 = 胶囊半径),
|
||||
文本用更小的 **`uiTiny`(10 逻辑 px)**;`LegendHit.rect` 同时是命中矩形与悬停重绘的托底,
|
||||
看到的必须严格等于点得到的。
|
||||
25. ⚠️ **`PopupMenu::Open(..., expandUp = true)`** 表示"锚点是菜单**左下角**、向**上**展开"。
|
||||
给按钮挂"二级面板"时用它(并把锚点设在按钮左上角再上移 `kAdjustMenuGap`);
|
||||
`RebuildLayout()` **必须复用 `m_buildExpandUp`**,否则数值回写触发的重排会让面板跳回向下展开。
|
||||
|
||||
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
|
||||
|
||||
|
||||
Reference in new issue
Block a user