修复长距离中键拖拽的绘制错误与抽搐("距离太远"不再退化成排队重画)

症状(用户报告 + TestData/PROBLEM_IMG):按住中键把视界纵向移动好几个网格高(或横向拖很远),
不松开再挪回来时出现大量绘制错误与持续抽搐。

根因:RollWideBuffer 对"位移超出图层余量"只做"排队等后续帧重画"(QueueLaneRebuild + continue):
  * 该帧 BlitWideBuffer 的 srcY = marginY + vOffset 已落在画布之外 → 只能贴出/贴不到像素
    → 网格里出现空白条带(截图里的白色块);
  * 下一帧位移依旧超限 → 每帧都重新入队,而 RenderPendingLaneRebuilds(1) 一帧只补 1 个网格
    → 5 个网格永远补不完 = 持续抽搐;
  * 纵向余量只有 ±marginY(半个网格高 ≈80px),中键纵向拖拽每帧很容易移动 20~150px
    → 几乎必现;横向同理(余量 ±半屏,快速拖拽会超过)。

修法:只有"内容真的变了"(contentStale:外观/网格几何/幅值跨度/可见性…)才排队(那时画面本身
仍是旧内容、不缺口);距离类问题一律本帧就地解决:
  * |shift| < bitmapWidth 且 |vOffset| < 画布高 → 滚动:BitBlt 整块平移 + 只补画暴露的细条
    (横向条 / 纵向条),并同步锚点记账(wideMin / anchorYMax);
  * 任一超出画布(内容整体滚出)→ 本帧立刻整块重画并重新居中
    (RenderLaneLayerRegion(i, layer, 0, bitmapWidth, 0, 0, LayerDefaultWideMin()),实测 0.4ms/网格)。

探针(.preflight/dragcheck,按用户要求"全程按住中键不放"):
  * 图层条带 vs 同锚点新渲染:逐像素(滚动是纯整数平移,必须相同);
  * 覆盖率:(ViewMin - layer.wideMin) * scale == marginX(±1px)且 marginY + vOffset 起的一整个
    网格高都落在画布内 —— 任一条不成立,BlitWideBuffer 就会留下空白条带;
  * 画面结构:网格内"无墨迹行数"与"墨迹占比"对比整幅重绘参考帧(空白条带 / 内容错位探测器)。
场景:上拖半个网格高;按住拖左右/上下/斜向并拖出窗口;纵向拖 4 个网格高再拖回来并小幅摆动;
快速大位移对角拖拽(每帧 150×110px)。A/B 验证探针有效性:临时把纵向滚动关掉
(kLayerRollQuantum = 100000)→ 立刻报 worst layer band diff 76221 / drawing errors 11;
恢复后 0 错误。

两点说明(避免误判):
1. dragcheck 的"画面像素 diff"会稳定出现 ~2.5 万像素(一个网格的 ~11%)差异 —— 那是图层与整幅
   重绘的列相位差(图层画布 2449px 按 1px 列聚合,整幅重绘 1224px 列,相位差 ≤0.5px),对稠密脉冲
   信号(256kHz 编码器)会放大成大片边缘差异;这是长期存在、由"结算帧"收敛的现象,不是本轮缺陷,
   因此画面检查用结构化指标而不是像素 diff。
2. 截图里"整条网格被信号色填满"是正确渲染:把幅值窗口平移到与稠密信号相交的区间时,每列的
   min/max 包络本来就覆盖整个网格(示波器上同样如此)。

验证(全部离屏,不占用鼠标/键盘/屏幕):dragcheck PASS(0 绘制错误;长拖拽 80 帧 + 快速对角 24 帧);
uitest 463/463;panperf PASS(竖直中位 4.4ms、水平 7-10ms);layercheck clobbers=0/24、
pan frames wrong=0/4;cursorcheck 30/30;make clean && make all 零警告。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-12 20:44:51 +08:00
1 parent 59a84ba126
commit 7344bbe9c4
2 files changed
+89 -22

No files matched your search

