Commit Graph
139 Commits
Author SHA1 Message Date
DeepSeek V4.1 Flash 21a9865365 fix(clip): 选区截取面板五处缺陷(条目/空白/多信号截取/滚轮)
一、未启用双光标时不再显示信号条目
   EdgePanelLayoutFor 与绘制侧都只判 clipPanel、没判双光标是否启用,
   于是照常累加内容高度、生成行矩形并绘制条目。
   - 高度算式新增 clipReady = clipPanel && CursorMode()==DOUBLE,未就绪时内容只占一行(提示);
   - 行几何循环加 clipRowsVisible,未就绪时不生成任何行矩形 => 绘制自然为空;
   - footer 同步改为"未就绪 = 0"。

二、条目区与底部按钮之间的大段空白(未启用双光标时也有)
   两个叠加根因:
   1) 高度算式的 maxContent 有"至少 3 行"下限 => 只有 1 个信号也硬撑 3 行高;
      改为 min(上限, max(1 行, budget))。
   2) 几何侧 viewHeight 多减了一个 pad(高度算式只含一个 pad + bottomPad);
      统一为 height - titleHeight - pad - footerHeight。
   高度算式与内容区几何必须严格同源。

三、多个信号「原始信号截取」结果不对
   同组信号共享同一条时间轴,ClipSignalRange 每次都裁剪 axis.time
   => 截完第一条后其它信号时间基准被改短 => 采样与时间错位。
   - 新增 DataSet::ForkAxisForSignal:轴非独占时复制一份新轴并让信号指向它;
   - ClipSignalRange 内再加一道守卫:轴仍被共享时直接返回 false(不改任何数据);
   - Application::ClipSignalInPlace 先 fork 再裁剪。

四、有滚动条时滚轮滚不到底
   1) OnMouseWheel 的 current 取值链漏了 CLIP => 读到 meanFilterScroll(恒 0)而写入 clipScroll,
      补上 CLIP 分支;
   2) PanelSlotKey 里 clipScroll 被写成布尔(>0 ? 1 : 0)=> 滚到 1px 后键不再变化,
      改为 mixFloat(lane.clipScroll) 真实值。

删 .o 全量重编译:零 error、零 warning,产物 861,184 B。
按用户要求未占用鼠标/键盘/屏幕,未运行 GUI 探针。
2026-11-03 16:39:18 +08:00
DeepSeek V4.1 Flash 62220477c0 feat(clip): 新增「选区截取」实验性功能 + 筛选态组复选框只作用于筛选结果 + 重采样统计不被省略
一、重采样面板统计信息不再被省略为 `…`
   - 根因:EdgePanelWidth 里数值列按"典型样例"(1h 1min 2s(1.23456 GHz))估宽,
     真实文本(16ms 182.614us(61.795 Hz))更长 => 绘制矩形不够宽 => 尾部被裁。
   - 新增 FormatIntervalWithFrequency() 作为「间隔(频率)」文本的唯一实现,
     宽度计算与绘制共用它;EdgePanelWidth 改为逐信号实测真实文本宽度取最大值。

二、筛选状态下组复选框只作用于「筛选出来的信号」
   - TabConfig 新增 SetGroupVisibleSubset / GroupAllVisibleSubset / GroupHasVisibleSubset,
     只读写掩码内元素,未通过筛选的信号可见性保持不变。
   - SignalPanel::BuildFilterMask 提供逐信号掩码(未筛选时返回 false,走整组语义)。
   - 组复选框的点击、绘制、行位图缓存键三处统一改用子集判定
     (只改绘制不改缓存键会导致勾选后复选框不刷新)。

三、新增实验性功能「选区截取」(EdgePanelKind::CLIP = 6,PLOT_CMD_LANE_CLIP = 14)
   - 菜单位置:实验性功能二级菜单,「重采样」之下、「自由标记」上方,带勾选。
   - 面板:逐信号一行(信号色叠底条目栏 + 右侧复选框,不可展开),内容可滚动;
     底部两个不滚动的按钮「截取为新信号」与「原始信号截取」;
     未启用双光标时两个按钮为空矩形并给出提示。
   - 截取为新信号:区间内原始数据(不烘焙)+ 继承源信号整套调整,
     名称为 <源信号名> (AAAA - BBBB)(显示时刻、保留三位小数、带 s),插入源信号下方,
     使用 AppendGroupAxis 新建的独立时间轴。
   - 原始信号截取:DataSet::ClipSignalRange 就地裁剪时间轴与采样,
     用新建+swap+shrink_to_fit 真正归还内存,并重算统计与轴的 minTime/maxTime/monotonic。
     调用前先 PauseDerivedAsync() 避免后台派生读到已释放采样。
   - 时间轴偏移参与区间比较:双光标是显示时刻(time+offsetTime),
     BuildClippedSignal 比较时加偏移,ClipSignalInPlace 换算回采样时刻(low-offsetTime)。
   - 同步点已全部补齐:LaneCarry(三处)/SplitLane/PruneLaneSignalItems/RemapAfterSignalInsert/
     purgeRefs/PanelSlotKey/CollectDebugBoxes/SceneContentHash/kEdgePanelKinds/kOrder/
     EdgePanelLayoutFor/EDGE_PANEL_MOVE/m_pendingPanelClose/MEAN_SCROLL/滚轮/悬停/
     SetLanePanelCollapsed/LanePanelCollapsed/QueryCursor。

make release 零 warning,产物 860,160 B。
2026-11-03 16:04:48 +08:00
DeepSeek V4.1 Flash c4300bd0e1 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。
2026-11-03 14:16:25 +08:00
DeepSeek V4.1 Flash 9fdbf49841 fix(resample,formula,clone): 重采样面板拖动/关闭失效 + 统计频率与输入框不一致 + 派生烘焙接入公式映射 + 克隆拆分
一、「重采样」浮动面板拖不动 / ✕ 关不掉(两个独立缺陷)
1. 拖不动:OnLButtonDown 的 resampleMode 分支处理完条目与按钮后无条件 return true,
   而「标题栏 -> EDGE_PANEL_MOVE」的判定在面板分支最末尾 => 标题栏按下被吞掉。
   改为只消费 insideContent 之内的点击。
2. 拖不动(第二处):OnMouseMove 的 EDGE_PANEL_MOVE -> switch (m_edgePanelDragKind)
   漏了 case RESAMPLE => 位置写进 edgePanel、又从 resamplePanel 读 => 纹丝不动。
3. ✕ 关不掉:OnLButtonUp 的 m_pendingPanelClose -> switch (kind) 漏了 RESAMPLE
   => 落进 default 关掉的是「边沿计数」。补 SetLaneResample(lane,false)。

二、统计「最小数据间隔」与「重采样频率」输入框不一致(用户报告)
   实例 MF4-Record.MF4 / m_MOT_Current_DCom[0]:最小间隔 0.003696631s
   - 统计行先舍入到 3 位有效数字(3.70ms)再换算 => 270.27 Hz;
   - 输入框用未舍入原值换算 => 270.517 Hz。
   真值 270.5166 Hz,两条路径基准不同。修法:频率一律由 RoundIntervalForDisplay
   之后的同一份显示值换算 => 输入框与统计行同为 270.27 Hz、间隔文本同为 3ms 700us。
   未改动面板行数与任何 UI 布局。

三、派生烘焙接入「公式映射」
   BuildDerivedSignal 与 CloneSignal 各写了一份
   gain!=1 || absolute || mirrorVertical || offsetY!=0,漏掉 HasFormula()
   => 带公式的信号派生/克隆后公式被静默丢弃。改为含 HasFormula() 的统一判定。
   BuildResampledSignal 本就走 ApplyDrawTransform 全程烘值,天然包含公式。

四、信号右键菜单:克隆拆成两个语义(均不烘焙采样值)
   - 「克隆该信号」:原始数据 + 外观 + 仅保留时间轴偏移,其余调整复位;
   - 「克隆该信号(带调整)」:原始数据 + 外观 + 整套调整参数。
   新增 Application::CloneSignalAdjusted、CMD_SIGNAL_CLONE_ADJUSTED=6、
   kMessageCloneSignalAdjusted=WM_APP+6。

五、教训:离屏探针崩溃会经 MemProbe::OnTerminate 弹模态「内存不足」框,
   反复重跑即反复弹窗(用户看到十几次报警,真实进程并无内存问题)。
   探针应避免 Initialize+Render,或只做数据层验证;必须无缓冲输出阶段标记。

验证:.preflight/r26 三个探针(真实 MF4 数据一致性 SAME;逻辑 7/7;端到端 12/12);
      make release 零 warning。
2026-11-03 11:09:25 +08:00
DeepSeek-V4.1-Flash 74456b9305 fix(formula,resample): 公式映射卡顿根治 + 语法/输入框完善 + 重采样面板三处修正
一、「公式映射」卡顿(用户报告:只是点进输入框就卡,编辑过公式后彻底没法用)
1. 真因:菜单内联编辑提交时无条件回写初值 ⇒ 点进输入框再点别处就把
   formula = "f(t)" 写进样式;而 HasFormula() 只判 !empty(),于是
   DrawTransformOrderPreserving() 变 false、ComputeLaneRange() 每帧逐点扫描
   整条信号(2580 万采样 ≈ 960ms/帧)。
   修法:新增 FormulaIsIdentity()(空串与 f(t) 等价);提交时归一化后恒等即清空;
   BuildLaneVisuals() 为「网格幅值范围」加指纹缓存(数据指针/长度/统计值 +
   影响数值的样式字段),键不变直接复用(实测 0.0005ms)。
2. 真实公式仍然贵:新增 PlotView::AcquireFormulaEnvelope()(变换后的逐分组
   包络,65536 组,按需算一次并缓存),BuildPolyline 的包络带改用它。
   实测:静止 215→21ms、中键拖动 8ms、缩放最坏 843→81ms;极值仍逐点统计
   (不会漏尖峰)。
3. AcquireProgram 只比 data() 指针 + 长度 ⇒ std::wstring 短串走 SSO 时,
   等长改公式会一直复用旧程序(f(t)^2→f(t)^3 波形不更新)。改为按内容判定。

二、公式语法(Utilities/FormulaEval)
- 新增 ^ 幂(右结合,2^3^2=512、2^-1=0.5、-2^2=-4),支持 2^f(t) 与 f(t)^2;
- 常量 e / pi(大小写均接受,保存前统一小写);
- 数据点只允许 X / x / f(t):单独的 f 不再接受(且不归一化,直接报错);
- 数字扫描修正:2e 里的 e 不再被当成指数吞掉。

三、「公式映射」输入框(SignalPanelMenu)
- Y= 画在输入框内部(非编辑态),进入编辑态只显示公式本身;
- label 留空 ⇒ 输入框占满整行,与下方「系数调整」同宽。

四、「重采样」面板
- 修复点进间隔/频率输入框后输入框整块消失(paintOverlays 的 PaintEditField
  名单漏了 RESAMPLE_INTERVAL / RESAMPLE_FREQUENCY);
- 最大/最小数据间隔:3 位有效数字 + Δt 复合单位,末尾追加(频率)尾缀;
- 间隔显示自适应单位、编辑态用秒;频率显示自适应单位、编辑态用 Hz(均不带单位);
- 面板宽度按含(频率)尾缀的文本量宽。

五、十进制格式化(Utf8Util)
- 新增 FormatDecimal(绝不产生科学计数法);FormatDurationCompound /
  FormatDurationSeconds / FormatFrequency 改用它,Δt/间隔/频率读数不再出现 1e-05。

验证:离屏探针 .preflight/perf/probe/formulacheck_main.cpp 75/75;
make release / make debug 零 warning;Release 856,064 B。
2026-11-03 02:55:22 +08:00
DeepSeek V4.1 Flash 1e1c2432ae feat(resample): 实验性功能新增「重采样」(位于「自由标记」上方)
- Lane::ResampleItem + resample/resampleItems/resamplePanel/resampleScroll,
  并在 LaneCarry / SplitLane / PruneLaneSignalItems / RemapAfterSignalInsert|Removal 同步
- 面板:可滚动分组列表(信号色条目栏 + 复选框 + 展开三角),展开后 4 行:
  最大/最小数据间隔(只读,勾选时一次性缓存,绘制不扫描采样)+ 重采样间隔/频率(输入)
- 输入框联动:改间隔同步频率,改频率同步间隔(输入单位分别为秒 / Hz)
- 「构建为新信号」:DataSet::AppendGroupAxis 追加新时间轴 + InsertDerivedSignal,
  采样值先按公式/系数/绝对值/镜像/Y偏移变换再等间隔线性插值,时间轴偏移不烘值
- 新信号名 <原信号> (重采样 <自适应单位频率>);Utf8Util::FormatFrequency 为唯一实现
- 非单调轴 / 点太少 / 间隔过大 / 点数超 2000 万 ⇒ 拒绝构建并给出自绘提示
- 顺带加入 FormulaEval(公式编译/求值,供 ApplyDrawTransform 与后续 UI 使用)
2026-11-02 20:41:26 +08:00
DeepSeek V4.1 Flash 0c320cf1b3 fix(plotview): 修复面板条目栏悬停时预览曲线高亮丢失 + 极端工况绘制管线提速
一、悬停高亮回归修复
- 根因:高亮画在预览曲线绘制路径里,而该路径在命中静态场景缓存时完全不会被调用
  (悬停态又不参与 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。
2026-11-02 18:47:21 +08:00
DeepSeek V4.1 Flash ea704b03ea fix(plotview): 边沿计数表格归位、统计面板悬停时持续更新、加载不再被统计阻塞、统计提速
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。
2026-11-02 16:15:40 +08:00
DeepSeek V4.1 Flash 8de8a8fb85 预览曲线按面板高亮 + 统计数字不再卡在“统计中…” + 中心轴面板改表格 + 默认主题 Light + 标签页 DPI 修复 + 布局边界幽灵元素
一、预览曲线悬停高亮(只开弱峰+中心轴时无反应;三个全开时总亮均值曲线)
   根因: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
2026-11-02 15:22:26 +08:00
DeepSeek V4.1 Flash 7a6fcd68e2 面板限高与滚动改造 + 边沿统计不再卡在“统计中…” + 组名折起真正收起
一、浮动面板(用户报告:下方空白、滚动时画到面板外、未改造的面板让一切交互卡顿)
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。
2026-11-02 10:00:24 +08:00
DeepSeek-V4.1-Flash 944c074ab3 288 信号卡顿根治 + 中心轴面板重做 + 标签页默认状态
一、卡顿真因(实测 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
2026-11-02 03:33:28 +08:00
DeepSeek V4.1 Flash 8271879ede 面板动画帧不再重绘绘图区(组名展开/收起卡顿根治)+ 均值面板两处修正 + 信号行集合自愈
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 第十八轮」,
并把第十七轮里"尚未启用 → 直接开始预览"改标注为已推翻。
2026-10-30 15:06:01 +08:00
DeepSeek V4.1 Flash 2d83b494a3 均值面板越界根因 + 网格线进入主题配色 + 条目栏悬停/整行点击 + 均值点数下限 1
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…上限)」「命中要求行完全落在内容区里」两条改成现行做法。
2026-10-30 10:51:39 +08:00
DeepSeek-V4.1-Flash bdd703b88b 主题自适应配色 + 预览曲线连续配色 + 样式区复位按钮 + 均值滤波面板重做 + 面板缓存环带残留 + 调试层序 + 自由标记选取与性能
① 主题自适应(`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` 第十六轮(含补丁脚本与编码/行尾踩坑)。
2026-10-30 03:10:51 +08:00
DeepSeek V4.1 Flash 71195c4c48 新增主题配色(4 套)+ 信号右键菜单精简与「克隆该信号」+ 布局边界层级根治 + 单点信号吸附修复
## 为什么

用户 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)。
2026-10-29 20:38:55 +08:00
DeepSeek V4.1 Flash 71c6ff91cb 修复:适应视图不跟随时间轴偏移 + 派生信号构造成空信号
## 为什么

用户 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 13:48:25 +08:00
DeepSeek V4.1 Flash 044b87383b 体积 L3(-fno-exceptions + MemProbe + LTO)与 L1 几何产物层;产物 909,824 -> 788,992 B
## 为什么

用户 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 11:53:54 +08:00
DeepSeek V4.1 Flash 739a523af5 fix(UI): 浮层不再被下层边界穿透 + 菜单条目裁剪 + 修正性能误判
## 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 10:21:20 +08:00
DeepSeek V4.1 Flash aa4925a2b7 fix(UI): 边界框不再内缩 + 文本获得几何身份 + 补全漏项 + 描边提速 32%
## 1. 取消内缩(这是"边框不清晰"的主因)

第九轮为"父子矩形共边不叠粗"给 depth > 0 的 box 加了 1px 内缩。结果 GX 徽标
(30x17 逻辑像素的胶囊,圆角 = 高的一半,外接矩形正好等于 LaneBadgeRect)内缩 1px 后
框比形状小一圈 ⇒ 左右尖端与上下圆弧全部落在框外。

现行规则:**框 = 可见形状的外接矩形,绝不内缩**。父子共边时让"后画的覆盖"即可,
这本来就是正确的图层语义。层级 > 0 只影响配色,不再影响几何。

## 2. 文本第一次有了几何身份(唯一出口回报,不逐处登记)

文本是本项目唯一"没有几何身份"的元素:65 处绘制点各自现算 gd::RectF、画完即弃。
逐处补登记必漏,因此在唯一出口回报一次:

  PaintUtil 新增
    class UiTextRectSink { virtual void OnTextRect(const gd::RectF&) = 0; };
    void UiSetTextRectSink(UiTextRectSink*);       // 开关关闭时为 nullptr
    void UiSetTextRectOffset(float dx, float dy);  // 离屏位图渲染的坐标换算
    UiTextRectSink* UiGetTextRectSink();

DrawUiText() / UiTextAtClipped() 画完后按对齐方式 + 实测宽高把布局矩形**收窄成墨迹矩形**
再回报。UiDebugBoxList 直接继承该接口,宿主只需 UiSetTextRectSink(&m_debugBoxes) 一句。
开关关闭时只是一次空指针判断,正常绘制零影响。

## 3. 缓存命中的行也要有文本框

面板的行是"先画进位图再贴回",命中缓存的帧**根本不调用 UiText** ⇒ 文本框第二帧起消失。
做法:行渲染时用本地 RowTextCollector 收集(位图坐标)→ 连同位图存进
RowBitmapCache::textRects → 贴图时按 (originX, originY) 回放。
⚠️ **必须无条件收集**:只在"外层有收集器"时收集会导致"关调试时滚过列表 → 打开调试 →
命中缓存 → 永远没有文本框"。

## 4. 补齐 PlotView 漏项(全部读现成成员)

m_timeAxisRect / m_scrollBarRect / BackToTopRect() / m_cursorSpanRect /
m_cursorLabelRects / EditFieldRect()

## 5. 描边提速:图元 12 → 8

微基准结论:图元数量是唯一瓶颈(每次 FillRectangle ≈1µs)。
旧实现把每条边拆成"中段 1px + 两端各一段加粗"= 12 次;新实现 = 4 条 1px 边 + 4 个自适应角块。
实测 paintBoxes 5.892ms → 4.028ms(-32%),观感几乎一致(角仍是两条粗边的交点)。
⚠️ 不要改写成 GraphicsPath/FillPath(单次约 20µs,比 12 段还慢);
GDI PolyPolygon 一次画 5688 个矩形要 114ms,更不能用。

## 6. 关闭开关时清空容器(释放内存,避免误读)

## 7. 验证

- make release / make debug 零 error 零 warning;TargetCheck.exe 909,824 B(888.5 KiB < 900 KiB)。
- 新增探针 .preflight/lbcovr 7/7:box-time-axis / box-plot-scrollbar / box-back-to-top /
  box-gx-badge-matches-pill(容差 0.5px)/ text-boxes-collected(89 个)/
  row-text-boxes-from-cached-rows / boxes-cleared-when-off。
- 回归:lbcheck 21/21、lbresid 5/5、lbzorder 4/4、uicheck 69/69。
- rendercheck:8 个场景的整帧与区域哈希与上一轮**逐一相同** + FUNC 7/7
  (证明调试功能对正常绘制零影响)。
- 帧耗时分解(MF4 / 283 网格):base 9.06ms、仅强制整帧 6.66ms、完整调试 11.35ms;
  collect 0.082ms、paintBoxes 4.028ms(上一轮 5.892ms)。

## 8. 文档

Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第九阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第十一轮」硬约束与踩坑。
2026-10-28 17:38:55 +08:00
DeepSeek V4.1 Flash 57e12cfb27 fix(UI): 布局边界按尺寸自适应 + 修复拖滚动条的绘制残留与浮层层级
## 1. 边界描边按元素尺寸自适应(修复"小元素变成粗描边")

旧规则四角固定 3-4px:对 15px 的复选框来说包角占短边 20%,看起来就是一整圈粗边框。
新规则全部以**短边**为基准:
- 四边固定 1 设备像素;
- 包角粗细 = min(基础 4/3, 短边/10),再夹到 [1, 短边/4];
- 包角长度 = min(14, 短边/3),且不小于包角粗细;
- 短边 <= 12 只画四个 L 形角标记、**不画边**(一圈连续描边会让小方框显得"被框住");
- 短边 <= 5 完全不画(再画就盖住元素本身);
- 内缩只用于「层级 > 0 且短边 >= 10」,否则小元素会被缩没。

同时**删除文字标签**(含 UiDebugPaintStyle):黑底白字在小元素上必糊成一片,体积也省约 1.5KB。

## 2. 拖网格滚动条时的"大量绘制残留":三个独立成因

R1. PlotView::CollectDebugBoxes 读了 m_decorRects —— 那是**装饰层位图缓存**的登记矩形,
    坐标处在位图/图层坐标系(带负 Y、超出绘图区),于是出现 (472,-27 1213x101) 这类
    **横跨工具栏的幽灵巨框**。改为只用 LaneBadgeRect 与 m_legendHits(屏幕坐标)。

R2. Paint 的三条快路径(静态场景贴图 / 宽基底贴图 / 样式补丁)只重画**变化的部分**,
    而调试边框是画在整个窗口上的叠加内容 ⇒ 上一帧的边框残留在未被重画的像素上,
    直到松开鼠标走整幅重绘才消失。
    新增 PlotView::SetForceFullRepaint(bool):开启调试叠加层时禁用全部快路径。

R3. 组件内元素可能落在自己地盘之外(滚出可视区的 lane.rect 的 Y 为负),不裁剪就画到
    工具栏/标签栏上。box 增加 clip:宿主按组件 Area() 设置,Add() 跳过无交集、
    绘制时与 clip 求交。

另外把 Render 的 fullChrome 并入 m_showLayoutBounds,装饰区也保证完整一帧。

## 3. 层级:排序 + 挖洞(修复二级菜单被下层边界覆盖)

- UiDebugBoxList::PushLayer() + 按 (layer, depth) 稳定排序:后收集的内容压住先收集的。
- 但仅靠排序不够:调试线画在所有内容之后,下层组件的线仍会显示在不透明菜单上面。
  新增 AddOccluder():菜单/拾色器把本体登记为遮挡矩形,绘制**更低层**的 box 时用
  Region::Exclude 挖洞。实测菜单区与子菜单区的琥珀/绿色像素均为 0。
- 浮层**不设** clip = 本体矩形:二级子菜单会展开到本体之外,设裁剪会把它整个裁掉。

## 4. 验证

- make release / make debug 零 error 零 warning;TargetCheck.exe 906,752 B(885.5 KiB < 900 KiB)。
- 新增 .preflight/lbresid(MF4-Record.MF4 / 283 网格 / 拖滚动条)**5/5**:
  toolbar-unchanged-while-scrolling、repaint-same-state-is-identical、
  toolbar-restored-by-full-repaint、residual-never-above-plot-area、later-layer-wins-over-earlier。
- 新增 .preflight/lbzorder **4/4**:菜单与二级子菜单区内下层颜色像素 = 0。
- 回归:.preflight/lbcheck 21/21、Tools/uicheck 69/69、
  Tools/rendercheck「工具栏带以下」区域哈希 8/8 与上一轮逐一相同 + FUNC 7/7。

注:判断"是否有绘制残留"不能用"拖拽帧 vs 松开后一帧"——两者图层预热进度不同会产生
几十万像素的**合法**差异;正确判据是**同一状态连续两帧必须逐像素相同**。

## 5. 文档

Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第八阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第十轮」硬约束与踩坑。
2026-10-28 17:01:26 +08:00
DeepSeek V4.1 Flash 24706c906a feat(UI): 布局边界改为四角粗/四边细包角 + 全元素覆盖 + 几何同源(RowRect O(1))
## 1. 边界观感:四角粗、四边细

用户反馈"边框就是实心矩形、粗细不一,效果不佳"。旧实现是四条等宽实心矩形
(层级 0 用 2px、其余 1px),同一元素的角与边粗细不一致,父子矩形共边时还会叠成更粗的一条。

新规则:
- 四条边固定 1 设备像素(四条独立实心矩形;不用 DrawRectangle,笔宽奇偶会偏半像素);
- 四个角额外加粗:层级 0 = 4px、其余 3px,沿水平/垂直各延伸 cornerLen(≤14px 且 ≤ 短边一半);
- 层级 > 0 整体内缩 1px,父子矩形共边不再叠粗;
- 绝不填充(几十层嵌套填充会把内容糊住);
- 整像素对齐 + PixelOffsetModeNone + 关抗锯齿。

四角粗不只是观感:多条边重合时靠角的形状分辨层次(父=青蓝大包角、子=同位置品红小包角)。

## 2. 协议:组件"收集 box",宿主统一绘制

UiComponent::CollectDebugBoxes(UiDebugBoxList&)  默认只登记 Area()
UiWidget::CollectDebugBoxes(boxes, depth)        递归整棵控件树
Application::PaintLayoutBounds                   收集后按 depth 升序一次性描边

刻意保持 box 为**值类型**而不是让它继承控件基类:PlotView 的 4 层位图缓存、
SignalPanel 的 192 槽行位图缓存都要求"绘制由组件自己负责",几何进继承体系会与
缓存键/失效体系打架。box 不参与绘制,因此能让绘制/命中/调试共用同一份几何。

## 3. 覆盖补齐(此前大量元素被裹在外层单位里)

- Toolbar:区域 / 每个按钮 / **按钮内容(文字或齿轮图标,新增 UiButtonWidget::ContentRect())**
- TabBar:区域 / 标签条 / 新建按钮 / 每个 ✕
- SignalPanel:4 分区 + 筛选输入框/清空✕/全部显示 + 滚动轨道与滑块
  + 样式区 5 行及其 12 色块/自定义/3 模式/3 调整/线宽与透明度轨道和数值
  + 目标行/名称/GX 徽标 + **每个可见行**的折起三角/复选框/色块/名称/GX 徽标/✕
  (组行另有组名/计数)+ 内联编辑框
- PlotView:区域 / 每个网格 / GX 徽标 / 装饰层矩形 / 图例托底+色块+文本
  / **5 类浮动面板全部 20+ 字段**(标题/✕/折起三角/徽标/编辑行/按钮/内容区/滚动轨道与滑块
  /透明度轨道与数值/两个开关/各行复选框与数值与滑轨与复位与名称与✕/分组栏/表头/固定点行)
- PopupMenu:浮层 / 每个条目 / 可滚动可视区 / 轨道 / 滑块 / 二级子菜单与条目
- ColorPickerPopup:面板 / 标题 / 预览 / 色号 / 色域 / 色相条 / 3 条滑轨与数值 / 输入框

标签增加**尺寸门槛**(< 56×16 逻辑像素不画):复选框/色块/✕ 紧邻,逐个小框贴字会糊成一片。

## 4. 设置按钮配色 + 齿轮图标

- ToolbarAction::SETTINGS 改为 primary = true,与「导入文件」同为强调色主按钮。
- 齿轮几何重做:齿占最外一圈(0.66R→1.0R)、外环中心线 0.54R/笔宽 0.24R、轴孔 0.17R。
  旧版让齿从环中心线伸出,齿被环的描边吃掉 ⇒ 看起来只剩一个"甜甜圈"。

## 5. 几何同源与性能

- RowRect(row) 原本每次调用都从头累加行高(O(row)),而画一行要调它 **5 次**
  (✕/徽标/名称/组计数/折起三角各一次)⇒ 200 行只见 30 行时每帧上万次加法,
  命中测试(每次鼠标移动)同样走这条路。
  新增 m_rowTop 前缀和(由 UpdateAnimGeometry() 统一重建)⇒ **O(1)**;
  长度与行集合不一致时自动退回逐行累加(正确性优先)。
- ContentOffsetBeforeRow 同样改用前缀和。
- PanelBounds() 收敛 6 处重复的 (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area。

## 6. 验证

- make release / make debug 零 error 零 warning;TargetCheck.exe 910,336 B(889 KiB < 900 KiB)。
- .preflight/lbcheck:21 / 21 PASS(含 bounds-off-restores-pixels:关闭后逐像素回到开启前)。
- Tools/rendercheck:「工具栏带以下」区域哈希 8/8 与上一轮**逐一相同**
  (证明本轮改动只落在工具栏带内);FUNC 断言 7/7 PASS。
- Tools/uicheck:69 passed / 0 failed。

## 7. 文档

Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第七阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第九轮」硬规则与踩坑记录。
2026-10-28 16:28:57 +08:00
DeepSeek V4.1 Flash ab19a6e72e feat(UI): 显示布局边界(Android 风格)+ 工具栏设置菜单(齿轮)+ 三处可见收益优化
## 1. 新功能:齿轮按钮 -> 应用设置菜单 -> 显示布局边界

- ToolbarAction::SETTINGS 位于「导入文件」左侧;工具栏布局改为
  「左侧固定簇(kLeftClusterCount = 2:齿轮 + 导入文件)+ 右侧按钮组」。
- 齿轮是矢量化绘制(8 齿 + 外环 + 轴孔,全部按 half 的比例推导),
  不用字体字符「⚙」——该字符在不同回退字体里大小与基线不一致,会忽大忽小、垂直不居中。
  UiButtonWidget 新增 UiButtonIcon(NONE / GEAR)与 SetIcon。
- MenuScope::APP_SETTINGS(新增)+ 设置菜单:HEADER「调试」+ 勾选切换项「显示布局边界」
  (keepOpen:点击后菜单保持打开,勾选态由 UpdateItemLabel 回写)。
  锚点取 Toolbar::ButtonRect(SETTINGS) 的控件实测矩形,宿主不再自己推算按钮几何。

## 2. 布局边界:基类给协议,组件自己上报

- UiComponent 新增 virtual PaintDebugBounds(g, scale),默认只画 Area()。
- Toolbar / TabBar 覆盖:跳过与区域重合的根控件,遍历 m_root->Children()(层级 1);
  TabBar 另上报每个 ✕(层级 2)。
- SignalPanel 覆盖:表头 / 筛选框 / 列表 / 样式区(1)+ 滚动条、样式行、当前可见列表行(2)。
- PlotView 覆盖:每个 LaneVisual::rect(1);不上报随数据变化的瞬态几何(曲线/胶囊/提示)。
- UiWidget 新增 PaintDebugBounds(g, scale, depth) 递归整棵控件树 —— 已上树的控件零成本获得边界。
- 宿主 PaintLayoutBounds 补客户区外框、分隔条、打开的菜单/拾色器。

视觉与实现约束(Android「Show layout bounds」风):
- 只描边、不填充(几十个嵌套矩形叠加填充会把内容糊住);
- 层级配色 青蓝(2px) / 品红 / 琥珀 / 绿(1px);
- 线宽按设备像素固定(不乘 scale),临时 PixelOffsetModeNone + 关抗锯齿 + 整像素对齐,
  保证 1px 就是 1px;
- ⚠️ 只在 Render 最后一步叠加,绝不进入任何位图缓存(PlotView 4 层缓存 / SignalPanel 行位图缓存),
  否则关掉开关后线条会残留;
- ⚠️ fullFrame 并入 m_showLayoutBounds:开启时必须整帧回送,否则「只回送绘图区」的快路径
  会让装饰区边界停在旧帧上。

## 3. 可见收益优化(行为不变)

- Toolbar::Layout:控件树改为按需重建(m_treeDirty),只在规格/字体/主题/缩放/动态文本
  变化时重建。拖动面板分隔条时 Relayout 每帧调用一次 -> 不再每帧 10 次堆分配 + 8 次测量缓存失效。
- Toolbar::SetActionLabel:只写 m_labelOverrides(唯一权威)+ 置树脏,消除「覆盖表与控件文本
  双写」可能不同步的问题。
- UiWidget::DispatchRedraw(event):新增;收敛 Toolbar / TabBar 里重复的
  Dispatch(); return UiRectHasArea(TakeDirty());(TakeDirty 会清零,漏一次就把下一次真实变化
  误判成「无变化」)。

## 4. 验证

- make release / make debug:零 error 零 warning;TargetCheck.exe 897,024 B(< 900 KB)。
- 新增离屏探针 .preflight/lbcheck:21 / 21 PASS
  (齿轮在导入文件左侧/同高/更窄;开启后青蓝 24,930 px、品红 19,734 px;关闭后逐像素还原;
   菜单 scope、锚定贴合按钮、宽度未被撑大、条目初始未勾选、点击翻转开关、菜单保持打开)。
- Tools/rendercheck:8 个场景的「工具栏带以下区域」哈希与改造前**逐一相同**
  (证明改动像素被完全限制在工具栏带内);整帧哈希因新增按钮整体变化,属预期。
  FUNC 断言 7 / 7 PASS。
- Tools/uicheck:69 passed / 0 failed。

## 5. 文档

Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第六阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第八轮」硬约束与验证坑。
2026-10-28 15:46:45 +08:00
DeepSeek V4.1 Flash e610cea481 UI 重构 P2 续:把布局与命中几何从绘制分片拆出(PlotViewPanels.cpp,逐像素 0 差异)
EdgePanelLayoutFor(651 行 / 19 个调用点)、EdgePanelWidth、LaneBadgeRect、
FreeMarkerValue/Delta/Contents/LabelRects、ProbeFreeMarker 都是「几何」而非「绘制」,
却塞在 9 千行的绘制文件里 —— 改布局要动整个绘制文件,回归面被放大。
本提交机械搬移这 5 个不连续块到新分片 PlotViewPanels.cpp(1,359 行),不改一行逻辑。

PlotViewPaint.cpp 6,645 -> 4,913 行;相对 UI_Refactor 起点 9,080 行共 -46%。
当前七个分片:Paint 4,913 / Interaction 3,430 / Features 3,266 / Raster 1,876 /
Panels 1,359 / Core 889 / CacheKey 474。顶层注释改为「七个分片(按关注点)」。

验证:
- make release TARGET_FILE_NAME=TargetCheck 零警告;Target.exe 877,568 B(< 900KB 阈值)。
- 逐像素 A/B:三场景哈希与迁移前完全一致
  A 2166933039626F66、B C444ECFEB18FD73D、C 7984446D8E7A4418。
- Tools/uicheck 62 passed / 0 failed。
2026-10-28 12:20:06 +08:00
DeepSeek V4.1 Flash 5cb84e4f97 UI 重构 P2:PlotViewPaint.cpp 按关注点分片(机械搬移,逐像素 0 差异)
PlotViewPaint.cpp 9,080 行里混住四类内容(真绘制 / 光栅化缓存 / 内容指纹 / 布局与命中几何),
按"函数名前缀"分片是上一轮膨胀的根因。本提交按关注点机械搬移函数定义,不改一行逻辑:

- 新增 PlotViewRaster.cpp(1,876 行):光晕离屏层、静态场景缓存、装饰层(AlphaBlend)、
  每网格宽基底图层/并行重建/滚动补条/预热预算。
- 新增 PlotViewCacheKey.cpp(474 行):SceneCacheKey、SceneContentHash/Geometry/Decor、
  样式与派生快照、只改外观的网格定位、样式补丁帧。
- PlotViewPaint.cpp 只留绘制:9,080 -> 6,252 行(-31%)。
- 顶层注释从「四个分片」改为「六个分片(按关注点)」,并保留
  「PlotView::Paint/PaintLane 只允许定义在 PlotViewPaint.cpp」。

pvdetail 全部 inline,故跨分片安全;无需改任何 include,Makefile 按 basename 自动收集。

验证:
- make release TARGET_FILE_NAME=TargetCheck 零警告;Target.exe 873,984 -> 875,008 B(+1KB 对齐差异)。
- 逐像素 A/B(仓库外一次性渲染哈希探针,三场景:全览 / 深放大+自由标记+均值滤波+弱峰调整+时间光标 /
  再平移一帧):分片前后哈希完全相同
  A 2166933039626F66、B C444ECFEB18FD73D、C 7984446D8E7A4418。
- Tools/uicheck 62 passed / 0 failed。

坑:用旧文件回退做 A/B 时必须先删除 Build/<Config>/PlotViewRaster.o 与 PlotViewCacheKey.o,
否则链接会因重复符号失败。
2026-10-28 12:18:42 +08:00
DeepSeek V4.1 Flash df715a035e 修复浮动面板事件穿透 / 自由标记总是预览最近信号 / 固定点位于GX与图例之下 / 预览曲线取值插值;体积红线放宽到900KB
1) 浮动面板不得被右键与中键穿透(用户报告:在面板上右键会弹出下面网格的右键菜单)
   - 新增 PlotView::OnRButtonDown:TopPanelAtPoint 命中即置顶并消费该次右键;
     Application::WM_RBUTTONDOWN 在「绘图区右键 → 网格菜单」之前调用它。
   - OnMButtonDown 开头同样判定面板:命中即消费,不再进入平移。

2) 「自由标记点选取」总是预览网格内离鼠标较近的信号(用户报告:单信号时也必须贴近曲线)
   - ProbeFreeMarker 删除 `if (!hitCurve) continue;`,改为两候选取更近:
     ① 鼠标到画出来的折线的垂直距离(阶跃竖直段只有它能命中);
     ② 同一时刻(鼠标时刻吸附最近采样)采样点的屏幕纵向距离。
   - 门槛 tolerance 只用于「已吸附」圆环;固定点仍恒吸附到真实采样时刻。