+66
View File
@@ -517,6 +517,7 @@ Documents/ 项目文档(含本文件)
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复"纵向预渲染带出的绘制异常/抽搐"(中键上下拖动、尤其拖到网格之外时),并把「弱峰增强」升级为「弱峰调整」(幅度 -200% – +200%,负值削弱)**。① 绘制异常根因:上一轮给图层加了纵向余量,但"偏移超出余量"仍走**排队整块重画**(一帧只补 1 个网格)→ 重画完成前 `BlitWideBuffer` 只能贴出部分行、**其余留空**(网格内出现空白条带),且重画与贴图的锚点不同源 = "波形没按预期移动";鼠标拖到网格外很远时每帧都触发 → 抽搐。修法:与横向**同构的纵向滚动**(BitBlt 整块滚动 + 只补画暴露的一整条,`anchorYMax -= roll/pxPerValue`),并且**必须先于横向补条**;滚动量**量化**(`kLayerRollQuantum = 16` 像素,不足量子交给贴图 `srcY` 偏移)以保持画布映射稳定、让折线缓存命中(未量化时每帧滚 1~30 行 → 折线每帧重算)。验证:`panperf` 拖出网格 900px 时被拖网格的**空白行数与整幅重绘逐帧一致**(64 vs 64,即无空白条带),竖直拖拽 16 帧仅 2 次图层重画。② 弱峰调整:`ComputeWeakPeakAdjust`(原 `ComputeWeakPeakBoost`)+ `WeakPeakItem.amountPercent`(-200–200,默认 150),面板「增强等级 %」→「调整幅度 %」、滑块量程 -200%–+200%(**0 在正中间**)、数值列加宽以容纳 "-200%";识别规则与旧实现完全一致,正幅度段与旧实现**逐位相同**,负幅度段改为以相邻**异类**极值(波峰看相邻波谷)为参照:-100% 拉到**较近**的那个、-200% 拉到较远的那个(压平/抹除,左右平滑过渡),且**不得反向增强**。离屏验证:`uitest` **443/443**(正幅度 0/50/100/150/200% → 550/555/560/580/600 逐位保持;负幅度 -50/-100/-200% → 峰 500/450/400、谷 500/550/560;超范围夹取;健康峰不动)、`panperf`/`layercheck`/`cursorcheck` 全过。 |
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复"上下拖动之后,左右/上下/斜向拖动都出现绘制错误"(真因:图层条带渲染时 GDI 网格线没被限制在条带内),并调整「弱峰调整」的弱峰/弱谷定义、默认幅度与数值符号显示**。① 根因:`PaintLane` 的 GDI 网格线裁剪矩形只限制了横向(`RectF(m_area.X, clipTop, m_area.Width, …)`),而**纵向补条是全宽 × 窄 y** —— GDI 绘制不受 GDI+ 裁剪约束,于是横向网格线被画到条带之外的已正确像素上、**覆盖在波形之上**,并**永久烘进图层**,随后每次平移都带着它一起漂移(用户看到"到处都有绘制错误")。修法:`laneClip = IntersectRect(m_area, 网格可见矩形)` 同时用于 `SetClip` 与 GDI 裁剪。② 新增离屏探针 `.preflight/dragcheck`(真文件 + 真中键拖拽消息:先上拖半个网格高,再左右/上下/斜向、最后拖出窗口;每帧把图层画布的有效条带与"同一 `wideMin` 的新渲染画布"逐像素比较):修复前被拖网格整行不一致(2410px ≈ 2 行全宽),修复后 ≤171px(仅补条边界的抗锯齿边缘)。③ 弱峰调整:**「是否偏弱」改为**"波峰必须同时低于左右两侧各自的(相邻波峰+相邻波谷)均值;波谷反之";默认幅度 **0%**(不改动);滑块右侧数值**始终带符号**(`+150%` / `-150%` / `0%`,验证 +150% 与 -150% 墨迹宽度一致且都比 `0%` 宽 ≥8px)。验证:`uitest` **441/441**、`dragcheck`/`panperf`/`layercheck`/`cursorcheck` 全过、`make clean && make all` 零警告。 |
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **「弱峰调整」升级为三组识别规则 + 滑动条 ±400% 梯度驱动**:第一类(两侧均值都要满足,原规则)在滑动条 0–200% 内走完自身 0–200%;第二类("或":任一侧均值满足即可)从 100% 起参与,own 幅度 = clamp(|a|-100%,0,200%);第三类(只与左右同类极值比较,最宽松)从 200% 起参与,own 幅度 = clamp(|a|-200%,0,200%);负值完全对称。每组自身的 ±200% 映射统一为:+100% → 左右同类极值中较温和的一侧、**+200% → 左右同类极值的均值**(不再取较大的那个);-100% → 相邻异类极值中较近的一个、**-200% → 相邻异类极值的均值**(不再取最小的那个)。同时满足多组时取**幅度绝对值最大**的组。验证:`uitest` **463/463**(第一类点 0/50/100/150/200% → 520/570/620/**625/630**;只满足第二类的峰 130 → 100% 不动、150%/200%/250%/300%/400% = 140/150/162.5/175/175;只满足第三类的峰 195 → 200% 不动、250%/300%/400% = 197.5/200/200;±400% 夹取;滑块中间 = 0%、两端 = ±400%)、`panperf`/`layercheck`/`cursorcheck`/`dragcheck` 全过、零警告。 |
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复"按住中键把视界纵向移动好几个网格高(或横向拖很远)后,出现大量绘制错误与持续抽搐"**:根因是 `RollWideBuffer` 对"位移超出余量"只做**排队等后续帧重画**(`QueueLaneRebuild` + `continue`)—— 该帧 `BlitWideBuffer` 取不到源行 → 网格里出现**空白条带**;且每帧都重新入队而 `RenderPendingLaneRebuilds(1)` 一帧只补 1 个网格 → **补不完 = 持续抽搐**;纵向余量只有 ±半个网格高,正常速度的纵向拖拽每帧就超过它,故几乎必现。修法:**只有"内容真的变了"才排队**,距离类问题一律本帧就地解决 —— 位移小于画布则滚动(O(1) BitBlt + 只补暴露细条),超出画布则**立刻整块重画并重新居中**(0.4ms/网格)。验证:新增探针 `.preflight/dragcheck` 按用户要求**全程按住中键不放**地做检查(图层条带逐像素 / 画布覆盖率 / 画面空白行数与墨迹占比),并把纵向滚动临时关掉做 A/B:立刻报 `layer band diff 76221 / drawing errors 11`,恢复后 **0 错误**;`uitest` **463/463**、`panperf`/`layercheck`/`cursorcheck` 全过、`make clean && make all` 零警告。 |
---
@@ -2820,3 +2821,68 @@ Documents/ 项目文档(含本文件)
轨道 3/4 处 ≈ +200%(第一类上限)。
- `[Y]` 端到端:合成 ripple(谷恒 500、一个弱峰 520)在 50% 幅度下弱峰区间 20 → 70(健康 120)。
- `panperf` PASS、`layercheck` `clobbers=0/24`+`pan frames wrong=0/4`、`cursorcheck` 30/30、 `dragcheck` 0 绘制错误;`make clean && make all` 零警告。
---
## 本轮(2026-10-12,起源设备):长距离中键拖拽的绘制错误/抽搐("距离"不再退化成排队重画)
用户报告(并附 `TestData/PROBLEM_IMG/*.png`):按住中键把视界**纵向移动好几个网格高**、不松开再挪回来(或继续上下移动)时,出现**大量绘制错误与抽搐**;要求"验证时不要松开中键截图"。
### A. 根因(`RollWideBuffer` 的"距离太远 → 排队"分支)
上一轮的图层余量只覆盖「横向 ±半屏、纵向 ±半个网格高」,超出余量时旧代码只做:
QueueLaneRebuild(laneIndex); // 本帧不画
continue;
于是只要"位移超过余量":
1. 该帧 `BlitWideBuffer` 的 `srcY = marginY + vOffset` 已经落在画布之外 → 只能贴出/贴不到像素,
网格里出现**空白条带**(用户截图里的"白色块");
2. 下一帧位移依旧超限 → **每帧都重新入队**,而 `RenderPendingLaneRebuilds(1)` **一帧只补 1 个网格**,
5 个网格永远补不完 → **持续抽搐**;
3. 纵向余量只有 ±`marginY`(半个网格高 ≈ 80px),而中键纵向拖拽每帧很容易移动 20~150px,
**几乎必现**;横向同理(余量 ±半屏,快速拖拽超过它)。
### B. 修法:距离类问题一律本帧就地解决
- **只有"内容真的变了"(`contentStale`:外观 / 网格几何 / 幅值跨度 / 可见性…)才排队** —— 那时画面
本身仍是"旧内容",不缺口,可以让后续帧慢慢补。
- 距离类问题本帧处理:
* `|shift| < bitmapWidth` 且 `|vOffset| < 画布高` → **滚动**:`BitBlt` 整块平移 + 只补画暴露的细条
(横向条 / 纵向条),并同步锚点记账(`wideMin` / `anchorYMax`);
* 任一超出画布(内容整体滚出)→ **本帧立刻整块重画并重新居中**
(`RenderLaneLayerRegion(i, layer, 0, bitmapWidth, 0, 0, LayerDefaultWideMin())`,实测 0.4ms/网格)。
### C. 探针(`.preflight/dragcheck`,按用户要求"全程按住中键")
真文件中键消息注入,**不松开**;每一步都做三类检查:
1. **图层条带 vs 同锚点新渲染**:逐像素(滚动是纯整数平移,必须相同);
2. **覆盖率**:`(ViewMin - layer.wideMin) * scale == marginX`(±1px)且
`marginY + vOffset` 起的**一整个网格高**都落在画布内 —— 只要有一条不成立,
`BlitWideBuffer` 就会留下空白条带;
3. **画面结构**:网格内"无墨迹行数"与"墨迹占比"对比**整幅重绘参考帧**
(空白条带 / 内容错位探测器;不松开中键时截取)。
场景:① 上拖半个网格高;② 按住拖左右/上下/斜向并拖出窗口;③(用户复现)**纵向拖 4 个网格高再拖回来**、
再小幅上下摆动;④ **快速大位移对角拖拽**(每帧 150×110px,超过纵向余量)。
**A/B 验证探针有效性**:把纵向滚动临时关掉(`kLayerRollQuantum = 100000`)→ 立刻报
`worst layer band diff 76221`、`drawing errors 11`;恢复后 **0 错误**。
### D. 两点说明(避免误判)
- `dragcheck` 的"画面像素 diff"会稳定出现 ~2.5 万像素(一个网格的 ~11%)差异:这是**图层与整幅重绘的
列相位差**(图层画布 2449px 按 1px 列聚合,整幅重绘 1224px 列,相位差 ≤0.5px),对**稠密脉冲信号**
(256kHz 编码器)会放大成大片边缘差异;这是长期存在、由"结算帧"收敛的现象,**不是**本轮缺陷。
因此画面检查采用结构化指标(空白行数 / 墨迹占比)而不是像素 diff。
- 用户截图里"整条网格被信号色填满"是**正确**渲染:把幅值窗口平移到与稠密信号相交的区间时,
每列的 min/max 包络本来就覆盖整个网格(示波器上同样如此)。
### E. 验证(全部离屏,不占用鼠标/键盘/屏幕)
- `dragcheck` PASS(0 绘制错误;长拖拽 80 帧 + 快速对角 24 帧,最差帧 ~25ms);
- `uitest` **463/463**;`panperf` PASS(竖直中位 4.4ms、水平 7-10ms);
`layercheck` `clobbers=0/24` + `pan frames wrong=0/4`;`cursorcheck` 30/30;
- `make clean && make all` 零警告,`Build/Release/Target.exe` 已更新。