3) 固定标记点位于 GX 编号徽标与图例之下(用户要求:数据显示不得遮挡它们)
   - PaintFreeMarker 把 m_legendHits 托底矩形与 LaneBadgeRect 用 gd::Region::Exclude
     排除出绘制区;仅在确有固定点/预览时才计算。
   - PaintFreeMarkerCapsule 新增 vectorText:胶囊与遮挡矩形相交时文字改走 GDI+
     (GDI DrawTextW 不认 GDI+ 裁剪区)。

4) 预览曲线(中心轴/均值滤波/弱峰调整)的光标取值点落回曲线
   - paintDerivedCapsule 改为在光标准确时刻按时间线性插值,且先 ApplyDrawTransform 再插值
     (绝对值/垂直镜像非线性,先插值会偏离折线);双光标增量同样插值。

5) 产物体积红线由 800KB 放宽到 900KB(到达阈值时提醒,不是禁止超过)。

验证(全离屏,未占用鼠标/键盘/屏幕):仓库外临时探针 25/25(A 面板右键/中键不穿透且面板外仍生效;
B 远离曲线可预览与单击固定、两信号按纵向距离择优;C 图例与 GX 区域像素差 0、整幅差异 5 万像素;
D 阶跃区标注点与曲线差 0.0px,插值点与最近采样点相差 39px/197px);A/B 恢复旧门槛 ⇒ 5 项失败,
改回最近采样取值 ⇒ 均值/中心轴各偏 36px 失败。make release / make debug 零警告;
Target.exe 860,672 字节(840.5 KiB)。
2026-10-27 20:14:49 +08:00
DeepSeek V4.1 Flash affb9cc340 目录规范化(Inc/Src)+「自由标记」第六轮交互调整
1) 目录规范化:所有 .hpp 只放 Inc,Src 只放 .cpp
   - 4 个 *Internal.hpp 从 Src 迁到 Inc(DataModel/Application/PlotView/SignalPanel)
   - Makefile 固化该约定(注释必须单独成行:写在同一行会把 CXX_INCLUDES_PATHS 整条注释掉)
   - include 全是扁平形式,搬家无需改任何 include

2) 常规模式 = 普通鼠标(未开启「自由标记点选取」)
   - ProbeFreeMarker 早退:不预览、不画纵向光标、单击不固定
   - 补 if (!hitCurve) continue:否则整列光标下 probe.ref 恒有效,任意位置都能固定
   - 固定点仍恒吸附到真实采样时刻;「已吸附」圆环只在稀疏判据成立时画

3) 两个显示开关:按钮改名「自由标记点选取」,其下新增「显示增量 | 显示枚举文本」
   - 默认关闭,只影响已固定点的胶囊文本;预览胶囊恒为「值 = 枚举」
   - 开关行计入面板高度与内容区高度

4) 面板六列:新增恒存在的「序号」列(组内 1 起)
   - 枚举列存在时,无枚举/对不上任何枚举项显示 N/A
   - 列与列之间画竖线;悬停某行时其它固定点封顶 50% 不透明度,移出恢复
   - 删除固定点 ✕ 的悬停浮动提示(PaintFreeMarkerTooltip 实现与声明全部移除)
   - 开启选取模式的网格上,信号悬停提示只显示信号名

5) 缓存键补齐
   - FreeMarkerContents 混入枚举表指纹(旧键会让加上枚举表后仍显示 N/A)
   - PanelSlotKey 混入两个新按钮矩形与开关状态

6) 注释/文档
   - 修正 FreeMarkerProbe、SetLaneFreeMarkerCursor、标记透明度等处的过期说明
   - 空态提示按是否开启选取模式区分文案

验证(全离屏,未占用鼠标/键盘/屏幕)
   - 新增 .preflight/mark5check 52/52
   - 回归:markcheck 67/67、mark4check 40/40、fixcheck 37/37、round3check 31/31、
     round4check 34/34、round5check 5/5、editcheck 20/20、convcheck 18/18、diag 19/19
   - check 136/137(唯一失败项为既有不稳定项,drift=15818 与改动前逐位相同)
   - make release / make debug 零警告;Release 产物 TargetCheck.exe 857,600 字节
2026-10-23 15:50:55 +08:00
DeepSeek V4.1 Flash 79eebc281e 自由标记:面板表头/吸顶/行选中与悬停联动 + 原位显示 + 探测容差与阶跃吸附
- 面板:每个展开分组下方新增一行列表头(标记值/枚举文本/增量/时间点/时间间隔),
  与数据行共用列边界;列宽同时容纳列标题;表头行计入内容总高与面板高度;
  全部无枚举文本时仍整列缺省
- 吸顶:分组栏与表头各自按 max(自然, min(吸顶, 组下边界-自身高)) 钉住,
  组末尾按"表头先走 -> 条目 -> 分组栏最后离场";绘制顺序改为条目->表头->吸顶分组栏,
  被盖住的条目不绘制也不可命中
- 行交互:Lane::freeMarkerSelected(单击选中/再点取消)+ 独立的整行悬停状态
  (整行悬停不会把该行 ✕ 变成 danger 色);悬停时网格内对应固定点整体高亮
  (数据点圆环 + 胶囊强制不透明 + accent 描边,仅视界内)
- 固定点恢复原位显示(删除纵向避让,允许遮挡,回到初版方案)
- 探测判据重写:鼠标到画出来的折线的垂直距离 <= kSignalHitTolerance(与曲线高亮同源),
  命中后吸附到最近采样时刻 => 修复阶跃竖直跳变线无法探测/固定的问题;
  删除已无用的 kFreeMarkerHoverTolerance
- 修复滚动时条目文字串到面板标题栏(GDI 文本不认 GDI+ 裁剪,改用 UiTextClipped)
- 顺带清理过期注释(四列->五列、折起指示器已无横线、探测判据说明),
  RemoveFreeMarker 改为复用 RemoveFreeMarkerAt(选中下标校正只有一处)
- 验证:新增离屏 .preflight/mark4check 38/38(三组 A/B 均如实失败);
  回归 markcheck 61/61、fixcheck 37/37、round3check 31/31、round4check 34/34、
  round5check 5/5、editcheck 20/20、convcheck 18/18;make release/debug 零警告
2026-10-22 21:04:03 +08:00
DeepSeek V4.1 Flash 8f735d51be 修复拆分网格误继承实验性功能 / 阶跃线宽 / 面板拖拽残影 / 自由标记卡顿与交互
- 拆分网格:实验性功能只由原网格的首个信号继承(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 零警告
2026-10-21 17:21:09 +08:00
DeepSeek-V4.1-Flash 3400ac7c29 自由标记体验重做 + 全部实验性面板可折起
- 网格内固定点:取值胶囊按已放置矩形做纵向避让(不再互相遮挡);行尾 ✕ 改为与
  胶囊同高的正方形;移除固定点的纵向半透明竖线
- 新增「标记透明度」(0–100%),作用于本网格全部固定点的圆点/胶囊/描边/文字/✕;
  半透明时文字改走 GDI+(GDI DrawTextW 忽略 alpha),100% 时像素零变化;
  ≤2% 时不绘制且不登记 ✕ 命中矩形(看不见的按钮不可点)
- 面板按信号分组:信号色托底分组栏(展开三角 + 信号名 + N 标记点 + ✕,文字/三角/✕
  一律用自适应对比色),点击分组栏收起/展开;条目五列(值/枚举/增量/时刻/间隔),
  首项增列与 Δt 显示 N/A,整个面板没有枚举文本时枚举列整列缺省;行尾仍有 ✕
- 面板内容超高时右侧出现滚动条(分组栏与条目一起滚动),滚轮在内容区翻页;
  底部按钮与「标记透明度」滑块固定在底部、不参与滚动
- 底部按钮改为「光标选取模式」切换类按钮(外观与工具栏切换按钮一致,激活高亮),
  激活时其上方出现「标记透明度」滑动条(与信号样式区的透明度滑块同款)
- 所有实验性功能浮动面板支持展开/折起:标题栏 GX 徽标左侧新增「三角 + 横线」指示器,
  单击切换、双击标题栏切换;折起的面板只有标题栏
- 数据层:LanePanelState.collapsed、Lane.freeMarkerOpacity/freeMarkerScroll/
  freeMarkerCollapsedGroups,LaneCarry 与 SplitLane 一并继承
- 验证:新增离屏 .preflight/perf/mark2check 69/69(含两组 A/B);回归 datacheck 69/69、
  verify 49/49、panelanim 158/158、errcheck 19/19;make release/debug 零警告
2026-10-21 02:59:05 +08:00
DeepSeek V4.1 Flash 067128f46c 自由标记改进:默认不启用光标模式 + 面板四列对齐 + 光标模式切换按钮
用户需求(逐条落地):
1) 默认不启用网格私有光标,只有鼠标处于信号上方时才显示数据点数值;命中
   「自动点线」稀疏判据的信号,只有靠近数据点时才有数值显示;
2) 有数值预览时左键单击固定该数据点(固定点样式不变:半透明纵向细线);鼠标
   下方没有信号曲线时,按下/拖动就是普通的平移视界(选区放大模式下照旧框选);
3) 浮动面板最下方新增切换按钮「启用光标模式」↔「关闭光标模式」;启用后与最初
   实现一致(跟随鼠标的纵向自由光标),并修掉「光标模式下拖不动浮动面板」;
   鼠标处于浮动面板上时网格不画自由光标,面板上的操作不穿透到网格;
4) 面板只列出确实有固定点的信号(否则该信号名条目不出现);
5) 每个信息元素纵向对齐:取值 / 增量 / 时刻 / 间隔各自一列。

数据层
- Lane 新增 bool freeMarkerCursor(默认 false,功能总开关仍是 freeMarker);
  LaneCarry 同步携带。
- PlotView 新增 SetLaneFreeMarkerCursor / LaneFreeMarkerCursor /
  ToggleLaneFreeMarkerCursor,切换时立即作废属于该网格的探针。

探针与绘制
- FreeMarkerProbe 新增 cursorMode。
- 非光标模式:稀疏判据成立但吸附失败 ⇒ 跳过该信号;其余要求鼠标与曲线纵向距离
  在 kFreeMarkerHoverTolerance(8 逻辑像素)以内;都不满足 ⇒ 探针无效 ⇒ 按下即
  普通平移/框选,不会误固定数据点。
- 光标模式:与之前实现一致(网格内始终有效)。
- PaintFreeMarker:纵向线 + 上下把手只在光标模式绘制;取值胶囊/数据点/吸附圆环
  两种模式都画。

浮动面板
- 底部新增「启用光标模式 / 关闭光标模式」切换按钮(layout.button),手型光标 +
  悬停高亮,PanelSlotKey 混入 freeMarkerCursor。
- 只给有固定点的信号发名称行(高度分支与矩形分支同步跳过空信号)。
- 四列固定横坐标(EdgePanelLayout::markerColumns:取值 / 增量 / 时刻 / 间隔),
  面板宽度 340 → 415(= 4 列 + 间隙 + 行 ✕ + 两侧内边距);绘制一律左对齐 +
  省略号截断,双击分行改按列边界判断(不再用 xxxRight - 40*scale 反推)。

修复的缺陷(用户报告)
- 「光标模式下拖不动浮动面板」的真因不是命中判定,而是 OnMouseMove 的
  EDGE_PANEL_MOVE 分支里 switch (m_edgePanelDragKind) 漏了 MARK:拖动坐标被写进
  lane.edgePanel,而布局从 lane.freeMarkerPanel 读 ⇒ 面板纹丝不动。已补 case。
- 鼠标压在浮动面板上时:OnMouseMove 先 TopPanelAtPoint,命中即清空探针并跳过固定
  点 ✕ 的悬停判定;OnLButtonDown 的自由标记分支增加 freeOverPanel 守卫,
  确保面板操作不穿透到网格(也不再绘制穿过面板的自由光标)。

验证(全程离屏,不占用鼠标/键盘/屏幕)
- .preflight/markcheck 重写并扩到 61/61:默认无光标、贴近曲线才有效、远离曲线单击
  不固定且拖动仍是平移、四列几何对齐、按钮切换光标模式、光标模式下拖动面板确实
  移动、面板上不画光标、选区放大共存、Δt 编辑重排、两处 ✕、空信号不出行、深度
  放大吸附、视界外固定点不绘制。
- 回归:editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、
  convcheck 18/18、check 136/137(唯一失败项是此前就存在的不稳定断言)、真实
  MF4-Record.MF4 仍 283 条信号。
- make release / make debug 零警告;Build/Release/Target.exe 834,560 字节。
- 同步更新 Documents/agent_mem.md(§11.2 第 189–193 条 + 文末本轮章节)。
2026-10-20 20:39:42 +08:00
DeepSeek V4.1 Flash dd8f63aa0e 新增实验性功能「自由标记」:网格内跟随鼠标的纵向光标 + 单击固定数据点 + 浮动面板
需求(用户):网格右键菜单「实验性功能」里新增带 √ 的「自由标记」。开启后网格内跟随鼠标
出现一条仅限本网格的纵向光标(上下各一个把手、不显示时间值),穿过信号时只标注离鼠标
最近的数据点;左键单击把该点固定(带 ✕ 可移除,鼠标移开仍显示、超出视界直接不显示),
后续固定点显示为相对前一个的增量;面板按信号分组列出「值(±增量) = 枚举」+「t:… Δt:…」
(两列右对齐、可双击按秒编辑并自动换位);命中自动点线判据的信号要有吸附提示;
两级 ✕ 都要悬停提示;与选区放大共存时原地单击 = 固定、按下拖动 = 框选。

一、数据层
- Lane 新增 freeMarker 开关、freeMarkerItems(FreeMarkerItem{ref, time})、freeMarkerPanel。
  time 是「显示时刻」(画在 TimeToX(time) 处),与光标同一口径;取值必须走
  SampleDisplayedSignalAt(内部处理时间偏移与水平镜像)。
- 新增 AddFreeMarker / RemoveFreeMarker / RemoveFreeMarkerAt / RemoveFreeMarkersOf /
  FindFreeMarker / HasFreeMarkers。同一信号同一时刻不重复加入(相对容差 1e-12);
  freeMarkerItems 保持插入顺序,显示顺序与「前一个固定点」一律由 SortedFreeMarkerIndices
  按时间排序得出(删除某项不打乱其它项下标)。
- LaneCarry 同步携带,勾选/取消勾选信号导致网格重建时不会丢掉已固定的点。

二、网格内交互
- ProbeFreeMarker:仅当鼠标所在网格开启该功能时有效;在网格内全部可见信号里挑屏幕纵向
  距离鼠标最近的数据点。MinorCellSparse 成立时经 SnapTimeToSample 吸附到真实采样时刻,
  绘制侧画一圈圆环作为吸附提示。吸附只看判据,不看当前绘制方式。
- PaintFreeMarker(接在 paintOverlays 里、浮动面板之前):纵向线只在本网格内,上把手尖端
  朝下(与常规光标顶部把手一致)、下把手尖端朝上,不画时间读数;已固定点画低透明度细线 +
  圆点 + 「值(±增量) = 枚举」胶囊 + 行尾 ✕(悬停高亮 + 提示气泡);
  超出视界直接不显示(不复用常规数据点的贴边定格 + 方向三角)。
- 网格内固定点 ✕ 的命中矩形由绘制时登记(m_freeMarkerHits),因为它的位置取决于取值文本宽度。
- OnMouseLeave 清跟随光标与三处悬停态(已固定的点保留)。

三、单击 vs 框选
- OnLButtonDown 在开启该功能的网格上只登记 m_freeMarkerPendingFix,并把拖拽模式设为
  「选区放大 ? RUBBER_SELECT : PAN」;OnLButtonUp 里只有「整段操作没有拖动且选区轴向未定」
  才 AddFreeMarker,否则照旧框选/平移。

四、浮动面板(EdgePanelKind::MARK / PLOT_CMD_LANE_FREE_MARKER = 12)
- 每个可见信号一行名称(右侧带清除该信号全部固定点的 ✕),名称行下方按时间升序列出该信号的
  固定点;没有任何固定点时第一行给灰色提示。
- 行 = 左侧「值(±增量) = 枚举文本」(无尖头括号、首项无增量)+ 右侧两列各自右对齐的
  「t:<时刻>s」与「Δt:+<复合单位>」(自动单位匹配)+ 行尾 ✕。
  两列右边界存进 EdgePanelLayout(markerTimeRight / markerDeltaRight),保证所有条目纵向对齐。
- 双击行:左半编辑自身时刻(FREE_MARKER_TIME),右半编辑相对前一固定点的间隔
  (FREE_MARKER_DELTA,最左边的点没有前一项所以不进入编辑)。提交后只改 time,
  顺序由排序函数自动重排(越过后续点即交换条目位置)。
- PanelSlotKey 把每个固定点的信号 + 时刻、全部 marker 行矩形与两列边界一起 mix。

五、一并修掉的两个自伤
1. EdgePanelWidth 的 MARK 分支原本逐行取采样值算文本宽度 —— 它被 EdgePanelLayoutFor 调用,
   而后者在 TopPanelAtPoint / SyncPanelOrder 里每次鼠标移动调用数次,等于「鼠标一动就做几十次
   采样查询」。改为固定宽度 kFreeMarkerPanelWidth(340 逻辑像素)+ 绘制侧 UiEllipsize。
2. PaintFreeMarker 与跟随光标的胶囊绘制原本是两份重复代码,抽 PaintFreeMarkerCapsule 共用;
   FreeMarkerLabelRects 增加 text 出参,避免绘制侧重复求解一次取值。

验证(全离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/markcheck 31/31:探测有效与就近选择、原地单击恰好新增 1 个固定点、
  选区放大共存(单击固定不缩放 / 拖动缩放不固定)、面板行数与时间升序、首项无增量、
  后续项增量与 Δt 正确、两列右对齐、行尾 ✕ 只删 1 项、信号名 ✕ 清空该信号、
  编辑 Δt 后移动并重排、深放大时吸附且时刻精确等于采样时刻、视界外固定点不绘制。
- 回归:editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、
  convcheck 18/18;check 136/137(唯一失败项是本轮之前就存在的不稳定项,上一轮已用
  git stash 在基线复现同一数值);真实 MF4 仍 283/8/18/24 个信号。
- make release / make debug 零警告。
- TargetCheck.exe 833,536 字节(814.0 KiB)—— 比上一轮 +24 KB,首次超出旧的 800 KiB
  红线约 14 KB。已做的瘦身见上;剩余为新功能的真实代码量,若要回到 800 KiB 以内需要
  改变取舍(动态 CRT / UPX / 砍其它功能),本轮按「实现功能优先」未动。

文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 181-188 条硬约束
与文末本轮小节。
2026-10-20 19:47:55 +08:00
DeepSeek V4.1 Flash 53b981a2a9 全项目大检查:修复数据层/UI 真 BUG、清理死代码与过期注释、并评估二进制体积
按「性能 / BUG / 错误 / 过期注释 / 命名与实际含义不符」四项对全仓做了一次审查,
四个只读子审查交叉核对后逐条回到源码确认再改。

一、数据层真 BUG
- MatH5:U8/U16/U32/U64 用逐字节 Cached() 拼装,而 Cached 只服务「完全落在同一个
  64KB 块内」的请求 => 跨块时静默读成 0(U32 整段为 0、U64 高 32 位为 0),分块 B-tree
  的 chunk 偏移/子节点地址被破坏且无任何报错。新增 ScalarLE():命中缓存用缓存,
  否则退回一次 ReadAt;U64 顺带从 8 次缓存查找降到 1 次。
- Mf4Loader::IsBlock 未校验链接表是否落在块内 => DataOffset 越界,随后
  block_len - (data - offset) 在 uint64 上下溢,得到一个约 2^64 的"可用字节数",
  下游 vector(usable+1) 直接 bad_alloc。同时把 offset + bytes > size 改成不产生回绕的写法。
- DecodeRawValue 的整数路径:bit_offset%8 != 0 且位宽 64 时需 9 个字节,先拼成 64 位
  再整体右移会把最高字节静默丢弃 => 改为逐字节带位偏移累加。
- cg_cycle_count 加「数据段总字节 / 记录字节」上限(压缩段按 deflate 最大膨胀比估算)。
- CsvLoader 表头分支同样跳过 #/% 注释行(否则整份文件按 1 列解析并被丢弃)。
- MatLoader:double -> int 的越界转换加了范围检查;普通对象递归加深度上限与成环检测。
- Utf8Util::BuildTicks:double -> long long 越界转换前先判商是否落在 ±9e18 内。

二、界面 BUG
- 工具栏按钮永远不显示悬停高亮:Paint 读的 Button::hover 全仓从未赋值,改为按 m_hoverIndex 判断。
- 菜单打开时"取消底层拖拽"对每个消息都执行,把菜单自己的 Target::EDIT_FIELD 也清掉
  => 菜单内联输入框无法拖拽框选。排除 EDIT_FIELD。
- GdiplusShutdown 之前未释放 PlotView 的 GDI+ 位图(Application 是进程单例,成员析构在
  WinMain 之后)=> 新增 PlotView::ReleaseGraphicsResources() 并在 Shutdown 里调用。
- WM_KILLFOCUS 现在会提交标签页/绘图区编辑并复位捕获与拖拽状态。
- WM_LBUTTONUP 不再抢走中键平移的鼠标捕获。
- TextEdit:被过滤器拒绝的字符返回 false;无选区时 Ctrl+X 只复制、不再清空整段文本。
- SignalPanel::InvalidateRows 作废编辑/待确认/改名计时等行下标状态。
- EndFilterEdit 改走 ApplyFilterText(否则输入纯空格后回车,列表仍停在筛选结果)。
- 新增 SignalBadgeVisible 作为唯一判据(原来命中只看"可见",绘制还要求"已分配网格")。
- 折起动画结束后夹取滚动量;双击分组名改名时清掉该组正在播放的折起动画。
- ScrollToSignal 复用 SignalScrollTarget(含悬浮组名避让)。
- RowNameRect 与绘制使用同一最小宽度(原来退化时返回空矩形)。
- 分隔条拖拽上限改用与 Relayout 同一公式;UpdateDragCursor 补 SPLITTER/EDIT_FIELD。
- SignalPanel 里几个守卫改为判断真正被解引用的指针;分组行 ✕ 也返回手型光标。

三、PlotView
- FindStyleOnlyLanes 改用完整的 SceneStyleEntry::operator==(原来只比 4 个字段,
  另一个网格同时改"调整"时带着旧像素被当成最新)。
- 宽基底图层不再烘焙拖拽光晕(LaneLayerHash 不含它),改由 PaintHoverHighlight 每帧叠加。
- PaintLaneLegend 提前返回前清掉本网格的命中矩形(否则能点中看不见的图例条目)。
- 面板主体改为完全不透明(消除平移后 4% 透出的旧波形,零额外代价)。
- PAN / Shift+滚轮改走 SetTimeView,使"自动点线"判据随平移重算。
- 内联输入框命中字体与绘制统一为 monoSmall。
- 派生曲线取值与窗口按 SourceIndexFor 映射(水平镜像时不再读到镜像另一端)。
- EdgePanelWidth 补 style/axis 空指针守卫;二分结果夹到 count。

四、清理
- 删除 Toolbar::ActionLabel、Theme::statusBg、PopupMenu 的 editResult 死机制、
  Toolbar::Layout 的重复 clear()、HitRow 的恒假分支、OnDoubleClick 的重复判定、
  PaintUtil 里孤立的注释块。
- 修正一批过期/错误注释(包络带"不做加宽"、LaneLayerHash 的图例说法、
  均值滤波曲线的窗口说法、样式区行数与「调整」按钮顺序、FIT_ALL 退出缩放模式、
  RemoveSignal 的 ✕ 位置、.mat/.dat 格式文档、MatH5 的"映射视图"、DataModel 的
  "点线/被移除文件"、SignalPanel 的"行高连续缩小"、TextEdit 的 PaintUtil 引用等)。

五、二进制体积评估(按要求只评估,未做压缩修改)
- release 809,472 字节;.text 652KB、.xdata+.pdata 约 75KB、.rdata 42KB、
  .rsrc 20KB、.idata 11.5KB。
- 去掉 -ld3d11 -ldwmapi -ld3dcompiler(仓内无任何调用)实测 0 字节收益
  (静态库只拉取被引用的对象)=> 删链接库不是压缩手段,只是配置整洁。
- 仓内已无 <iostream>/<sstream>/<filesystem>/<regex>/<locale> 等体积炸弹。
- 结论:已无明显的浪费可捡;要显著变小只能改变取舍(动态 CRT / UPX / 砍功能),
  需要用户决策,本轮未动。

验证
- editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、convcheck 18/18。
- check 136/137:唯一失败项经 git stash 回到改动前复现同一数值,属该探针自身的既有
  不稳定项(取决于前置用例留下的缓存/时序),与本轮改动无关。
- 真实数据:4 个 MF4 => 283/8/18/24 个信号;.mat 样例 8 信号、
  带无障碍对照-防夹力曲线数据 8 信号、BIG_MAT.mat 5 信号(私有内存 +741MB)。
- make release / make debug 零警告。

文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 171-180 条硬约束
与文末本轮小节(含体积评估数据)。
2026-10-20 16:10:29 +08:00
DeepSeek V4.1 Flash 30f853a361 MF4 ##CA 全字段/三维 + cc_type=8 区间枚举 + 稠密包络带线宽双向加粗
一、##CA 数组通道:从「按魔数猜」改成「按记录容量校验」
- 头字段全部按块长度做边界检查后读取:+0 ca_type / +1 ca_storage /
  +2 ndim(U16) / +4 flags(U32) / +8 ca_byte_offset_base(U16) /
  +10 ca_inval_bit_pos(U16) / +16 dim_size[](U64,步长 8),
  其后才是**可选**的 cycle_count[](U32)。
  ⚠️ 实测 CANape 写出的文件没有 cycle_count(块长度恰好到 dim_size 就结束),
  盲读会把下一个块头当数字(实测得到 1481909027)。
- 展开条件(任何一条不满足就原样保留,只显示元素 0,绝不猜):
  ca_storage == 0;ca_type <= 2(0 数组 / 1 缩放轴 / 2 区间轴,三者物理布局相同);
  cn_bit_count ∈ [1, 64];cycle_count 每维 <= 1(元素不被拆到多条记录);
  bit_offset + 元素数 × 位宽 <= cg_data_bytes × 8(真正的完整性校验)。
  规模护栏:元素数 <= 16384 且 元素数 × 记录数 <= 32M 单元格
  (取代原来的 count > 1024 魔数)。
- 元素名按 dim_size 从末维向前取余,1/2/3 维分别得到 [i] / [i][j] / [i][j][k]。
- MF4-Record.MF4 仍是 283 个信号(25 个 ##CA 全部通过新的容量校验,最大 7x9=63)。

二、cc_type = 8(值 -> 文本,区间表)补齐
- cc_val[] 交错存放 (下限, 上限),cc_ref[4 + i] 是第 i 段文本(最后一个引用是默认文本,不使用);
  cc_val_count 必须是偶数(奇数说明表残缺,整表丢弃);下限 > 上限时交换。
- SignalEnumText 新增 rangeMin / rangeMax / rangeTexts,并拆出 HasExact() / HasRanges();
  Find() 先查精确表(cc_type = 7)再查区间表(cc_type = 8,闭区间)。
- 界面没有改动:两种枚举共用同一出口,只在**光标取值标注**追加 " = <文本>",
  常规状态零变化。

三、修复:稠密包络带下改变线宽时竖直沿不变粗
- 根因:包络带每列只有 1 个 x 坐标,旧实现只在 top/bottom 各外扩半个线宽
  => 水平段会变粗,但方波竖直沿(上升/下降段)的横向宽度恒为 1 个像素列。
- 修法:EmitEnvelopeBand 新增 halfWidth 形参,先对上下包络做横向窗口膨胀
  (top 取窗口最小、bottom 取窗口最大,半径 ceil(halfWidth / 列距)),再纵向各外扩;
  膨胀只在同一段内生效,不会跨 NaN 断点连线。

验证(全离屏,不占用鼠标/键盘/屏幕)
- .preflight/convcheck 18/18:新增 3 维命名与取值、元素放不进记录时必须拒绝展开、
  cc_type = 8 的命中/未命中。
- 新增 .preflight/round5check 5/5:合成 20 万采样数字信号(必然走包络带),
  在网格中间行测量竖直沿横向像素宽度:线宽 1px -> 6px,10px -> 30px。
- A/B:临时把横向膨胀半径置 0(等价旧行为)=> 1px -> 2px、10px -> 2px 且如实 FAIL。
- 回归:check 137/137、editcheck 20/20、round3check 31/31、round4check 34/34;
  真实 MF4 信号数 283/8/18/24 不变;make release / make debug 零警告;
  TargetCheck.exe 808,448 字节(789.5 KiB)。

文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 165-170 条硬约束与文末本轮小节
(含「6 位有效数字」的口径说明)。
2026-10-20 14:29:49 +08:00
DeepSeek-V4.1-Flash 181f95cc3a MF4 补 ##SD/##SI/VLSD + 信号枚举文本 enumText + .mat 改随机读(流式进度)
1) 修复上轮记录的两个已知问题
   - MatLoader: LoadPlainObject 不再为拿 dims 调 ReadDataset(整块解码第二次),
     改用 ReadDatasetInfo;普通 v7.3 大矩阵的时间/内存不再翻倍。
   - MdfFile::TextBlock 注释与实现对齐:真的支持 ##MD(UTF-8 XML,无长度前缀)。

2) 信号枚举信息(以 TestData/NEW-MF4.mf4 为目标)
   - 新增 SignalEnumText(DataTable.hpp):values[] + texts[] + Empty()/Find(),
     命名用 enumText —— 它描述"取值代表哪个状态",不是单位。
   - DataTable::columnEnumText 与 columns 同下标;SignalData::enumText 由
     BuildSignalGroup 移动赋值;其它格式(CSV/.mat)保持为空。
   - MF4 cc_type = 7:cc_val[i](F64,数据段 +24)↔ cc_ref[4 + i](##TX/##MD 文本),
     最后一个引用是默认文本,不使用。
   - 界面只在**光标取值标注**追加 " = <枚举文本>"(等号两侧各一个空格、无尖括号),
     查表用采样原值(ApplyDrawTransform 之前);常规状态显示一个像素都不变。
   - 实测:NEW-MF4.mf4 8 信号 / 4 条带枚举(32+5+32+5),MF4-Record.MF4 另有 2 种(12/32)。

3) MF4 支持范围
   - ##SI 采样索引:只有索引 ≥2 条且全部 < 周期数才生效(逐记录有效性位图)。
     实测部分工具给每个通道挂只含常量 513/4 的 ##SI,必须忽略,否则整列变 NaN。
   - ##SD + VLSD(cn_type == 1):##SD 数据段是「UINT32 长度 + 字节」样本序列,
     记录里放 64 位偏移;AppendSignalData 递归展开 SD/DZ/DL/HL(单通道上限 512MB),
     DecodeVlsdValue 先按直接偏移、失败再按 >>1 回退,取样本第一个元素。
   - CN 的 cn_data / md_unit / md_comment 下标按链接总数反推:8 链接(MDF 4.0 风格)
     = 换算[4]/数据[5]/单位[6]/注释[7],9 链接(4.1)= [5]/[6]/[7]/[8]。
     实测 Simulink 导出的“4.10”仍写 8 链接,不能写死 Link(cn,4/5)。
   - 主通道:cn_sync_type 1/2/3(时间/角度/距离)→ 名字 t/time/timestamp/zeit/时间
     (含 t[...])→ 第一个数值通道兜底;cg_flags 第 2 位(远程主通道)跳过 sync 判定。
   - 记录长度优先 cg_data_bytes(CG 数据段 +24);旧实现把 U8(cgData) 当
     cg_record_id_len,其实是 cg_flags 低字节(总线事件位)。
   - 进度:先走 DG 链得到真实组数(不再写死 64),hasBytes = true,bytesRead 取
     MdfFile::ReadAt 累计(夹到文件大小);4MB 分块读取检查取消。
   - ##DZ:数据段前有 28 字节头(original_type/zip_type/param/original_size/zip_size),
     旧实现整段当 zlib 流是错的;只支持纯 deflate(zip_type = 0)。
   - 一个信号都没有时,错误信息附带「布局无法解析 / 缺少时间通道 / 空数据组」计数。

4) .mat:内存映射 → 随机读 + 元数据块缓存(消除 SEH 风险)
   - MatHdf5::Open 改为 CreateFileW(FILE_FLAG_SEQUENTIAL_SCAN) + ReadFile;
     删除 MappedBytes,改为 ReadAt / Cached(16 × 64KB LRU + 上次命中槽)/ Tag /
     ReadData / ReadCString。I/O 失败只会变成 false,不再触发页访问异常。
   - 数据读取:连续布局按 4MB 切片;分块布局按需 + 64KB 对齐。
     固定 4MB 预读在交错存放的分块数据集上会把读盘量放大到 2.2 倍(2874MB/1293MB),
     改后 1301MB。
   - 关键提速:dims=[N]、chunk=[32768,1] 归一化后 rank=2、每行 1 字节,逐行算坐标
     是 12.9 亿次迭代;新增「rank == 2 且 chunkDims[1] == dims[1]」快路径(整块一次
     sink)。BIG_MAT.mat(1.29GB / 5 × 16,142,471):21.5s → 7.9s,私有内存 +741MB。
   - 进度:Open 立刻上报「0 / 总大小」,之后每读一块回调 ReadProgress(bytesRead,total)
     → MatLoader 映射到 0.02–0.98 并单调夹紧(字节进度与逐流解码取较大值),收尾 1.0。
     遮罩层百分比不再一上来就 100%;取消会中止(StreamDataset 返回 false),不留 NaN 空洞。
   - 连续布局数据不足(文件被截断)现在明确失败,不再静默截短。

验证(全部离屏:无可见窗口、不注入鼠标键盘屏幕)
- 新增 .preflight/perf/loadercheck(数据层)32/32:
  NEW-MF4 8 信号 / 4 条枚举 / 枚举文本逐条核对 / 9396 与 94 行;
  MF4-Record 124 信号 / 3 轴 / 4 条枚举;data-sample.mat 4 组 8 信号 / 行数;
  BIG_MAT.mat 5 信号 × 16,142,471 + 进度(total 已知、ratio 单调、bytes 递增、
  起点 ≤0.05、终值 1.0、更新 ≥10 次)。
- 新增 .preflight/perf/enumui(真实 Application + SW_HIDE)12/12:
  只显示 gVR_WinOpenReq[0],光标落在枚举采样点上 —— 标签宽度 265px;
  临时清空 enumText 后 25px;差值 240px 与 " = WinOpenReqAtDrvr_Idle" 的
  UiMeasureTextWidth 完全一致;恢复后宽度回到 265px。
- 回归:datacheck 69/69、matcheck 21/21、matui 15/15(loading bytes 达到总大小)、
  verify 49/49。
- make release / make debug 零警告。Build/Release/TargetCheck.exe = 820,224 字节
  (上轮 785,920,+34KB)。(Target.exe 正在被用户运行的程序占用,链接步骤未覆盖它。)
2026-10-20 01:56:47 +08:00
DeepSeek V4.1 Flash 75f0fafd41 .mat 改为流式解码(内存映射 + 逐 chunk 填充);MF4 支持 ##DL/##DZ/##HL;光标数据点被时间面板遮住时改三角
1) .mat(BIG_MAT.mat 1.29GB / 5 信号 x 16,142,471 采样)
   旧链路同时驻留三份内存:整文件 fread 进 vector、整个数据集解压成字节数组、再 memcpy 成
   double;外加 streams 缓存与每条信号各一份时间列拷贝。实测私有内存 +1233.9MB、117s。
   现改为:
   - MatHdf5::Open 用 CreateFileW + CreateFileMappingW + MapViewOfFile(MappedBytes 只读视图,
     解析代码零改动;虚拟地址不等于物理内存,未访问页不驻留);
   - 新增 ReadDatasetInfo(只读元信息)+ StreamDataset(ByteSink)(连续布局 4MB 切片、
     分块布局逐 chunk inflate)+ ReadDoubleStream(SDI #data# 字节流按 8 字节一组直接写 double,
     7 字节尾巴处理非 8 对齐 chunk);ReadNumbers 改为流式转换;
   - MatLoader 先收集信号清单,再按(分组,时间流)合并为一个 TableBlock:时间列按内容去重、
     数据列直接 move;进度改成 0-0.02 映射 / 0.02-0.98 逐流解码 / 1.0。
   结果:117.3s -> 12.5s,私有内存 +1233.9MB -> +740.4MB(最终列数据 738.9MB)。
   关键性能修正:SDI 的 #data# 是 dims=[N]、chunk=[N,1],归一化后每行只有 1 字节;旧的逐行回调
   (且每行分配 coordinate 向量)只有 11MB/s。把目标连续的行合并成一次回调后约 110MB/s。
   顺带把 inflate 的 Huffman 解码改成 9 位快查表,并抽到新的 Deflate.hpp/.cpp 供 MF4 复用。

2) MDF4 数据块
   Problem-MF4.mf4 的第二个数据组把记录写在 ##DL(数据列表,两个 ##DT:131040+504 字节)
   里,旧实现只认 ##DT,整组被跳过,于是 8 个信号只读出 4 个。
   新增 CollectDataSegments(递归展开 ##DL / ##HL,叶子识别 ##DT / ##DV / ##DZ)与
   ReadRecordSegments(跨段拼接、压缩段先 inflate、非压缩段仍按 4MB 分块读),
   压缩段的记录长度改用 max(cg_record_id_len*8, 各通道 bitOffset+bitCount) 推算(载荷是压缩长度)。
   实测 8 个信号:gVP_WindowLearnSt[0] 94 行、gVR_WinOpenReq[0] 9396 行,时间列单调。
   ##SD(已排序数据)仍未实现,这类数据组会被跳过。

   另按要求只做检查、不改代码:该文件中 gVP_WindowLearnSt[0] 与 gVR_WinOpenReq[0] 都存在
   值枚举文本(##CC,cc_type=7;links[4+i] 对应 cc_val[i]),清单已写入 Documents/agent_mem.md。

3) 光标数据点标记
   光标的时间读数与差值胶囊绘制在绘图区底部、且在取值标注之后绘制,靠近底部的数据点圆点
   会被完全盖住。新增 MarkerCoveredByCursorLabel(圆心落入面板或重叠面积达到半个圆)
   与 CoverHiddenMarker(改成向下的三角、锚点放到面板上沿上方),在原始信号与派生曲线胶囊
   两处调用,语义与“数据点在视界下方”一致。

验证(全部离屏:SW_HIDE 窗口 + 只注入合成消息 + BELOW_NORMAL,不占用鼠标键盘屏幕):
- 新增 .preflight/round4check 34/34;A/B:临时禁用 ##DL 支持 => 2 项如实失败;
  临时禁用 CoverHiddenMarker => 3 项如实失败(含像素断言为 0);
- 回归:.preflight/round3check 31/31、editcheck 20/20、check 137/137;
- make release / make debug 零警告;Build/Release/Target.exe 785,920 字节(< 800KB)。
2026-10-19 20:13:17 +08:00
DeepSeek V4.1 Flash be76f523f3 修复组名编辑态点击穿透;浮动面板 GX 徽标悬停提示 + 点击滚动联动;最小网格高度提升到 81 逻辑像素
1) 组名/信号名处于编辑态时,在编辑框内双击、快速连点或框选编辑文本会让分组展开/收起。
   两处根因:
   - 行高大于输入框高度(上下各留 3 逻辑像素),起点落在内边距上时 OnEditMouseDown 返回
     false,宿主把按下交给 OnLButtonDown,命中分组行的 BODY 分支 ⇒ 折叠状态被切换;
   - 窗口带 CS_DBLCLKS,快速第二次按下被合成为 WM_LBUTTONDBLCLK,而 OnDoubleClick 没有
     编辑态保护,直接进入分组行双击逻辑 ⇒ 再次折叠 / 再次改名。
   修法:新增 NameEditAbsorbRect(输入框横向范围 × 整行高度)作为编辑态的“文本吸收区”:
   OnLButtonDown 命中即按文本操作处理(落点夹回输入框再算插入符),OnDoubleClick 命中即
   直接返回。横向仍限定在名称区域,同一行的复选框 / 「N 项」/ GX 徽标 / ✕ 继续可点。

2) 所有实验性功能浮动面板标题栏左侧的 GX 徽标:
   - EdgePanelLayout 新增 badge 矩形,成为绘制 / 悬停 / 手型光标 / 点击联动的唯一几何来源
     (命中顺序 close → badge → title,徽标在标题栏内部,放到 title 之后会被判成拖面板);
   - 悬停时徽标高亮 + 气泡提示(“G12 · 点击滚动到该网格” / “G12 · 已在视界内”),气泡在
     面板像素图层存进缓存之后绘制,避免被烘进位图后在鼠标移开后残留;
   - 点击调用 ScrollLaneIntoView:目标网格不可见时启动与“点选信号联动滚动”同源的平滑动画,
     已完整可见时不做任何改动。

3) 网格被压缩时的最小高度由 54 提升到 81 逻辑像素(1.5 倍)。同时修掉 m_minLaneHeight 被
   二次乘 m_scale 的隐患:Layout 里不再预先乘缩放,滚动条判定改用 laneCount × 81 × scale,
   与实际网格高度使用同一换算(125% 缩放下 128 个网格实测 101.25px = 81×1.25)。

验证(全部离屏:SW_HIDE 窗口 + 只注入合成消息 + BELOW_NORMAL,不占用鼠标键盘屏幕):
- 新增 .preflight/round3check(真实 TestData/MF4-Record.MF4,128 网格)31/31;
  A/B 临时禁用双击保护 ⇒ 1 项如实失败(证明断言有判别力);
- 回归:.preflight/editcheck 20/20、.preflight/check 137/137;
- make release / make debug 零警告。
2026-10-19 14:01:19 +08:00
DeepSeek-V4.1-Flash f44f8021e3 Release 产物 1.74MB → 796KB(去掉 <filesystem> 与 zlib)+ .mat 加载进度不再停住
一、体积膨胀的真因(用 -Wl,-Map 逐对象/逐节对比上一提交定位,只有两个)
1) <filesystem>(致命,约 +900KB):MatH5::Open 用 std::filesystem::file_size 取文件大小,
   MinGW 的 libstdc++ 因此链入 fs_path/fs_dir/fs_ops/c++locale/codecvt/collate_members/
   cow-wstring-inst/cxx11-wlocale-inst/fstream-inst/cp-demangle 整套 locale/iostream 机器
   (归档成员 415→538);还把 64KB 静态表从 .bss 挤成 .data(1.8KB→73.8KB)。
   修法:改用 _wfopen_s + _fseeki64/_ftelli64 取大小。
2) -lz(约 46KB):只需要"解开 HDF5 分块"。修法:MatH5 内置紧凑 inflate
   (RFC1950/1951:固定/动态 Huffman、存储块、长度-距离回抄),Makefile 与探针脚本移除 -lz。
顺带把 UploadHaloLayer 里 64KB 的预乘 alpha 查表换成 (v*a+128)*257>>16(无除法、无表)。

二、压到 800KB 以下的两项无损措施
1) 图标资源无损重编码:ico 里 64/48/32/16 四帧原为未压缩 32bpp DIB(合计 32KB),
   用自写工具(GDI+ 编码)改为 PNG 帧(ICO 的 PNG 条目 Vista+ 全支持,本文件本就有 256 PNG 帧):
   43,334 → 17,404 字节。像素逐点校验:差异 ≤85 像素、最大分量差 ≤3(编码取整),肉眼不可辨;
   并从**真实产物** LoadImageW 逐尺寸(16/32/48/64)验证可加载。
2) RELEASE_OPT 由 -Os 改为 -Oz(省约 6KB);实测动画帧 p50 7.73ms(-Os) vs 7.77ms(-Oz),
   无可辨识差异。
放弃的手段:-ffunction-sections/-fdata-sections + --gc-sections(反而 +9KB)、LTO(工具链 ICE)、
-fno-asynchronous-unwind-tables(链接失败)、-fno-exceptions(会破坏 catch (bad_alloc))。
结果:Build/Release/Target.exe = 795,648 字节。

三、.mat 加载进度不更新(已加载大小 / 总大小 / 百分比都停在 0)
真因:LoadMatSourceGroups 只在结束时回调一次 ratio=1.0,且从不设置 hasBytes;
读 42MB 文件与解码 19 个样本流(解压+拷贝,占大头)期间进度恒为 0。
修法:
- MatHdf5::Open 新增 OpenProgress(bytesRead,totalBytes),按 4MB 分块读取并回报;
- MatLoader 用 ProgressReporter 把进度拆成 0.00–0.55 读文件(hasBytes=true)与
  0.55–0.98 解码样本流(每解完一个流推进 done/total),末段 1.0;
- 取消与出错分开:取消时清空 error,不弹错误框。
实测:回调 29 次、字节数变化 11 次、比例单调、终值 1.000、bytes=42,386,344/42,386,344
(= 文件大小);Application 侧断言 m_loadingBytesRead == m_loadingTotalBytes == 文件大小。

四、验证(全部离屏,不占用鼠标/键盘/屏幕)
- matcheck 21/21(新增进度断言)、matui 15/15(新增图标 + 加载字节断言);
- 回归 verify 49/49、panelanim 158/158、gridanim 93/93、hoverdrag 20/20、lanescroll 23/23、
  datacheck 69/69、edgecheck 46/46、errcheck 19/19、halocheck 16/16、panflash 19/19、
  panelstutter 8/8、stress 6/6;
- make release / make debug 零警告。

文档:Documents/agent_mem.md 追加本轮小节(含 5 条新规则:MinGW 下 <filesystem> 是体积炸弹、
链一整条库前先算账、膨胀要看节表而非 .o 大小、图标可无损重编码并用产物校验、
长解析必须按阶段回报进度且 hasBytes 只在有字节时置真)。
2026-10-19 02:37:52 +08:00
DeepSeek-V4.1-Flash 5b24dacee0 悬停高亮恢复旧版外延观感 + 支持 MATLAB .mat(v7.3 / HDF5,嵌套分组)
一、悬停高亮「几乎看不到、只是曲线换了颜色」(对比提交 f845616 的旧版观感)
根因:调色板第 1 色就是主题强调色(0xFF268BD2),而上一轮为提速把稠密信号的光晕改成
「按包络带原始几何 FillPath」——与曲线**逐像素同形**,于是曲线上的那层半透明强调色
完全看不出变化;旧版观感来自「lineWidth+4 的描边笔向两侧各外扩 2~3px」。
修法(PlotViewPaint.cpp::PaintSignalHaloFor):
1) 逐列成对填四边形,上下左右都外扩 grow = (lineWidth + 4) * scale / 2
   (竖向跃变处旧版 5px 笔涂出的正是那条整高色带);
2) 不能把整条包络拼成一个多边形:满幅方波相邻列区间互相交叉,缠绕填充规则会在交叠处
   相互抵消 ⇒ 填充变成一条条竖缝(实测只覆盖约一半带宽);
3) alpha 从 0x55 调回 0x66(与旧版描边笔一致)。
代价仍只在「换信号/换视界/改样式」时付一次,此后每帧只 AlphaBlend。
三维对照(新增 haloshot 探针 + f845616 工作树构建):变化像素 旧 73726 / 上一轮 13529 /
本轮 74916,目视与旧版一致(跃变处的整高色带恢复)。

二、MATLAB .mat(v7.3 = HDF5)支持,含嵌套分组
新增只读 HDF5 子集(不引第三方库):Utilities/Inc/MatH5.hpp + Utilities/Src/MatH5.cpp
- 超级块 v0/v2;对象头 v1(含续块,主块长度以 headerSize 为准);符号表(B-tree v1 +
  本地堆)与 link 消息;数据空间 v1/v2;数据类型 v1;数据布局 v3(紧凑/连续/分块);
  分块数据集支持 deflate(zlib);地址一律相对基地址解析。
新增 MATLAB 语义层:Utilities/Inc/MatLoader.hpp + Utilities/Src/MatLoader.cpp
1) Simulink SDI / SimulationData.Dataset(识别 #refs# + #sigstream#):
   · #refs# 元素有 ElementType(signal/dataset)、Name、Values;
   · timeseries 的 Values/DataR2、Values/TimeR2 是 #sigstream#/<n> 的流编号;
     #data# 是 uint8 字节流,按 8 字节一组解释为 double(与 #length#/#sampleSize# 一致);
   · 子数据集的 Values 是对象引用数组(子元素)⇒ 还原嵌套树;
   · 分组名 = 从根到信号的各级数据集名用 - 连接(如 A/A1/XYZ → 组 A-A1、信号 XYZ),
     信号名取元素 Name(空则退回 timeseries 的 Name,再退回 信号N)。
2) 普通 v7.3 变量:顶层数值矩阵按「列 = 信号、行 = 采样」读取(时间轴 = 采样序号),
   结构体按字段递归。
加载管线扩成多分组:新增 LoadSignalSourceGroups()(DataLoader)→
AsyncLoadState::groups → Application::LoadFile 逐个 AddGroup;其它格式仍只有一个分组。
文件对话框与 IsSupportedSignalFile 增加 .mat。

三、验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 haloshot(截图对照 f845616)、matcheck(15/15:4 组 8 信号、时间轴单调、采样数正确)、
  matui(11/11:Application::LoadFile 端到端把 4 个分组加进数据层与信号列表);
- 回归 verify 49/49、panelanim 158/158、gridanim 93/93、hoverdrag 20/20、lanescroll 23/23、
  datacheck 69/69、edgecheck 46/46、errcheck 19/19、halocheck 16/16、panflash 19/19、
  panelstutter 8/8、stress 6/6;
- make release / make debug 零警告;Makefile 与探针构建脚本补 -lz。

文档:Documents/agent_mem.md 追加本轮小节(含 4 条新规则:叠加层必须自带外扩、
缠绕填充不可用于自交包络带、HDF5 主消息块长度看 headerSize、
MATLAB 分块维度可能比数据空间多一维、按右侧补 1 对齐)。
2026-10-19 00:52:41 +08:00
DeepSeek-V4.1-Flash 24f0bf849d 修复悬停高亮变长方形色块/联动失效 + 信号列表动画卡顿真因(行位图缓存)
一、悬停高亮恢复"整条曲线发光"(上一轮裁成鼠标附近一段 ⇒ 满幅方波上就是个长方形色块)
光晕的语义是"整条曲线一起发光",对编码器这种满幅方波,一列包络本来就跨满网格高,
裁成 144px 宽 × 满高的窗口在屏幕上就是一个像选区框的色块(用户 2026-10-18 第二轮报告)。
修法(PlotViewPaint.cpp):
- PaintSignalHaloFor 恢复**整条**几何(包络带 FillPath / 折线 DrawLines);
- 新增 PaintSignalHaloCached:光晕几何只取决于「信号 + 视界 + 网格几何 + 样式」,**与鼠标无关**
  ⇒ 渲染一次进离屏位图,之后每帧只做一次 AlphaBlend(源位图预乘 alpha,用 256×256 查表);
- 缓存键与 AcquirePolyline 同源,**不含网格屏幕位置**(滚动网格只按新位置贴同一张位图)。
  210ms/帧(整条描边)→ 一次性 15ms 渲染 + 每帧 ~2ms 贴图。
halocheck 校验:悬停必命中(60/60 位置)、光晕贴合带宽(outsideBand=0)、不越出网格、
bbox 覆盖整条曲线(不是中段方块)、列表行悬停联动仍生效。

二、信号列表动画卡顿的三个真因(探针 panelstutter:129 个可见网格 + 反复"展开→切组复选框→收起")
1) 面板每帧重画 ~36 行 × ~0.35ms(十来个 GDI+ 原语)⇒ panel.Paint 24~35ms;
2) 动画帧里仍在预热网格图层:每帧重建 1 个网格图层,2500 万采样信号 10~30ms ⇒ 帧被拉长;
3) 勾选分组会把上百个**离屏**网格排进待重画队列,随后每帧补 1 个 ⇒ 数秒持续掉帧。
修法:
1) 行位图缓存(SignalPanel::PaintRowCached):按"行的可见内容"做键,整行渲染进离屏 DIB,
   命中时一次 BitBlt。panel.Paint 24~35ms → 5~6ms。
   ⚠️ 必须 DIB + 内存 DC(gd::Bitmap 上 GDI 会关闭 ClearType,整列文字退化);
   ⚠️ 行内几何在 RowRect 里平移(GDI 文本不认 GDI+ 世界变换,否则文字画到小位图之外);
   ⚠️ 渲染到行位图时不按列表矩形裁剪,裁剪放到贴图时的 DC(GDI 不认 GDI+ 裁剪,
      必须自己 SaveDC + IntersectClipRect,折起动画时行不许画到组名上);
   ⚠️ 缓存上限 192 行(≈10MB)。静态画面逐像素一致(面板区域 54/275200 不同)。
2) 新增 PlotView::SetAnimationBusy:UI 动画期间不预热网格图层(队列保留,动画结束后继续)。
3) 待重画队列只保留可视 + 预取带内的网格;RollWideBuffer 不再登记离屏网格。
实测:展开 p50 35ms → 13ms、收起 33ms → 13ms、动画帧内图层重建 56 次 → 0 次。

三、试过并放弃(避免以后重走)
- ExtTextOutW 替代 DrawTextW:快 2~4 倍但光栅化不同(面板 10% 像素差异)⇒ 观感变化,放弃;
- 用 gd::Bitmap 做行位图:文字丢失 ClearType,放弃。

四、验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/perf/panelstutter_main.cpp(8/8)、halocheck_main.cpp(16/16);
- 回归 verify 49/49、panelanim 158/158、hoverdrag 20/20、gridanim 93/93、lanescroll 23/23、
  datacheck 69/69、edgecheck 46/46、errcheck 19/19、panflash 19/19、stress 6/6;
- make release / make debug 零警告(TARGET_FILE_NAME=TargetCheck/TargetCheckD)。

文档:Documents/agent_mem.md 追加本轮小节(含 4 条新规则:高亮语义不得为性能让步、
GDI 文本的三个隐形陷阱、面板卡顿先量"每行成本×可见行数"与"本帧是否顺带做绘图区重活"、
动画帧内绝不做可延迟的整帧级工作)。
2026-10-18 21:40:56 +08:00
DeepSeek-V4.1-Flash 0efaa8c0ca 悬停光晕 210ms/帧 → 4ms(包络带填充 + 光标窗口裁剪)+ 平移丢波形判据修正 + 随机组合压力测试
一、悬停巨型信号时整帧卡死(用户报:VERY_BIG_DATA 的「编码器A相」,浮动提示不再跟随鼠标)
探针复现:hoverMean = 210.31ms / baseline = 0.90ms。
根因:PaintSignalHalo 用「加粗 GDI+ 折线」描边整条曲线,而包络带信号的顶点是
「每像素列上/下包络交替」的满幅竖锯齿 —— GDI+ 描边代价 ≈ 线段数 × 覆盖行数
(2503 顶点 × 1081 行)。同一几何改用 FillPath 只要 ~1ms。
修法(PlotViewPaint.cpp / PlotViewInternal.hpp):
1) 包络带信号(cached.bandMode)的光晕改为**填充与曲线本身同一几何**的包络带
   (bandBreaks 段 → 上包络正向 + 下包络反向,alpha 0x55);
2) 只画鼠标附近一段(kHaloWindowHalfWidth=72 逻辑像素、kHaloWindowMinPoints=360):
   · 包络带:窗口换算成列区间再填充(下包络反向存储 ⇒ 列 c 在 start+half+(half-1-c),
     列区间恰对应一段正向可读的连续区间);
   · 折线描边:O(n) 扫一遍取窗口内的连续顶点段,段首尾各多含一个顶点(跨窗口的线段完整画出);
   · 散点高亮同样按窗口过滤(步长仍与 PaintLane 同源)。
3) 窗口中心 = 鼠标 x;鼠标不在绘图区时取绘图区中心(列表/样式区悬停代价同样恒定)。
实测 210ms → 4.0ms(提示恢复跟手)。几何正确性用「透明位图上两次绘制」探针确认:
窗口内与整条光晕逐像素相同(mismatched=0)、窗口外 0 像素(far=0)、全部落在包络带内(outsideBand=0)。

二、组折叠/展开动画卡顿甚至中断(用户用 MF4→BIG→MF4 + 只勾「编码器A相」稳定复现)
这是上面那条的连带症状:列表悬停时 Application::Render 把 panel.HoveredSignal() 推给
SetExternalHoverSignal ⇒ 鼠标每动一次就重画一次光晕;悬停在 2576 万采样的编码器行上
= 210ms/帧 ⇒ 动画定时器被饿死,看起来「卡顿甚至中断」。
同一修复后列表悬停帧 0.72ms(≈ 基线 0.81ms)。探针 [3b] 固化该判据。

三、快速中键拖拽「所有网格信号一起消失」——主修复已正确,之前的失败是度量错了
上一轮把「位移超出 kLayerRollMaxShiftPixels 就作废图层」改成
「driftLimit + 每帧最多滚 kLayerRollMaxStepPixels=240px」,剩余位移由 BlitWideBuffer 的
srcX 承担 ⇒ |shift| 可长期停在 ±(margin-24) 附近而**不会空白**。
之前探针拿「适应视图时的墨迹」当基准,而平移本身会移出数据范围(时间轴外没有采样),
墨迹自然下降 ⇒ 误报。正确判据(已固化进 panflash / stress):
同一次拖拽中把当前帧与**同一视界的整幅强制重绘**(m_sceneValid=false + 清空 m_layerFront)比墨迹,
比值 < 0.9 才算丢波形。三条拖拽模式(120px/帧、90×7 斜向、260px/帧)全部 badFrames=0,
worstRatio 0.966~0.999;MF4 8 个单点信号网格 framesWithMissingPoints=0。

四、新增随机组合压力测试(用户第 4 项要求:给出评估方法与压力验证)
.preflight/perf/stress_main.cpp:1 个 VERY_BIG_DATA(5×2576 万采样)+ 6 个 MF4 分组
(~1500 信号、41 个可见网格),固定种子的随机序列覆盖:中键拖拽(抽样做空白判据)、
滚轮缩放/分布缩放、悬停扫掠、组折叠展开(**每帧都把鼠标扫过正在出现/消失的行**,并校验
m_contentHeight 单调)、适应视图、面板滚动条拖拽、勾选/取消信号、标签页新建/切换/关闭。
判据全部为**同一次运行内的相对量**(基线帧 vs 交互帧),避免绝对 ms 阈值的漂移。
实测:ops=26 frames=209 mean=8.53ms worst=44.87ms(toggleSignal) overBudget=0;
pan blank check 90 个车道样本 0 个低于整幅重绘的 80%;group animation heightRegressions=0。
最慢帧是勾选信号(41 个网格全部重画曲线像素),属于用户明确排除的「需要重绘信号曲线」场景。

五、验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/perf/panflash_main.cpp(panflash.exe)19/19:三条拖拽模式的丢波形判据、
  MF4 单点网格、悬停耗时(4.0ms)、列表悬停联动(0.72ms)、光晕窗口化几何精确性;
- 新增 stress 6/6;既有回归 verify 49/49、panelanim 158/158、hoverdrag 20/20、
  gridanim 93/93、lanescroll 23/23、datacheck 69/69;
- make release TARGET_FILE_NAME=TargetCheck 通过(Target.exe 被运行中的实例占用)。

文档:Documents/agent_mem.md 新增本轮小节(含 4 条新规则:GDI+ 加粗描边不得用于包络带几何、
随鼠标变化的绘制必须 O(光标附近)、判据必须是同一次运行内的相对量、平移正确性只能与整幅重绘对比)。
2026-10-18 03:51:53 +08:00
DeepSeek-V4.1-Flash fd606a6f80 多层加载拖拽卡顿根治 + 高亮命中线段中段 / 散点高亮 + 选区放大也高亮 + 自动点线判定与规则修正
一、三明治加载后拖拽视界卡顿(39.2ms → 8.2ms 均值,47.9ms → 10.3ms 最坏)—— 四个叠加根因
1) RenderLaneLayerRegion 每次「横向补条」都调用两次 BuildLaneVisuals()(对全部网格算
   ComputeLaneRange,O(网格数))⇒ 253 网格时单帧 RollWideBuffer = 26.8ms。
   修法:进入前快照 m_lanes / m_laneContentHeight,结束时直接拷回(它只渲染一个网格)。
2) 横向「重新居中」本来就不是正确性要求:BlitWideBuffer 按 (ViewMin - layer.wideMin) * scale
   算源偏移,不滚动也画得对 ⇒ 新增 kLayerRollQuantumX=32(不足一个量子不滚)+
   kLayerRollsPerFrame=3(每帧最多重居中 3 个网格,分帧摊销)。
3) 位移过大时旧实现「立刻整块重画」:巨型信号(25M 采样)一次 20~130ms,实测拖拽最坏帧 28ms。
   修法:kLayerRollMaxShiftPixels=160,超出就作废图层 + 登记待重画,本帧由 BlitWideBuffer 的
   网格底衬兜底(正是用户要求的“没来得及画信号就按只有网格叠底继续拖”);
   并且 RollWideBuffer 只处理可视/预取带内的网格(离屏网格不做像素工作)。
4) 包络带路径也支持横向补条裁剪:包络是逐像素列独立计算的,只构建条带覆盖的列即可。
   旧实现对包络带总是构建整幅可见范围 ⇒ 巨型信号一条 32px 竖带要 5ms。

二、鼠标悬停「两点之间的连线」无法高亮(放大到自动点线后)
根因:HitTestSignal 的横向窗口用 upper_bound(fx+tol) 取顶点区间,只扫 [first, last) 的线段;
放大后相邻点相距几十像素,鼠标落在两点中间时两端都在窗口外 ⇒ windowFirst >= windowLast ⇒ 跳过。
修法:线段扫描上界改为 min(stop, windowLast + 1)(必须包含“一端在窗口内、另一端在窗口外”的线段);
散点命中的顶点范围也向两侧各扩一个。实测 11 段长线段中点命中 0/11 → 11/11。

三、散点模式补充高亮(用户要求:原线段/点线高亮“去掉数据点之间连线的部分”)
PaintSignalHalo 原来对 DrawMode::SCATTER 直接 return ⇒ 只有拖拽提示、没有高亮。
现在给每个参与绘制的散点套一圈半透明强调色圆环(步长与绘制同源 kMaxScatterPoints)。
线段 / 点线保持原有的折线光晕。列表 / 图例 / 样式区「目标」行联动高亮走同一条路径,自动生效。

四、「选区放大」模式恢复悬停高亮(仍不允许拖信号)
OnMouseMove 去掉 ZoomMode() == NONE 的门控;OnLButtonDown 在 SELECT 模式下仍直接进入 RUBBER_SELECT。
该模式下的提示文案只显示信号名(去掉 “· 按住拖拽可移入其它网格” 尾缀)。

五、自动点线误判(MF4 + VERY_BIG_DATA 同时加载时 MF4 信号默认就被切点线)
根因:MinorCellSparse 把整个视界切成小格。VERY_BIG_DATA 跨度 ~130s、MF4 数据只覆盖 ~11s ⇒
MF4 信号在 90% 的格子里本来就没有任何采样点 ⇒ “存在一个格子内 < 5 个点”恒成立。
(用户猜测是“用了抽稀点”,实际是“空格子”,但现象一致。)
修法:先用可见数据的显示时间跨度 [dataMin, dataMax] 收窄参与的格子(firstCell..lastCell),
鸽巢判定的分母改用参与格子数;端点 NaN 时做 64 步有界探测并回退到轴区间。
实测:248 个 MF4/中等信号被误判的数量 248 → 0。

六、自动点线规则收窄(用户本次要求)
只有 DrawMode::LINE 的信号在放大时自动切 LINE_SCATTER;SCATTER 不切换(散点已足够提示数据点位置)、
LINE_SCATTER 不动;用户手动改过样式(autoScatterUserOverride)时不切换,避免“改完下一帧又被改回去”。
退出时(用户未手动改过)恢复原来的 LINE。
实测:LINE → 放大后 mode=2 auto=1 → 适应视图后 mode=0 auto=0;SCATTER → 放大后 mode=1 auto=0。

验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/perf/hoverdrag_main.cpp(hoverdrag.exe)20/20:三明治加载后的点选跳转 + 拖拽逐帧耗时、
  线段中点命中、散点高亮像素、选区放大悬停命中、自动点线判定与规则。
- 既有回归:verify 49/49、panelanim 158/158、datacheck 69/69、edgecheck 46/46、errcheck 19/19、
  gridanim 93/93、lanescroll 23/23。
- 探针的绝对毫秒阈值改为相对基准(同一基线代码在不同时段可差 2~3 倍,避免假失败)。
- make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与运行中实例冲突)。

文档:Documents/agent_mem.md 新增 §9.45、§11.2 第 141–150 条与变更日志一行。
2026-10-18 01:08:11 +08:00
DeepSeek-V4.1-Flash 6b15575829 滚动动画根治(巨型信号不再影响滚动)+「N 项」计数点击语义 + 浮动面板首现右上角 + 开关/关闭面板卡顿
一、滚动过渡动画在巨型信号处卡顿 —— 真因是「退化成整幅重绘」
滚动到**从未构建过图层的区域**时,可见网格一个都贴不出图层像素 ⇒ BlitWideBuffer() 返回 false
⇒ 落到 Paint() 末尾的整幅重绘 ⇒ 把本帧全部可见网格的波形(含千万级采样的巨型信号)每帧重画一遍。
实测(MF4 124 网格 + VERY_BIG_DATA 5 网格,点选最后一个信号):单帧 20~35ms,且「可见的巨型信号
网格数」越多越慢(0 个 ≈20ms、5 个 ≈34ms);对照手动拖滚动条只要 ~6ms。
修法:BlitWideBuffer 只要还能画网格底衬(backing 非空)就返回 true,
只有 blits 与 backing 都为空时才交给整幅重绘 —— 这正是「没来得及画信号时就按只有网格叠底的状态继续滚动」。
实测:动画均值 23.0→11.2ms、最坏 35.6→13.1ms;含 5 个巨型信号网格的帧 33→10.7~12.3ms。

二、面板动画帧只重绘面板(额外 −2.7ms/帧)
新增 Application::m_panelDirty:滚动定时器在只有面板动画在跑时置位,Render 走 else if (panelOnly)
只重绘面板 + 分隔条(SignalPanel::Paint 会铺满自己的区域)。m_chromeDirty(真内容变化)仍走完整装饰重绘。
⚠️ 回送仍是 if (fullFrame) {整窗} else {绘图区} 的无条件分支(遵守 §9.41)。
实测帧耗时拆解:plot 4.8ms / plot+panel 11.4ms / plot+全装饰 13.9ms。

三、分组行「N 项」计数:只折叠展开,绝不进入改名
新增 RowPart::GROUP_COUNT 与 SignalPanel::GroupStatsRect(row)(绘制与命中的唯一几何来源,命中矩形在
文本左右各外扩 3 逻辑像素)。RowHitPart 在名称区域之前判定计数区:
- OnLButtonDown:折叠/展开;OnDoubleClick:只折叠/展开,任何点击频率都不进入改名;
- DoubleClickIsPlainPress 返回 true ⇒ 快速连点每一击都生效。
⚠️「快速双击进改名」的计时只对 RowPart::BODY 记录(否则“点计数、紧接着点名称”会被误判成双击)。

四、浮动面板首次出现的位置:右上角
旧默认位置 top = plotTop + 16 + laneIndex*28:上百个网格时 laneIndex 很大 ⇒ 被夹到右下角
(实测 plotH=1081 时 topMargin=964)。改为按「当前真正显示出来的面板中排在本面板之前的个数」错开
(固定遍历顺序:网格下标 → 面板种类),并把错开量夹到“面板下沿不超过绘图区 45% 高度”以内:
第一个面板错开 0 ⇒ 正好贴在上右角(实测 topMargin=24、rightMargin=24),6 个面板时最坏 topMargin=234。
⚠️ 判定「面板是否显示」不能只看 *Panel.open —— LanePanelState::open 默认就是 true,必须同时看功能启用。

五、开关实验性功能 / 关闭面板卡顿 —— RebuildWideBuffer() 整批重建
四个 SetLane* 末尾都调 RebuildWideBuffer()(注释说是“预建宽缓冲基底”),而它同步重渲染全部网格的图层:
129 个网格(含 5 个千万级采样的巨型信号)整批重建要几百毫秒 ⇒ 点一下功能开关 / 关闭浮动面板就会卡住。
修法:新增 PlotView::EnsureLaneLayersPrefetched() —— 只有「图层数组与网格数量不符」(首次绘制 / 增删网格)
才同步整批建立;其余只 RollWideBuffer() 登记缺失/过期网格,由既有预算(每帧最多一个)逐帧补齐。
实测:几百毫秒 → ≤22ms;巨型信号网格上开关均值滤波同样 15ms 量级。

验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/perf/lanescroll_main.cpp(lanescroll.exe)23/23:逐帧耗时 + 「可见巨型信号网格数」关联、
  「N 项」计数热区与双击不进改名、面板首现位置与多面板错开、开着巨型信号的开关/关闭面板耗时。
- 既有回归:verify 49/49、panelanim 158/158、datacheck 69/69、edgecheck 46/46、errcheck 19/19、
  gridanim 93/93。
- make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与运行中实例冲突)。

文档:Documents/agent_mem.md 新增 §9.44、§11.2 第 134–140 条与变更日志一行。
2026-10-17 09:56:45 +08:00
DeepSeek-V4.1-Flash 436dcbfd52 滚动过渡动画根治 + 快速连点跟手 + 分组名单击展开 + 光标吸附同源 + 按钮淡出对齐 + 滚动位置复位
一、滚动过渡动画卡顿(用户报告:甚至不如手动拖滚动条)—— 三处独立根因
1) WM_TIMER(kScrollTimerId) 无条件整窗失效 ⇒ WM_PAINT 看到更新区域不在绘图区内 ⇒ 把
   m_chromeDirty 置真 ⇒ 每一帧都重绘标签栏/工具栏/面板。
   修法:用「推进之前」的面板动画状态决定失效范围 —— 面板动画(滚动到目标信号 / 分组折起展开)
   确实要改面板像素 ⇒ 整窗失效;只有绘图区动画(网格滚动到可视区 / 回到最上方)⇒ 只失效绘图区。
   实测:绘图区动画帧 13.6ms → 3.5ms;手动拖滚动条参照 4.2ms。
   ⚠️ 未改变 §9.41 的回送结构(仍是 if (fullFrame) {整窗} else {绘图区})。
2) 图层预热不足:上百个离屏网格长期不建图层,长距离滚动动画扫进去时每一帧都有十几个网格
   没有图层像素,只能靠「网格底衬兜底」重画(约 0.5ms/网格 ⇒ +8ms/帧)。
   修法:LaneVisuallyRelevant(laneIndex, prefetchScreens) 增加 ±1 屏预取带(AnyVisibleLanePending 用),
   并新增 PrefetchStepAllowed()(kPrefetchMinIntervalMs=32 ⇒ 约 31 次/秒)限速地继续请求重绘,
   让整批图层在静置期间逐步建好。
   实测:lane-only + VERY_BIG_DATA 帧 13.9ms → 5.3ms;signal-click + VERY_BIG_DATA 18.3ms → 10.3ms。
3) 网格滚动量原本参与「宽基底可用性」判据(上一轮已修:SceneContentHash(..., includeLaneScroll=false))。

二、光标吸附改用「放大自动点线」的同一判定(用户 2026-10-17 修订)
AutoPointLineNeeded 改名 MinorCellSparse(中性名,同一判定两处用途):任一小格内有效数据点 < 5 即为稀疏。
- 「放大自动切点线」与「光标吸附」共用它;判定完全不看绘制方式 ⇒ 即使用户手动改过显示样式也照样吸附。
- 鼠标下方网格内既有符合又有不符合的信号时,只有符合的那条的数据点参与吸附;没有符合的则自由移动。
- 删除旧的整屏判据 SignalSnapEnabled 与 kSnapVisibleCellsPerPoint(死代码)。

三、「回到最上方」按钮淡出与「浮动提示」一致
kBackToTopFadeOutMs = kHintFadeMs(500ms)+ 线性 alpha,到达顶部立即开始淡出(不停留)。
淡入保留 170ms ease-out。实测 50% 时刻 alpha = 0.500(线性),旧 ease-out 会是 0.875。

四、快速连点跟手(根因:CS_DBLCLKS 合成 WM_LBUTTONDBLCLK 导致按下丢失)
组件各自实现 DoubleClickIsPlainPress(x, y)(该落点有没有双击语义),宿主 WM_LBUTTONDBLCLK 据此
选择走 OnLButtonDown(当作按下)还是 OnDoubleClick:
- PlotView:浮动面板(含复选框/滑块/复位/✕/标题栏)、「回到最上方」按钮、绘图区滚动条、GX 徽标 ⇒ 按下;
  时间光标线/读数标签/图例条目名/绘图区空白 ⇒ 保留双击。
- SignalPanel:样式区数值框、「目标」行名称与 GX 标签、行名称区域 ⇒ 保留双击;其余全部 ⇒ 按下。
- 弹出菜单 / 拾色器打开时,WM_LBUTTONDBLCLK 也按各自 OnLButtonDown 走。

五、分组行:名称恢复单击展开收起 + 只有快速双击进入改名
- OnLButtonDown 分组分支不再排除 RowPart::BODY:三角 /「组X」/ 名称本体统一折叠展开。
- OnDoubleClick 名称区域:只有快速双击(kGroupRenameDoubleClickMs=300,比系统 500ms 紧)才进入改名;
  超阈值的「慢双击」只当又一次单击;快速双击前先撤销按下那一次的折叠切换,保证进编辑时状态与点击前一致。

六、信号重现时网格滚动回到最上方
“有过需滚动条的网格 → 没有任何信号显示 → 又出现需滚动条数量的网格”这一类情形必须从最上方开始看。
修法:Application::SynchronizeView 的「首次出现可见信号」分支里,在 FitAll() 之后 tab.LaneScroll() = 0。

验证(全部离屏,不占用鼠标/键盘/屏幕)
- grabdanim(.preflight/perf/gridanim_main.cpp)93/93:真实消息循环帧距 + 逐帧渲染耗时(与手动拖滚动条
  对比)、光标吸附新判定、按钮淡出线性、快速连点路由、分组名单击/双击/慢双击、滚动位置复位。
- 既有回归:verify 49/49、panelanim 158/158(按新分组名规则更新 8 条断言)、datacheck 69/69、
  edgecheck 46/46、errcheck 19/19。
- make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与正在运行的实例冲突)。

文档:Documents/agent_mem.md 新增 §9.43、§11.2 第 125–133 条与变更日志一行。
2026-10-17 03:17:42 +08:00
DeepSeek-V4.1-Flash 34375481de 网格底衬永不空白 + 动画流畅性根治 + 光标随首个信号重现 + 「放大自动点线」新判据
一、网格底衬消失(半屏/局部/全屏,且要等一会才刷新好)—— 两个机制叠加
1) BlitWideBuffer 对「跨度已过期」的图层原用 StretchBlt 放大占位:深度放大时把一小段像素放大
   几十倍 ⇒ 整块网格变成一片无网格的模糊色块(用户看到的就是「网格叠底消失、没有任何内容」)。
   修法:删除占位拉伸;对拿不到(完整)图层像素的网格【就地补画网格底衬】
   (新增 PaintLaneGridBacking + m_renderGridOnly,复用 PaintLane 的网格部分,跳过波形/散点/
   派生曲线/Y 轴刻度/图例)。底衬代价极低,可每帧执行 ⇒ 网格永不空白/闪烁。
   ⚠️ 顺序:先画底衬(GDI+)再 g.GetHDC() 做 BitBlt;只有全部网格都贴不出像素时才交给整幅重绘。
2) 待重画队列按下标顺序消费(一次 1 个)⇒ 上百个网格时可视网格要等上百帧。
   修法:RenderPendingLaneRebuilds 里做稳定划分,【可视网格优先】(LaneVisuallyRelevant);
   「结算」判据由 LanesAllReadyForView() 改为 LanesVisiblyReadyForView()。

二、过渡动画卡顿/不平顺/直接闪到终态 —— 三处根因
1) 「m_layerRebuildPending 非空就每帧请求再送一帧」:上百个网格时 700ms 内挤出 124 次重绘,
   而 WM_TIMER 优先级低于投递消息 ⇒ 动画定时器被饿死(实测 frames=124 / timerTicks=1)。
   修法:新增 AnyVisibleLanePending(),只用「还有没有可视网格待补」决定是否请求再送一帧;
   离屏网格改为机会式预热(实测 frames=25 / timerTicks=16)。
2) SetTimer 的间隔会被量化到系统 tick(默认 15.6ms):请求 16ms 实际等到下一个 tick
   ⇒ 实测帧距 ~26ms(只有约 38fps)。修法:kScrollTimerPeriodMs 16 → 10(实测 ~15.5ms ≈ 64fps;
   真实消息循环帧距 ~25ms → ~16ms)。实测映射:8→15.8 / 10→15.5 / 12→16.5 / 16→26.4 / 31→37.9。
   ⚠️ timeBeginPeriod(1) 在本机无效(已 A/B 验证)。
3) 网格滚动联动 / 「回到最上方」每帧都改 LaneScroll(),而它原本参与「宽基底可用性」判据
   (SceneGeometryHash)⇒ 每一帧都退化成整幅重绘。修法:SceneContentHash 新增 includeLaneScroll 形参,
   宽基底可用性判据不计网格滚动量/各网格屏幕纵向位置(它们只是图层的贴图落点);
   静态场景缓存的指纹仍然计入。实测网格滚动动画单帧 18.36ms → 1.19ms。

三、光标工具激活且没有任何信号绘制时,首个信号出现后按规则重新出现(用户要求)
新增 PlotView::ResetCursorPlacementOnFreshContent()(规则与 CycleCursorMode() 的「首次出现」同源:
单光标置于视图中心;双光标第二条紧邻第一条且落在可见范围内),由 Application::SynchronizeView
的「首次出现可见信号」分支在 FitAll() 之后调用。
实测:单光标 stale=4.60 → 新的视图中心 20.92;双光标 stale=(2.89,7.18) → (20.92,26.07)。

四、「放大自动点线」规则改为:任一小格内数据点少于 5 个即自动切换(用户重新定义)
新增 PlotView::AutoPointLineNeeded()(推翻此前「一屏内数据点 < N×4」):按 GridMinorCellCount()
切成横向小格,只要存在任何一个小格内有效数据点 < kAutoPointLineMinPointsPerCell(=5) 就切「点线」。
实现:先「总有效点 < N×5」鸽巢 O(1) 充分判定;单调时间轴逐小格两次二分取区间(单格累计到 5 个即早退,
另给 kAutoPointLineCellScanCap=512 封顶);非单调时间轴一次有界扫描分桶。保留「视界内至少一个可见点」保护。
⚠️ 光标吸附仍用旧的整屏判据 SignalSnapEnabled(用户明确要求「其它规则不变」)。

验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/perf/gridanim_main.cpp(gridanim.exe)55/55:
  [A] 缩放/适应视图/「预热未完成再滚动」等每个用例逐帧断言「每个可视网格都有网格底衬
      (有竖直锐边、无模糊占位、无未绘制带)」;[B] 直接驱动 + 真实消息循环两条路径统计每帧耗时、
      帧距均值/最大与进度单调性;[C] 光标重现;[D] 自动点线新判据(稠密不切、稀疏切换、恢复原样式)。
- 既有回归:verify 49/49、panelanim 156/156、datacheck 69/69、edgecheck 46/46、errcheck 19/19。
- make release 零错误零警告(使用 TARGET_FILE_NAME=TargetCheck 以避免与用户正在运行的实例冲突)。

文档:Documents/agent_mem.md 新增 §9.42、§11.2 第 117–124 条与变更日志一行。
2026-10-17 00:56:19 +08:00
DeepSeek V4.1 Flash f845616938 回退 5b52eef 并修复「屏幕不刷新」根因:拖动滚动条卡顿 / 悬停高亮卡住 / 信号拖拽无效果
根因(代码级确认)
- Render() 把界面画进后备缓冲区后用 GetDC + BitBlt 回送窗口,5b52eef 把回送写成
  `if (fullFrame) { BitBlt(整窗) } else if (panelOnly) { BitBlt(本帧重绘区域并集) }`,没有兜底 else。
- 「鼠标只在绘图区内移动」的帧(WM_MOUSEMOVE 的 plotOnlyMove 路径只 InvalidatePlotRect)恰好
  fullFrame == false && panelOnly == false ⇒ 后备缓冲区被正确重绘,窗口 DC 一个像素都不回送。
- 这一个根因同时造成用户报告的三个症状:悬停光晕卡住(移开不消失)、拖动滚动条不跟手/非常卡顿、
  拖拽信号换网格看不到任何结果。

修法
- git reset --hard HEAD~1 撤销 5b52eef(保留 backup-bad-commit 标签供 A/B)。
- 只挑回该提交里与更新区域无关的功能改动(用户明确要求过、本身正确):
  * SignalPanel::ExpandGroupForSignal():联动滚动前立刻展开折起的分组(不播展开动画);
  * FitAll() / FitSinglePointCluster() 不再复位 Tab().LaneScroll()(适应视图不影响滚动条);
  * 右下角「回到最上方」浮动按钮 + 网格滚动到可视区的平滑动画。
- Application.cpp 只保留 3 处最小改动:Render 里把 panelAnimating 计入整帧脏、WM_TIMER 推进
  AdvanceBackToTopAnimation() 且保持整窗失效、定时器存活判定带 BackToTopAnimationActive()。
  Render 的绘制与 BitBlt 路径逐字节等同于 82b5ebe。
- 彻底删除 m_paintPlot / m_panelDirty / panelOnly / InvalidatePanelRect / 并集回送。

验证(全部离屏执行,不移动真实光标、不抢焦点、不截屏)
- .preflight/diag(新增离屏探针,19/19):窗口移到屏幕外 + WS_EX_TOOLWINDOW + SW_SHOWNOACTIVATE,
  保证有真实更新区域与 WM_PAINT 但不影响用户。断言滚动条拖动(滑块与绘图区都在后备缓冲区被重绘)、
  悬停光晕出现与消退(像素完全回到基线)、在曲线上按下进入 MOVE_SIGNAL 且拖到另一网格后归属正确。
- .preflight/check 137/137。
- .preflight/diag/verify_blit.ps1(新增源码级护栏,10/10):断言 Render() 的回送块必须有无条件 else、
  不得出现 else if、m_paintPlot/m_panelDirty/panelOnly 必须不存在、失效只允许整窗或仅绘图区。
  A/B:换回 backup-bad-commit ⇒ 4 项如实失败;换回修复版 ⇒ 10/10。
- make release 零错误零警告。

文档:Documents/agent_mem.md 新增 §9.41、§11.2 第 108–116 条(含 §9.40 与旧 108–114 条的作废标注)
与变更日志一行。

教训(写进记忆):① 离屏探针无法验证「窗口回送 / 合成」(屏幕外 DWM 不合成、窗口 DC 恒为空白)⇒
回送类约束必须有源码级护栏;② 离屏窗口 + TrackMouseEvent 会立刻收到 WM_MOUSELEAVE,探针须丢弃该消息;
③ 网格回收会让下标左移,断言要用锚信号。
2026-10-16 17:33:08 +08:00
DeepSeek V4.1 Flash 82b5ebe4f9 「所属网格」菜单:标题固定 + 10 行上限 + 滚动条;网格滚动联动改为动态过渡
1) 菜单滚动时最上方的「所属网格」标题不再跟着滚走
   - 根因:上一轮只实现了“尾部固定区”(最后一个分隔线 + 「新建网格」钉在底部),
     而滚动区是从条目 0 开始的,于是标题也被一起滚掉。
   - 修法:BuildLayout 新增 m_scrollFirst(= 前导 HEADER / HEADER_ACTION 的个数),
     滚动区收敛为半开区间 [m_scrollFirst, m_pinnedIndex):
     · 前导标题从菜单顶向下固定排(不参与滚动);
     · 滚动区条目受 m_scrollOffset 影响;
     · 尾部固定区从菜单底向上对齐。
   - 配套:Paint 的裁剪与“跳过不可见条目”范围、ItemAtPoint 的“必须落在滚动视口内”判定、
     EnsureItemVisible 的下界全部改用该区间;新增 ScrollRegion(first, last) 供查询/测试。

2) 菜单长度上限改为「最多 10 条网格」并加滚动条
   - MenuLayoutConstraint 新增 maxScrollRows(滚动区最多显示的标准行数;与 maxHeight
     同时给出时取更严格者);OpenLaneMenu 传 10(kLaneMenuVisibleRows),
     另保留 maxHeight = max(180, 客户区高 × 0.8) 作为矮窗口的硬上限。
   - 新增滚动条:轨道贴滚动条目区右内侧(不盖住标题与底部固定项),滑块高度按视口/内容比
     (下限 22 逻辑像素);滚动区条目宽度让出 kScrollBarWidth + kScrollBarGap,
     文本与对勾不会压在滑块上。
   - 滚动条可拖拽:按下滑块 → 拖动(只改偏移、不更新悬停);点击轨道空白 → 滑块中心移过去;
     松开结束拖拽且不下发命令、菜单保持打开。新增 ScrollOffsetForThumbTop 反算偏移。
   - ⚠️ BuildLayout 绝不能复位 m_scrollBarDragging:拖拽时每帧都会 RebuildLayout(),
     在那里清零会让拖拽“拖一下就脱手”(该状态只在 Open / Close 里复位)。

3) 网格滚动联动改为动态过渡(旧实现直接把视界瞬移过去,过于僵硬)
   - PlotView::ScrollLaneIntoView 只负责启动平滑动画:与信号列表的“滚动到目标信号”同源
     (ease-out cubic;时长 clamp(距离×0.35ms, 130, 300)),新增
     m_laneScrollAnimActive/From/To/StartMs/DurationMs。
   - 宿主 16ms 定时器(kScrollTimerId)调用 AdvanceLaneScrollAnimation() 逐帧推进,
     与信号面板动画共用同一条定时器;Render 的“需要保持定时重绘”判定加入
     LaneScrollAnimationActive()。
   - 每帧 ClampLaneScroll() + BuildLaneVisuals()(网格滚动量参与场景/图层指纹与各网格 rect);
     结束时精确落到目标。已完整可见的网格仍然不启动动画。
   - ⚠️ 用户自己滚动网格(滚轮 / 拖滚动条)会 CancelLaneScrollAnimation(),避免与动画抢滚动位置。

验证(全部离屏执行:窗口 SW_HIDE + 只注入合成消息 + 进程与线程 BELOW_NORMAL,
不移动真实光标、不抢焦点、不截屏):
- .preflight/check 扩到 95 项,95/95 通过;新增断言:
  · 菜单顶部标题在滚动时不动(Y 坐标不变)、滚动视口高度恰为 10 × 行高;
  · 滚动条轨道/滑块存在、随滚动移动、按住滑块拖到底(菜单不关、不下发命令、末条目入视口)、
    滚轮回到顶部;
  · ScrollLaneIntoView 不瞬移(刚触发时几乎未动)、中间帧单调递进、收尾精确到位、
    已可见时不启动动画、滚轮/手动滚动会取消动画。
- A/B:回退三处(标题固定 / 10 行上限 / 滚动动画)⇒ 10 项如实失败;恢复后 95/95。
- make release / make debug 零警告。

文档:Documents/agent_mem.md 新增 §9.39、§11.2 第 104–107 条与变更日志一行。
2026-10-16 14:12:42 +08:00
DeepSeek V4.1 Flash 2904699eae 修复五项交互缺陷:折起后滚动条 / 单点信号样式 / 线宽实时生效 / 网格菜单限高滚动 / 点选信号滚动绘图区
1) 分组折起后再展开,信号列表的滚动条消失(筛选态与常规态均可复现)
   - 根因:AdvanceGroupAnimations 只在“折起完成”时重建行集合;而“展开完成”那一帧
     m_contentHeight 从收起态跳回展开态,却没有任何路径重算滚动条可见性 ⇒ 滚动条停在隐藏。
   - 修法:任一方向动画结束都 m_rowsDirty = true + BuildRows(),并每帧 RefreshScrollBar(false)。
     ⚠️ 动画期间不夹取滚动偏移,避免打断与滚动同步的折起动画(scrollSynced)。

2) 单点信号(finiteCount == 1)默认绘制方式由「点线」改为「散点」
   - ApplySinglePointDefaultStyle → DrawMode::SCATTER(仍取最大线宽)。
   - 新增 SignalPanel::ModeOptionEnabled:单点信号的「线段」「点线」置灰、不可点、
     且不参与悬停高亮(避免“看着能点其实点不动”);样式区的绘制与命中都用它。
   - PlotView::UpdateAutoScatterStyles 跳过单点信号,否则“放大自动点线”会把它又改回「点线」。

3) 拖动线宽滑块完全不生效(拖动时线宽不变、松手瞬间变了、再移动鼠标又变回去)
   - 根因一:AcquirePolyline 的折线缓存键没有 lineWidth,而 BuildPolyline 把线宽烘进
     包络带几何(每个像素列的上下包络各向外扩 max(1,线宽)*scale*0.5,「梳状」判定也依赖它)
     ⇒ 稠密信号(采样数 > 绘图区宽度)永远复用旧线宽的顶点与包络带。
   - 根因二:样式补丁只更新“静态场景缓存 + 后备缓冲区”,而每网格图层(宽基底)仍是旧样式像素,
     下一帧命中“宽基底快路径”时把新样式覆盖回旧像素。
   - 修法:PolylineCacheEntry 增加 lineWidth 并进比较;
     样式补丁之后 QueueLaneRebuild(受影响网格) + RenderPendingLaneRebuilds(1)(沿用每帧至多 1 个的预算)。

4) 「目标」行的 GX 标签复用「所属网格」菜单,并先给 PopupMenu 增加「限高 + 条目区滚动 + 尾部固定项」
   - MenuLayoutConstraint::maxHeight(OpenLaneMenu 传客户区高度的 62%);
   - 固定区 = 最后一个分隔线及其之后的条目(即「新建网格」)始终钉在菜单底部,其余条目可滚动;
   - 滚轮滚动条目区(Application 在菜单内滚动时不再关闭菜单,其余情形保持既有行为);
   - 键盘移动悬停项自动滚入可视区(EnsureItemVisible);ItemAtPoint 要求条目同时命中滚动视口;
   - 滚动区文本用 UiTextClipped(GDI 文本不遵循 GDI+ SetClip),并跳过完全不可见的条目(性能);
   - BuildLayout 不再无条件清零 m_scrollOffset(否则 RebuildLayout 会把滚轮位置吃掉);
   - 「目标」行 GX 标签点击**不参与**联动滚动(悬停联动保留)。

5) 点选信号名 ⇒ 绘图区自动把目标网格滚动到可视区内(上百个独立网格时旧有高亮无法定位)
   - SignalPanel::RequestPlotLaneScroll / TakePlotLaneScrollRequest;
     Application::ApplyPendingPlotLaneScroll(WM_LBUTTONDOWN 的面板分支);
     PlotView::ScrollLaneIntoView(在可视区上方就上滚、在下方就下滚、已完整可见则不动)。
   - 只有“点击信号名”触发;悬停绝不改变视图;勾选框 / GX 徽标 / 移除 ✕ 也不触发。

验证(全部离屏执行,窗口 SW_HIDE + 只注入合成消息 + 进程与线程 BELOW_NORMAL,
不移动真实光标、不抢焦点、不截屏):
- 新增 .preflight/check(不入库)68/68 通过;
- A/B:五处修复全部回退 ⇒ 17 项如实失败(B 4 / C 4 / D 7 / E 2);“折起后滚动条”需要
  逐帧重绘驱动动画才能复现,单独回退时 ⇒ 5 项如实失败;恢复后 68/68;
- make release / make debug 零警告。

其它:
- 按要求删除工作区 Tools/(structcheck 脚本)并移除 Makefile 的 structcheck 目标;
- Documents/agent_mem.md 同步:新增 §9.38、§11.2 第 97–103 条与变更日志一行。
2026-10-16 11:06:31 +08:00
DeepSeek V4.1 Flash d2e6fdd085 收尾:LanePanelState 抽取 + 错误提示非模态化(清空上一轮的“未完成”)
一、Lane 的内聚性(上一轮建议 5,本轮完成)
- 新增 struct LanePanelState { bool open = true; double x = -1.0; double y = -1.0; };
- Lane 的 12 个平铺面板字段(edgePanelOpen/X/Y 等 × 4 类实验性功能)
  → 4 个聚合成员 edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel;
- 访问点全部改为 lane.edgePanel.open 形式(DataModelTab.cpp 快照拷贝、
  PlotViewInteraction.cpp 面板拖拽、PlotViewPaint.cpp 面板层序绘制);
- RebuildLanes 内部的 LaneCarry 同步收敛为 4 个 LanePanelState,
  12 行逐字段赋值简化为 4 行整体赋值。
- 教训:批量改名不能对“声明”和“使用”跑同一组替换对,
  否则声明行会变成 `bool edgePanel.open = true;` 这类非法代码。

二、错误提示与模态阻塞解耦(上一轮建议 1,本轮完成)
- Application 新增 SetModalErrorsEnabled(bool) / ModalErrorsEnabled(),
  成员 m_modalErrorsEnabled 默认 true(交互场景行为不变);
- Initialize 最先执行 SetModalErrorsEnabled(showCommand != SW_HIDE):
  隐藏窗口(自动化/离屏)下没人能点掉模态框,等于把进程钉死;
- SetStatusMessage(text, isError=true):允许模态时原样 MessageBoxW,
  否则改走非模态通道 m_plot.ShowPersistentHint(text)(绘图区底部浮层);
  普通提示与空文本依旧被忽略;
- PlotView 新增公开接口 ShowPersistentHint(text)(内部转 SetHintSticky),
  不向宿主暴露粘滞/淡出等内部细节。

三、顺带修掉一个真实缺陷
- PlotView::Paint 在 m_lanes.empty() 时提前 return,而提示浮层在其后的
  叠加层绘制 ⇒ 一个信号都没勾选时任何错误都显示不出来,恰恰是
  “文件加载失败”最需要提示的场景;该分支补一次 PaintHint(g)。

四、验证(全部离屏:SW_HIDE + 不注入输入 + BELOW_NORMAL)
- make release / make debug:通过,零警告;Build/Release/Target.exe = 764416 B;
- verify 49/49、datacheck 69/69、edgecheck 46/46、panelanim 156/156;
- 新增探针 .preflight/perf/errcheck 19/19:默认值 / 隐藏窗口自动降级 / 宿主显式开关;
  工作线程 + 5s 超时判定“不阻塞”(旧行为会永久阻塞,判失败并 ExitProcess,
  探针不会挂住);EnumWindows 统计本进程 #32770 顶层窗口数量前后不变;
  错误文本进入 m_hintText / m_hintSticky / HintNeedsAnimation / m_hintShownAt;
  像素级:有提示帧与无提示帧在绘图区内的差异像素 3781,且全帧差异同为 3781
  (证明差异只来自提示)。

五、文档
- Documents/agent_mem.md:§9.37 新增第八节(本轮补齐)、第九节(仅剩 MF4 项)、
  §11.2 新增第 93–95 条硬约束、变更日志新增一行;
- Documents/refactor_report_2026-10-16.md:§7.3 / §7.4 标记完成,新增 §九 收尾补记
  (含 9.1–9.5:聚合化、解耦、探针、验证结果、变更清单)。
2026-10-16 04:38:12 +08:00
DeepSeek V4.1 Flash e6b7bf66ec 按组件目录结构把四个巨型实现文件拆分为多个协作文件(PlotView / SignalPanel / Application / DataModel)
承接上一条提交(0871eb0)—— 该提交只完成了 Components/Shared 与注释/命名清理,并**主动未做**
大文件拆分;本提交把大文件拆分补齐,至此“按功能拆分逻辑块到各组件自己的 Inc/Src”已落地。

## 一、拆分结果

| 原文件 | 拆分后 |
| --- | --- |
| Components/PlotView/Src/PlotView.cpp(13 695 行 / 544 KB) | PlotView.cpp(787) + PlotViewPaint.cpp(6 367) + PlotViewInteraction.cpp(2 715) + PlotViewFeatures.cpp(3 005) + PlotViewInternal.hpp(936) |
| Components/SignalPanel/Src/SignalPanel.cpp(4 184 行) | SignalPanel.cpp(1 141) + SignalPanelPaint.cpp(697) + SignalPanelInteraction.cpp(2 095) + SignalPanelInternal.hpp |
| Components/Application/Src/Application.cpp(3 135 行) | Application.cpp(1 427) + ApplicationCommands.cpp(1 184) + ApplicationLoading.cpp(270) + ApplicationInternal.hpp |
| Utilities/Src/DataModel.cpp(2 615 行) | DataModel.cpp(1 583,含自由函数区) + DataModelTab.cpp(732) + DataModelInternal.hpp |

分片职责(以 PlotView 为例):
  core        视图核心:构造/析构、上下文、布局、坐标映射、视界与平移、网格结构与归属
  paint       渲染与像素缓存:全部绘制、场景/装饰缓存、每网格图层、宽基底与滚动补条
  interaction 交互与编辑:命中测试、鼠标/键盘、光标形状、内联输入框、浮动面板层序
  features    视图特性与派生数据:光标读数/提示/缩放、自适应、吸附、单点、派生曲线线程池、
              边沿统计、实验性功能参数
(SignalPanel 为 core/paint/interaction;Application 为 core/commands/loading;
  DataModel 为 core/tab。)

## 二、关键手法(这是能安全拆开的前提)

原文件的辅助区都是**匿名命名空间**(PlotView 约 870 行常量与工具函数),被所有成员函数广泛引用。
直接拆文件会让各 .cpp 拿不到这些工具,因此:

1. 把该块整块搬进 `<组件>Internal.hpp`,从 `namespace { }` 改为**具名**命名空间
   (pvdetail / spdetail / appdetail / dmdetail);
2. 块内**每个函数与常量都标 inline** ⇒ 多个翻译单元共用同一个定义:既不重复符号,
   也不产生 N 份函数定义;
3. 头部末尾 `using namespace <ns>;` ⇒ 各 .cpp 函数体里原有的非限定调用(IntersectRect /
   NiceStep / ClampDouble …)**一行都不用改**;
4. 成员定义按「方法名 → 分片」归类后**整块搬移** —— 全程**纯代码搬移**,没有改动任何一行函数体。

## 三、拆分过程中踩到并修正的四个坑(已固化到 .preflight/perf/split_component.ps1)

1. `[[maybe_unused]]` 等属性**必须排在 inline 之前**(`inline [[x]] T` 非法);
   单独的 `{`/`}` 行、注释行、`struct`/`enum`/`using`/`template` 行不能加 inline。
2. 匿名命名空间的**结束行可能带注释**(`} // namespace`),只认 `^\t}$` 会一路匹配到后面
   某个成员函数的结束括号,把大段成员定义误当成“辅助区”搬走。
3. **首个成员定义之前的所有行**必须跟着进核心文件:DataModel.cpp 在匿名命名空间之后、
   首个成员定义之前还有约 390 行命名空间外的自由函数(SignalStats / ComputeStats /
   CacheSparseSamples / ApplySinglePointDefaultStyle …),漏掉会被静默丢弃。
4. 内部头必须带上原文件真正用到的全部头:DataModel 需要 Theme.hpp / <cstring> / <cstdio> /
   <deque> / <atomic> / <cwctype> / <new>(原文件靠这些头才编译得过)。

## 四、附带完成的命名审计

PlotView 中残留的 File 语义命名一并修正:LaneSpansMultipleFiles → LaneSpansMultipleGroups、
firstFile → firstGroup、multiFile → multiGroup、SceneCacheKey::haloFile → haloGroup。

## 五、验证(全部离屏:SW_HIDE + 不注入真实输入 + 进程/线程 BELOW_NORMAL)

- .preflight/perf/verify.exe(PlotView 渲染/交互,含逐像素断言)49/49
- .preflight/perf/panelanim.exe(信号面板:卷尺式折起/展开、筛选态、悬浮组名、目标行)156/156
- .preflight/perf/datacheck.exe(数据层:单轴/多时间轴 CSV、MF4、重命名语义)69/69
- .preflight/perf/edgecheck.exe(边沿统计与面板排版)46/46
- make release / make debug 零警告。
即:拆分后**行为与拆分前完全一致**(纯代码搬移,四套探针全绿)。

## 六、体积(诚实记录)

  Target.exe: 754 176 → 764 416 B(+10 240 B,+1.4%)
因为 inline 工具函数会在每个引用它的翻译单元里各生成一份(PE/COFF 链接器没有 ICF 合并)。
这再次印证:拆分解决的是**可维护性**,不是体积(本轮的 −24.6% 全部来自 ICO 256×256 条目改 PNG)。

## 七、文档

Documents/agent_mem.md:§9.37 新增“七、多文件拆分(已完成)”与“八、仍未完成”(只剩
Lane → LanePanelState、模态提示解耦、MF4 压缩块三项);§11.2 新增第 91、92 条(多文件化的
唯一安全手法 + 体积代价)。Documents/refactor_report_2026-10-16.md 同步更新 §五/§七/§八。
2026-10-16 04:02:36 +08:00
DeepSeek V4.1 Flash 0871eb0cba 分组折起热区与改名输入框修正 + 数据模型去冗余(单一名称)+ 新增 Components/Shared 组件 + 过时注释/命名大清理 + 二进制瘦身 25.6%
## 一、UI 交互修复(用户报告)

① 分组折起热区(用户要求):点分组**名称**不再折起,只有**折叠三角**或**「组X」标签**
   才折起。RowPart 新增 GROUP_TITLE;RowHitPart() 为分组行判出三角 / 复选框 / 关闭 ✕ /
   「组X」标签矩形 / 其余 BODY;OnLButtonDown 只在这两个热区调用 ToggleGroupFromRow()。
   附带:双击「组X」不再进入改名(该区域现为折起热区),双击分组名仍可改名。
② 分组改名输入框不再吞掉「N 项」:RowNameRect() 对分组行的右边界改为「项数左边缘 − 6*scale」;
   新增 GroupLabelText()/GroupStatsText() 作为文本与宽度的唯一来源(PaintGroupRow 与
   RowNameRect 共用),避免名称可用宽度与实际绘制文本漂移。

## 二、数据模型去冗余(用户指出的重点)

- SignalData / SignalGroup **只保留一个名称字段** name:删除 displayName、删除 Title()
  (31 处调用点改为直接用 name);DataSet::SetSignalDisplayName / SetGroupDisplayName
  → RenameSignal / RenameGroup,且**空串 / 纯空白一律拒绝**(名称不再有回退值)。
  依据:信号靠 SignalRef 下标定位、从不按名字匹配 ⇒ 两个字段是同一语义的两份副本。
  收益:每条信号 / 每个分组少一个 std::wstring(24B + 堆分配)与一条 Title() 分支。
- 删除 Application::m_loadingBytes(全仓既不读也不写的死成员,注释却称“用于展示体积”)。

## 三、新增 Components/Shared 组件(通用 UI 控件)

- 从 Utilities/PaintUtil.* 迁出全部「控件类」绘制/命中:图标(+ / ✕ / 三角)、复选框、
  滑块(UiSliderRatioFromX / UiDrawSlider)、内联输入框(UiDrawEditField /
  UiDrawEditFieldContent / UiEditTextLeft / UiEditOffsetAt / UiEditFieldMouseDown /
  UiEditFieldMouseMove)、颜色样块(UiDrawSwatch / UiSwatchNeedsOutline)、
  取值胶囊配色(UiInk / UiColorLuminance / UiContrastInk)。
- PaintUtil.* 只保留低层绘制原语(矩形 / 圆角 / 直线 / 批量实线 / 颜色混合 / 文本与测量)。
- 依赖方向固定:Components/* → Components/Shared → Utilities(不得反向,Utilities 不再含控件)。
- Makefile 与 .preflight/perf/build.ps1 已登记新目录;调用方改 include "UiControls.hpp"。

## 四、过时注释 / 命名批量修正(承接上一轮未做完的部分)

- 注释:文件下标→分组下标、文件内信号下标→组内信号下标、所属文件下标→所属分组下标;
  措辞统一 文件分组→分组、移除该文件→移除该分组、打开 CSV 文件→打开数据文件 等一批。
- 文不对题的形参名:VisibleIndexRange(const TimeAxis& file,…) → axis;
  Application::LoadFileAsync(…, SignalGroup& file,…) → group。
- 函数名里的 File 语义(上一轮只改了 RowKind::FILE):PaintFileRow→PaintGroupRow、
  FileCloseRect→GroupCloseRect、OpenFileMenu→OpenGroupMenu、FileGroupLabelWidth→GroupLabelWidth、
  StyleEditField::FILE_NAME→GROUP_TITLE;局部变量 fileRow/fileHeight/isFile →
  groupRow/groupHeight/isGroup。
- 修复 DataModel.hpp 两处被压扁的注释块(/**<TAB><TAB>* @brief …)与 Application::NewTab
  上方重复的文档块。
- ⚠️ 踩坑:GROUP_NAME 撞上 nb30.h(windows.h 引入)的 #define GROUP_NAME 0x80,
  导致枚举编译失败,改名 GROUP_TITLE 解决。

## 五、二进制瘦身:1 013 248 → 754 176 B(−25.6%,省 259 KB)

- 真因不是 PlotView.cpp,而是 Resources/JustDataInspector.ico 里 **256×256@32bpp 以未压缩
  BMP 存储的条目(270 376 B,占整包 27%)**。改为 PNG 存储(Vista+ 标准做法,观感零变化)
  ⇒ 该条目 270 376 → 11 280 B、整个 ico 302 430 → 43 334 B(小尺寸仍保留 BMP)。
- 实测结论:“拆分大文件 ⇒ 瘦身”不成立(同一批机器码仍要进二进制);
  -ffunction-sections/-fdata-sections/-Wl,--gc-sections 在本工程反而 +9 216 B(已回退)。
- ⚠️ .o 体积受节对齐/重定位表主导,判断“代码搬去哪了”要用 nm,不要看 .o 字节数。

## 六、验证(全部离屏:SW_HIDE + 不注入真实输入 + 进程/线程 BELOW_NORMAL)

- panelanim 156/156(连跑 5 次稳定;新增/更新:点分组名不折起也不滚动、扫掠定位「组X」热区、
  筛选态与普通态折叠三角逐像素一致、改名输入框右边界早于项数文本);verify 49/49;
  datacheck 69/69(含“重命名写入唯一名称字段且空名被拒”);edgecheck 46/46。
- A/B:回退两处 UI 修复 ⇒ 10 项如实失败;恢复后全绿。
- make release / make debug 零警告。

## 七、本轮未完成(已在报告与 agent_mem.md 中如实记录)

- PlotView.cpp(13 695 行 / 544 KB)按功能拆分为多个协作文件:未执行。
  前置改造是把第 13–888 行的匿名命名空间(~870 行常量与工具函数)改成具名命名空间并逐个
  inline,再逐个文件搬移并反复验证;时间不足以安全完成,故主动不做(附可执行方案)。
- SignalPanel.cpp / Application.cpp / DataModel.cpp 的多文件化、Lane 抽出 LanePanelState、
  SetStatusMessage 模态阻塞解耦:均未执行。

## 八、文档

- 新增 Documents/refactor_report_2026-10-16.md(本轮详细报告);
  删除 Documents/refactor_report_2026-10-15.md(有效内容已并入 agent_mem.md 与本报告)。
- Documents/agent_mem.md 新增 §9.37 与 §11.2 第 85–90 条;更新数据层架构摘要(单一名称字段);
  §12 历史附录加“时效声明”。
2026-10-16 03:25:48 +08:00