-------------------------------------------------------------------------------- 软件下载与售后支持、需求提出 -------------------------------------------------------------------------------- 官网marrychip.com 逻辑分析仪QQ群:389908364 FPGA/IC吹水群:377248676 雷电扩展坞群:807604723 -------------------------------------------------------------------------------- PXView V1.6.4修改 时间:2026年9月15日 变更如下: 【问题修复】 *修复 MCP 配置写入与 Qt GUI 不同步(系统性梳理全部 10 个写配置类 MCP 工具后统一修复): - TriggerDock::update_view() 原为空函数,TriggerConfigChanged 广播链(MCP configure_trigger / DeviceOptionsDock CAPTURE_RATIO 同步钩子)到达 GUI 后什么都不做,滑条/阶段控件不回填;实现改为统一走 refresh_ui_from_core()(triggerdock.cpp) - on_trigger_config_changed 补刷新 DsoTriggerDock,MCP set_dso_trigger_config 新增广播 typed TriggerConfigChanged(event_dispatcher.cpp、session_service.cpp) - MCP 通用 set_config_*(string/bool/uint64/int32/double/byte)原只写驱动零广播,DeviceOptionsDock 所有键均不刷新;新增 notify_device_options_updated() 成功后广播 typed DeviceOptionsUpdated(采集中跳过,避免 reload() 破坏采集状态)(session_service.cpp、session_service.h) - MCP configure_probe 旧实现只下发 PROBE_FACTOR,vdiv/coupling/mapDefault 三个参数形同虚设;且 PROBE_FACTOR/PROBE_VDIV 驱动声明类型为 SR_T_UINT64,原 double 读写会被 hwdriver 层类型检查静默拒绝;补齐 4 键下发(vdiv 伏特→毫伏换算、API 耦合 AC=0/DC=1→驱动 0=GND/1=DC/2=AC 映射)、改用 DeviceAgent typed wrapper 读写并同步 Core SignalModel(session_service.cpp) - MCP configure_channel 的 enabled/name 原只写驱动,GUI header 勾选与标签不更新;补 SignalModel::set_enabled/set_name 同步(镜像 sr_channel 并 emit 信号实时刷新 View)(session_service.cpp) - configure_and_start 的 capture_ratio 原只写驱动,采集启动时被陈旧 Core TriggerConfig 覆盖;补同步写 Core TriggerConfig(session_service.cpp) - MCP set_sample_rate/set_sample_limit/set_time_base 成功后补广播 DeviceOptionsUpdated,与 GUI SamplingBar 提交后的广播语义一致(session_service.cpp) - MCP 通用 set_config 写 SR_CONF_CAPTURE_RATIO(uint64/int32/byte)成功后同步 Core TriggerConfig,与 GUI DeviceOptions::config_setter 钩子一致,避免采集启动时被陈旧 Core 值覆盖(session_service.cpp、session_service.h) - 修复 DeviceAgent::set_channel_name 为空壳实现(写入代码被注释、直接 return true),MCP configure_channel 改名从未落盘到 sr_channel->name;改为直接 g_strdup 写入(deviceagent.cpp) - 修复 MCP set_dso_trigger_config 的 TRIGGER_SOURCE/TRIGGER_SLOPE 静默失败:驱动声明类型为 SR_T_UINT8(hwdriver.c),原 set_config_int32/get_config_int32 被类型检查拒绝,且 any_ok 被成功的 horizPos 掩盖;改用 set_config_byte/get_config_byte,实测 source/slope/horizPos 全部落盘回读(session_service.cpp) - 修复 configure_probe 探针通道定位错误:API channelIndex 是 sr_channel index(demo 的 DSO/模拟通道 A0=32 起),原脚本用 0 命中逻辑通道导致驱动拒绝;检查表脚本改用 O0(37) 并实测 vdiv/vfactor/coupling/mapDefault 落盘(tests/check_mcp_gui_sync.py) - 修复 MCP 改名/启停通道后 GUI header 不刷新:view::Signal 的 Trace 名称缓存与 _local_enabled 不回读 Core SignalModel,on_appearance_changed/on_visibility_changed 重绘前从模型同步(signal.cpp) - DeviceOptionsUpdated 新增 from_external 字段区分事件来源;MCP 写入触发时 GUI 的 DeviceOptionsDock 全量重读驱动(原 device_updated() 在绑定存在时空转,RLE 等键值控件不刷新)、SamplingBar reload(采样率/深度 MCP 修改后不刷新);GUI 自身提交路径行为不变(events.h、session_service.cpp、event_dispatcher.cpp) - 修复 MCP configure_channel 禁用通道后 GUI 通道仍占位:from_external 分支原先提前 return,跳过了 rebuild_signals()/signals_changed()——reload() 重建 SignalModel 后 View 的 Signal 对象必须同步重建(按 model->enabled() 决定显示/占位);改为两个分支共用保存布局 + rebuild 尾部(event_dispatcher.cpp) - DeviceOptionsDock::update_view() 全量重建后保留外层滚动容器位置(修复 MCP 触发 dock 刷新后滑动条回顶)(deviceoptionsdock.cpp) - 检查表脚本修正:DSO 触发 horizPos 单位为 0..100 整数百分比(原 0.3 被驱动取整为 0,无法核对)、slope 改 1 便于观察变化、探针步骤写明 channelIndex 是 get_channels 的 sr_channel index(GUI 显示名 O0)(tests/check_mcp_gui_sync.py) - 修复禁用通道在波形区占位(MCP configure_channel 禁用与 DeviceOptionsDock"全部禁用"均复现):header 只画 enabled() 的行而布局按 visible() 分配高度,禁用通道仍占一行(并触发 rasterize_logic_channel 对空数据行的无效绘制告警"no data or samplerate==0");布局统一按 enabled() 跳过禁用的非 DSO 通道(view_signal_sync.cpp) - 修正 MCP 工具描述的工作模式编号错误:实际映射为 0=Logic, 1=Analog, 2=DSO, 3=MSO(api WorkMode 枚举),原描述"1=DSO, 2=Analog"与实现相反,导致按文档切 DSO 实际切到 Analog;同步修正 switch_work_mode/get_work_mode/get_supported_work_modes/get_channels 的描述与 configure_probe 错误提示(mcp_tool_registry.cpp、tests/check_mcp_gui_sync.py) - 修复禁用通道灰行残留的真正根因(MCP configure_channel 禁用后 header 仍画灰色占位行,而 DeviceOptionsDock 路径正常):SignalFactory::update_signals(AllReplaced) 的 restore_ui_state 用旧 Signal 的 visible(恒为 true)覆盖了 apply_model_properties 刚按模型设置的 visible=false,paint_label(trace.cpp 对非 DSO 仅挡 !visible())照画灰行并占旧槽位;restore 时以 model->enabled() 钳制 visible,Signal::on_visibility_changed 同步 set_visible(signalfactory.cpp、signal.cpp;运行日志证据:禁用后 reload models.size()=31 但灰行仍绘制) - 修复 configure_channel 不触发 GUI 刷新的漏调用(dock 网格与 viewport 残留的真正根因):set_channel_enabled/set_channel_name 此前只发 ServiceEvent(仅 MCP/WS 客户端可见),漏调 notify_device_options_updated() → typed DeviceOptionsUpdated 从未广播,GUI 的 dock 刷新 handler 整条链从未执行;补上后调试日志(%TEMP%\pxv_dock_dbg.log)证实 from_external=1 → deferred 刷新 → update_view/save/rebuild_signals 完整执行(session_service.cpp) - 外部写入的 GUI 刷新整体延迟一拍(QTimer::singleShot(0)):同一广播先派发 SigSession::reload() 重建 SignalModel,GUI 刷新若同步执行读到 reload 前旧模型 → dock 网格/viewport 分组卡片残留;延迟后保证读到最终状态(event_dispatcher.cpp) *修复 MCP 服务器 initialize 返回的 instructions 恒为空:McpServer 仅经 set_instructions_file("pv/mcp/mcp_instructions.txt") 以进程 CWD 相对路径读文件,install.dir 部署下该文件不存在、静默失败留空,LLM 客户端从未收到过系统提示词(GUI McpControlDock 显示的编译期嵌入常量 kInstructions 与 SDK 实际下发内容不一致);改为 create_mcp_server 直接 set_instructions(pv::mcp::kInstructions),单一数据源编译期嵌入、零运行时文件 I/O(mcp_tool_registry.cpp) *修正 MCP 工具文档与实现脱节导致 LLM 按文档首次调用即失败的 5 处(系统性核对 45 个工具描述/schema 与 handler 行为): - start_capture 的 captureMode 枚举文档写 'timed'/'manual'/'stream',实际 handler 只认 single/manual/repeat/loop('timed'/'stream' 被静默忽略、collect mode 不变、无任何报错),真正的硬件流式采集是 channelMode="Buffer"/"Stream"(SR_CONF_CHANNEL_MODE,大小写敏感);修正字段文档与 instructions 的 Capture Modes 节,明确 captureMode(collect mode)与 channelMode(硬件采集模式)两个参数的分工(mcp_serializers.h、mcp_instructions.txt) - mcp_instructions.txt 工作模式编号写反(写 1=DSO/2=Analog,实际 WorkMode 枚举 1=Analog/2=DSO),按它操作第一次 switch_work_mode 即切错模式;且 Mode-Specific Constraints 节引用已删除的 get_math_results/get_spectrum_results/get_lissajous_results 工具名与文末"已移除工具"节自相矛盾;同步修正模式编号、推断规则与工具名(mcp_instructions.txt) - set_config 的 value 参数 inputSchema 声明 "type":"object" 但 handler 期望标量(value.get/uint64/string/...),严格校验的客户端拒绝发请求、宽松客户端发对象则抛类型异常;any_param 改传空 json_type(SDK 约定省略 type 关键字=任意 JSON 类型),描述给出各 type 对应标量示例(mcp_tool_registry.cpp) - set_sample_config 的 timeBase 描述写 "Time base in samples",实际直写 SR_CONF_TIMEBASE uint64、单位纳秒,按样本数发送时基差数个数量级;get_config/set_config 的 type 枚举描述漏 uint64(采样率/样本数类键必需,仅错误消息里可见)(mcp_tool_registry.cpp) - configure_probe 的 coupling 描述写反(写 0=DC/1=AC,实际 API Coupling 枚举 AC=0/DC=1,驱动映射 0=GND/1=DC/2=AC)(mcp_tool_registry.cpp) *补全 MCP 工具参数文档中 LLM 无法自行得知的约束/单位/枚举值:configure_trigger 的 configJson 给出 JSON 结构 {"enabled":0|1,"position":0-100,"trigger_config":{...}} 并建议先无参 GET 取模板再改、horizPos 注明百分比 0-100(驱动 uint8 hrate)、source 补 3=CH0 and CH1/4=CH0 or CH1、holdoff/margin/channel 标注 reserved 实际不下发驱动(set_dso_trigger_config 只写 source/slope/horizPos 三键);configure_glitch_filter 的 modes 补 GlitchFilterMode 枚举 0=Both/1=High/2=Low,channels/thresholds/modes 标注平行数组等长约束,configure_signal_invert 同;export_data_table_csv 的 radixType 补 0=保留解码器原始文本(switch 仅处理 1-3、4=Ascii 不格式化);get_samples 的 logic 返回注明每样本 1 字节(0/1)再 base64;save_capture 的 endSample 修正为 startSample<=endSample 硬约束(原 "0 = all" 与 set_save_range 的 start<=end 校验矛盾,start>0&end=0 必报错);find_next_edge 的 risingEdge 注明默认 true;set_sample_config 描述中已删除的 get_sample_config 引用改为 get_session_status include='config'(mcp_tool_registry.cpp、mcp_serializers.h) *复查 MCP 工具 schema 与描述的残余缺口(第二轮,全量导出 45 个工具 inputSchema 逐一核对): - 修复 json_type_name 模板缺 int8_t/uint8_t 特化:落入主模板默认值把 schema type 生成 "object",switch_work_mode.mode(必填)、get_channels.mode、set_sample_config.collectMode、configure_probe.coupling 四参数受影响,严格校验的 MCP 客户端会拒绝 {"mode":0} 这类合法整数实参(而 Params 运行时类型检查按 is_any_type/integer 误判路径放行,问题被掩盖);补 int8_t/uint8_t→"integer" 特化后四参数 schema 均为 integer(mcp_params.h) - 修复 get_active_decoders.includeConfig 描述与实现不符(写 "include configurations",实际 handler (void)p 完全忽略该参数);改为如实标注 accepted-but-ignored 并列明响应实际字段 instance_id/decoder_id/display_name/row_index/is_running/progress(mcp_tool_registry.cpp) - 5 处 channelIndex 参数描述补"以 get_channels 返回的 index 字段为准"(configure_channel/configure_probe/get_samples/find_next_edge/find_pattern;demo 设备 DSO/模拟通道 index 从 32 起,按显示顺序猜测会命中错误通道)(mcp_tool_registry.cpp) - find_pattern 多通道模式补 state 取值('1'/'0'/'x')与"所有通道须在同一样本位置同时匹配"语义(mcp_tool_registry.cpp) - get_config/set_config 的 key 参数原为纯数字且无任何查询途径,LLM 无从得知 SR_CONF 数值:mcp_instructions.txt 新增 Generic Config Keys 表(SAMPLERATE=30000、LIMIT_SAMPLES=50001、TIMEBASE=30010 纳秒、CAPTURE_RATIO=30001(0-100)、PROBE_VDIV=60035 毫伏、PROBE_COUPLING=60036(0=GND/1=DC/2=AC)、VTH=60033、PWM0/1_EN/FREQ/DUTY 等约 30 键的数值与 type 映射,类型均经 demo/pxlogic 驱动 config_get 源码核实),两工具的 key 参数描述同步引用该表,set_config 补"成功写入不等于 SignalModel 重载,优先使用专用工具"提示(mcp_instructions.txt、mcp_tool_registry.cpp) *第三轮复查补齐"LLM 无从得知的取值选项/来源"类缺口: - load_capture/create_session 的 filePath 原只写"文件路径",未说明支持格式;补齐 '.pxl'(采集数据存档,save_capture 的输出格式)/'.pxc'(会话/配置文件)及上游 sigrok 会话 zip(load_capture 还注明会把会话设备切换为文件设备)(mcp_tool_registry.cpp) - save_capture 的 filePath 注明输出为 '.pxl' PXView 会话存档(mcp_tool_registry.cpp) - add_analyzer/reconfigure_decoder 的 options/channelMap 补 key 来源:option id 与解码器通道名均通过 get_analyzer_options 查询(其响应含 channels/option id 列表)(mcp_tool_registry.cpp) - 5 处 analyzerId/stackOnAnalyzerId 补来源(add_analyzer 响应或 get_active_decoders 的 instance_id)(mcp_tool_registry.cpp) - export_raw_data 的 digitalChannels/analogChannels 原未说明省略行为,实测 csv/vcd/hex/bits 格式省略列表 = 不导出任何文件、仅 binary 默认导出全部启用通道;按格式分别写明(mcp_tool_registry.cpp) - start_capture 的 digitalChannels/analogChannels、configure_cursors 的 index 补取值来源(get_channels / action='get' 列表)(mcp_serializers.h、mcp_tool_registry.cpp) *修复被禁用通道重新启用后排序到尾部(无需拖动其它通道即可复现):根因是禁用期间 SigSession::reload() 只为 enabled 通道创建 SignalModel,被禁通道的模型与 view::Signal 一并移除,此后任何 save_signal_config 的 channel_layout 中都没有该通道,SignalConfigStore 对缺失通道把 view_index 强制写 -1(本意防跨模式过期传播),重新启用后 restore 只认 view_index>=0,归一化把 -1 通道排在所有显式通道之后;改为 channel_layout 缺失时若 work_mode 与通道类型均未变化则从旧配置继承 view_index,跨模式仍写 -1(signalconfigstore.cpp) *修复 viewport 分组卡片与 header 不同步(残留色块):Header::paintEvent 画分组卡片时已跳过 disabled 通道(其 v_offset 不再被 layout 更新、是陈旧值),但 viewport 侧 GroupCardBackgroundPass::render 无任何 enabled 检查,禁用通道仍按陈旧 v_offset 画彩色矩形并污染分组边界——禁用通道后 header 行消失而 viewport 残留色块/卡片错位;改为与 header 完全一致的过滤规则(enabled||as_dso),组内无可绘制通道整组跳过(render_pass.cpp) *修复分组卡片排序键与 INT_MAX 哨兵问题:卡片排序键原取 groups[i].traces[0] 的原始 v_offset,traces[0] 可能是 disabled(陈旧偏移)或未布局(Trace 构造初始 _v_offset=INT_MAX 哨兵)的通道,导致卡片绘制顺序错乱、互相覆盖,未布局通道参与边界计算还会把卡片撑到无穷远;排序键改为组内第一个可绘制(enabled/DSO 且非 INT_MAX)通道的 v_offset,边界计算与彩色矩形绘制同步挡 INT_MAX,header 与 viewport 两侧规则统一(render_pass.cpp、header.cpp) *修复解码器选项窗口的下拉列表伸出对话框外时点不中(点击溢出部分的列表项会关闭整个对话框):根因是 DecoderOptionsDlg::eventFilter 用 rect().contains() 判定"点击在窗口外",而 rect() 只覆盖对话框本体,DsComboPopup 下拉列表是独立顶层窗口(Qt::Tool | FramelessWindowHint,为规避 Qt::Popup 的 grabMouse 使按下/释放跨弹窗被判为外部点击而无限闪烁,刻意不用 Qt::Popup),其溢出 rect() 的部分一律被误判为"点了窗口外"→ reject() 关掉整个对话框;原兜底 qApp->activePopupWidget() 只跟踪 Qt::Popup 类型窗口,对 Qt::Tool 恒为 nullptr,兜底完全失效;且两个事件过滤器都装在 qApp 上,对话框的(exec() 里先装)比 popup 的(构造函数里才装)先收到事件,popup 自身的外部点击判定永远执行不到(日志中从无 eventFilter: outside click)。改为以"鼠标落点所属的顶层窗口"为判定基准:QApplication::topLevelAt(globalPos) + parentWidget() 父链归属判定(新增文件内静态辅助 belongs_to_dialog()),popup 的 parentWidget() 是 DsComboBox、父链连通到对话框故判为内部不关闭,点真正的对话框外部仍正常关闭(decoderoptionsdlg.cpp) *修复加载 .pxl 后采样栏不显示采样率与采样时间:根因是采样栏两个下拉框完全由驱动的类型列表构建,而 .pxl 回放所用 session_driver 在 devopts 里只声明 SR_CONF_SAMPLERATE | GET | SET(无 LIST),其 config_list 走 STD_CONFIG_LIST(NO_OPTS, NO_OPTS, devopts) → std_opts_config_list 只处理 SCAN_OPTIONS/DEVICE_OPTIONS、其余一律 SR_ERR_NA,sr_config_list 又要求 key 带 LIST 标志,故 get_config_list(SR_CONF_SAMPLERATE) 恒为 nullptr;SamplingBar::update_sample_rate_selector 原在该分支清空下拉框后直接 return,既丢掉设备内已正确解析的采样率(session_file.c 由 header 的 "samplerate = ..." 写入 SR_CONF_SAMPLERATE,get_sample_rate() 本可读到),又连带跳过 update_sample_count_selector(),采样深度/时基框也不按本文件重建;改为无列表时回退为"当前值单项"(文件采样率是文件固有属性,本就不可改)并继续走公共尾巴 update_sample_rate_selector_value + update_sample_count_selector,使采样深度按 total samples / samplerate 显示真实采集时长;顺带修复该函数 have_instance()==false 提前 return 未重连 currentIndexChanged、使采样率下拉框永久失联的问题(samplingbar.cpp) *修复应用毛刺滤波/信号反相时界面长时间"未响应"(大规模波形下必现):根因不是算得慢而是主线程被挡住——(1) clear_glitch_filter/clear_signal_invert 把"滤波运行中直接返回"的守卫排在 _backup_mutex 之后,滤波进行中点"清除/撤销滤波"会先被阻塞整整一趟、再发现正在运行并什么都不做地返回,用户看到的是"点了按钮界面死住";(2) FilterProcessor::wait_idle(int max_wait_ms) 的实现把形参丢弃(void wait_idle(int))并直接调无界的 ThreadPool::wait_for_idle(),而调用点 init_signals 在主线程上,改采样率/重新采集/切设备都会让事件循环停摆到滤波结束。改为守卫前置到取锁之前、_backup_mutex 改 std::timed_mutex 并以 try_lock_for(200ms) 有界尝试(抢不到就放弃本次请求,宁可"没做事"也不让事件循环停摆)、wait_idle 真正实现超时并在等待前置协作取消标志(滤波扫描每轮轮询一次,边界等待从"整个滤波时长"塌缩到毫秒级;超时则拒绝向下重建快照,避免把快照从 worker 脚下抽掉)(filterprocessor.cpp、filterprocessor.h、thread_pool.h、sigsession.cpp) *修复滤波过程中日志 I/O 拖长临界区:STABLE / SKIP-FILTER 两个分支原来每个"稳定跳变"都打一条 pxv_info(格式化 + 落盘全部发生在持锁窗口内),密集边沿信号会产出百万级日志行;改为与 GLITCH 分支一致的前 5 条 + 每 1000 条节流(logicsnapshot_glitch_filter.cpp) *修复渲染端读取滤波区间表的数据竞争(悬垂指针隐患):LogicSnapshotGlitchFilter::get_filtered_ranges 返回的是 worker 线程正在 push_back 的 vector 的 const&,渲染线程每帧遍历它,一旦触发扩容,渲染线程手里的迭代器即悬垂(原注释声称的 "thread-safe" 不成立);改为滤波过程中把区间累积到本地 vector、整趟结束后一次性以 std::shared_ptr> 发布为不可变快照(读者按引用计数持有),读者永远看不到半截或正在扩容的表(logicsnapshot_glitch_filter.h、logicsnapshot_glitch_filter.cpp、logicsnapshot.h、logicsnapshot.cpp、rasterize.cpp、header.cpp) *修复渲染 overlay 每帧全量遍历区间表:rasterize_logic_channel 的红/橙 overlay 原对整张区间表线性遍历、并在循环内做屏幕外剔除,密集波形(百万级区间)下仅这一步就足以拖慢绘制;改为利用区间表已按 start 升序这一不变式做二分定位可见窗口(O(log n + 命中数))(rasterize.cpp) *修复渲染路径每帧的堆分配:rasterize_logic_channel 的 cur_pulses / cur_edges / wave_lines 三个局部 vector 每帧每信号都要堆分配(内容每帧全量重建,是纯 scratch);改为函数内 thread_local scratch 跨帧复用,绘制线程单线程故无需加锁,把分配器从 paintEvent 路径上移除(rasterize.cpp) *修复滤波 Mipmap 重建量远超实际改动:apply_batch 原从本批样本区间反推 [start_blk, end_blk) 并对其中每一个叶块做整块重建(memset 33KB + calc_mipmap 重算 16.7M 采样的全部 mipmap 层),一批跨 1000 块就是 1000 次全块重算,而实际被改写的块通常是个位数;改为在写入循环里记录真正写过的 (idx0,idx1)、去重后只重算这些块(logicsnapshot_glitch_filter.cpp) *修复 copy_from 在解码迭代器活跃时把 _ch_data 翻倍并读到旧数据:free_data() 在 has_active_iterators() 为真时直接 return(P1-6 的保护),而 copy_from 其后无条件对 _ch_data 逐个 push_back,于是 _ch_data.size() 翻倍且前半段是改写前的旧块(内存翻倍 + 数据陈旧);改为先 wait_active_iterators_zero(3000) 排空迭代器、排不空则拒绝本次拷贝并置 _memory_failed,并补一道 free_data 后的空性复核(防止排空与调用之间新出现的读者再次触发延迟)(logicsnapshot.cpp) *修复"改数据"与"解码"的先后顺序(解码结果会被拼成两个版本):解码线程读的正是被滤波/反相就地改写的叶块,原顺序是"先改数据 → 完成事件里再停解码并重启",这中间解码器会把改写前/改写后的样本拼进同一条注解(结果随后被丢弃,但窗口与 CPU 浪费都不必要);改为 FilterProcessor 在改写任何采样之前先调 clear_all_decode_task2() 停掉解码(worker 线程安全,DataFeedParser 在采集边界用同一函数同一理由),并把"重新解码"的职责从 View 的四个事件处理器上移到 Core 的 SigSession::on_snapshot_edited()(同一事件的订阅者)——因为 pxviewd / MCP 路径没有 View,挂在 View 上的重启在那里不会发生,解码会被永久停掉(filterprocessor.cpp、sigsession.cpp、sigsession.h、event_dispatcher.cpp) *修复协作取消在批内不被响应(前一版改动自身留下的小缺口):apply_glitch_filter 只在扫描循环里轮询取消标志,而单个 apply_batch 可能要触及上百个叶块、每块重建整份 mipmap(16.7M 采样)即秒级工作量——采集/配置边界请求取消时要等这一批跑完才生效,恰好抵消了协作取消本要消除的那段停顿;改为在批内逐块重建 mipmap 之前也轮询取消,中断处半成品 mipmap 由调用方随后的 revert_all_edits() 重算覆盖(logicsnapshot_glitch_filter.cpp) *修复 copy_from 深拷贝期间源快照正被编辑(跨版本拼接):copy_from 按裸指针逐块 memcpy 源快照的叶块,若该快照上正跑一批毛刺滤波/反相写入,就会在"mipmap 已刷新、memcpy 未到"之间读到两个版本拼接的数据;改为整趟持有源快照的 EditReadPin(让后台写入批次等待——它是后台线程,而 copy_from 服务的是用户发起的保存/复制),并沿用既有的 _mutex → _edit_visibility 锁序(logicsnapshot.cpp) *修复大文件滤完波后波形不变(小文件正常):根因是上一版给可逆编辑日志设的预算是**记录条数**上限,而条数正比于毛刺个数而非内存开销——现场 2.5 G 采样、每 10 采样一个 5 采样窄脉冲的采集产出 8,060,928 条记录,撞上 8,000,000 条的计数上限,而其有效载荷只有 11.6 MB(每条记录自身开销约 96 B,合计约 900 MB),字节预算只用到 4%;整趟因此中止并回滚,用户看到的就是"大文件没变、小文件正常"。改为按**内存**计预算(载荷 + 记录数×每记录开销)并按快照大小给定(floor 64 MB、ceiling 2 GB),语义是"永不比它取代的全量备份更严"——密集到毛刺铺满整通道时日志本就退化到约 1× 通道数据,正是 _logic_backup 原来的代价,在那里拒绝反而是相对旧代码的回归;同时滤波被拒时经 set_error/session_error 给出用户可见报错,不再只留一行日志(logicsnapshot_glitch_filter.cpp、logicsnapshot_glitch_filter.h、filterprocessor.cpp) *修复编辑日志在密集毛刺下退化成"每个毛刺一条记录":各写入段原先独立记账,且毛刺间隔小于 8 采样时相邻段的字节窗口还会重叠(1 字节 = 8 采样),于是记录数随毛刺数线性增长;改为把同一块、间隔不超过 64 字节的相邻写入段合并进同一条记录(重叠同样合并——重叠部分存的是"上一次写之前"的原始内容,扩展部分此时尚未被写,故合并后的 blob 仍是整段原始内容),记录数由"每毛刺一条"降为"每块每批一条"(实测 419,430 个毛刺合并为 1 条记录)(logicsnapshot_glitch_filter.cpp) *修复撤销无法恢复"被 RLE 折叠释放"的块(表现为撤销后波形仍是滤波结果):当一趟滤波把某个叶块的跳变全部抹平(整块变成常量电平)时,calc_mipmap() 会把它折叠成常量(RLE)表示并 push_to_free_list 释放存储——该路径在 _able_free 为真的会话(stream/repeat 模式、able_free 采集)会真正执行;此后撤销时的字节回放因块指针已是 nullptr 被 if(ptr) 静默跳过,块停留在滤波后状态且 tog 位为 0,读者于是走常量分支、整块返回同一电平,撤销看起来完全无效;改为按元数据中记录的电平把块重建出来(未被记录的字节本就没有被滤波改过、仍是该常量)再回写原始字节,随后重算 mipmap 恢复 tog/first/last(logicsnapshot_glitch_filter.cpp) *修复滤波过程中波形不刷新(必须滚动视图才看到已滤好的部分):根因既不是锁也不是算得慢,而是整趟滤波期间**没有任何人请求重绘**——渲染刷新需要同时满足"信号 pixmap 被置脏"与"排了一次重绘",二者的触发源只有采集数据包、解码增长、布局变化与 DataUpdated 事件;而滤波期间采集已停止(无数据包)、解码被暂停、布局未变,DataUpdated 只在整趟结束时广播一次,每通道的 GlitchFilterProgress 只更新状态栏文字、不碰 viewport,于是渲染侧始终认为缓存有效、持续 blit 滤波前那份 pixmap,直到用户滚动改变 scale/offset 强制重建 pixmap 才看到进度(现场日志印证:2.5G 采样那趟 3943 次 apply_batch,整段日志里 GUI 侧零活动)。改为让数据层在每提交一批就地编辑后回调(apply_glitch_filter 新增 batch_callback,回调在独占写事务内、契约是不得阻塞也不得读快照),Core 侧以 150ms 限流后走既有的 DataUpdated → 置脏 + 重绘契约发布"可见修订",不新造刷新路径;反相路径同样按通道发布(logicsnapshot_glitch_filter.cpp、logicsnapshot_glitch_filter.h、logicsnapshot.h、logicsnapshot.cpp、filterprocessor.cpp、filterprocessor.h) *修复点"清除/撤销滤波"时界面长时间"未响应"(大文件上整趟撤销期间窗口卡住):根因是 clear_glitch_filter / clear_signal_invert 在 **GUI 线程**上同步跑完整趟撤销——revert_all_edits 重放最多约一份通道数据(2.5G 采样实测约 300~625 MB 字节回写)、并为每个被触及的叶块重建整份 mipmap(16.7M 采样),末尾还要 restart_decode_tasks() 去 join 解码线程;改为把撤销工作提交到 FilterProcessor 的线程池,完成时由 GlitchFilterCleared / SignalInvertCleared + DataUpdated 通知(与同步路径完全相同的刷新链)。同步语义保留给 API/MCP:MCP 的 configure_glitch_filter(channels=[]) 返回 {"cleared":true} 后客户端可能紧接着读采样,异步则读到的是带滤波的数据,故拆成两个入口——clear_glitch_filter/clear_signal_invert 同步等待(120s 上限,仅在非 GUI 线程调用)与 request_clear_glitch_filter/request_clear_signal_invert 异步提交(GUI 三个调用点:清除按钮、取反切换、Ctrl+Z 撤销全部改走后者);提交前的廉价检查留在调用方线程且不碰 _edit_mutex(守卫顺序),并在提交时丢弃排队的 apply 请求(最新指令优先),worker 侧持锁后重新核对 active 标志(幂等)(filterprocessor.cpp、filterprocessor.h、sigsession.cpp、sigsession.h、view_glitch_filter.cpp) *修复后台编辑期间 paintEvent 阻塞(把撤销搬到 worker 线程并不足以消除"未响应"):编辑可见性守卫在事务开启期间让读者**取共享锁等待**(EditReadGuard 见到 _edit_epoch 为奇数即 lock_shared),而撤销与反相各自持有**单一**事务跑完整趟,渲染一旦在 paintEvent 里读到 get_display_edges 就会阻塞到整趟结束——GUI 线程停在 paintEvent 里,窗口管理器照样报"未响应";改为渲染侧先做一次原子判断(新增 LogicSnapshot::edit_in_progress(),读 _edit_epoch 的奇偶,一个 relaxed/acquire 原子载入、永不阻塞),事务开启期间**跳过本帧的信号 pixmap 重建**、保留脏位并继续 blit 上一份完整自洽的修订,事务发布或结束后第一帧即以新修订重建(滤波按批发布,故渐进刷新不受影响;仅"必须新建 pixmap"的首帧/尺寸或 DPR 变化例外——那时没有缓存可 blit)。顺带为撤销/反相加了进度通报(revert_all_edits 新增可选进度回调),其可见性前提是"撤销/反相要按块分块发布编辑事务"——该分块在下方的【重构】条目中完成(render_pass.cpp、logicsnapshot.h、logicsnapshot_glitch_filter.cpp、logicsnapshot_glitch_filter.h、logicsnapshot.cpp) *修复一次内存失败永久污染后续所有滤波/撤销(_memory_failed 粘滞标志,缺口审计①):该标志原先只在重新采集/清缓冲(init_all/free_data)时清零,而采集期块分配失败、mmap 配置降级、copy_from 中止、上一趟滤波/撤销的重新实体化失败等 7 处都会置位;Core 的每个编辑趟结束时都检查该标志(rebuild_filtered_state / signal_invert_task / clear_signal_invert_task 的 memory_failed 判定与 apply_glitch_filter_all 的首通道后返回),于是出过一次瞬时 OOM 之后,后续所有滤波趟都会被判失败并整趟回滚、撤销重滤同样回滚——表现为"出过一次内存问题后滤波/撤销再也不生效",只有重新采集能恢复,比单次 OOM 本身严重得多。改为把复位放到 revert_all_edits() 入口(Core 每个编辑趟的第一步),复位点即"每趟编辑作用域"的起点:本趟内(撤销/反相/滤波)新产生的失败仍存活到趟末检查点被如实上报,而上一趟遗留的失败不再判死当前趟(logicsnapshot_glitch_filter.cpp、logicsnapshot_glitch_filter.h、logicsnapshot.h、logicsnapshot.cpp) *修复撤销失败被静默吞掉(缺口审计②,用户看到"已清除"而波形仍是滤波结果):revert_all_edits() 原返回 void,撤销内部块重实体化失败(LeafBlockPool::acquire 返回 nullptr——密集毛刺把整块滤成常量、被 RLE 折叠释放后正是该路径)只置 _memory_failed 便返回;clear_glitch_filter_task 不检查任何状态就清 _glitch_filter_active 并广播 GlitchFilterCleared"已清除",clear_signal_invert_task 同样照清照广播。改为 revert_all_edits() 返回 bool(任一条记录未能还原即 false),各撤销路径显式处理:清除路径失败时保持 _glitch_filter_active 不变(数据仍处于滤波态,UI 状态与数据一致,可重试——重试的撤销会再次复位 OOM 作用域)、不广播"已清除"并以 set_error/session_error 给出用户可见报错;滤波趟 rebuild_filtered_state 初始撤销失败即中止(不得在混合数据上叠加反相/滤波),趟末终检对 memory_failed 与 edit_log_overflowed 一视同仁上报(原先只有后者对用户可见);信号反相趟与反相清除同样补齐(回滚撤销本身再失败时留 pxv_err 记录,下一趟从干净作用域重来)(logicsnapshot_glitch_filter.cpp、logicsnapshot_glitch_filter.h、logicsnapshot.cpp、logicsnapshot.h、filterprocessor.cpp) *修复滤波/反相进行中点"清除"被静默丢弃且无任何反馈(缺口审计③):submit_clear_glitch_filter 原先见到 _glitch_filter_running 直接返回无效 future(不排队、不报错),而此刻 _glitch_filter_active 仍为 false、View 的 was_active 判据把 toast 一并抑制——用户点了"清除"连"被忽略"都看不见,只能重试;MCP 同步版 clear_glitch_filter 更是在无效 future 上直接返回,违背"返回即已恢复采集原始数据"的 API 契约(客户端随后读到的是滤波数据)。改为运行中提交的清除**排队**执行:清除任务照常提交线程池、在 _edit_mutex 上等待当前趟结束后执行(future 完成即数据真正还原,同步 MCP 契约恢复;代价是一个 worker 暂时驻留等待,属既有 2 worker 容量的已知取舍);"最新指令优先"语义以意图序号保持——新增 _edit_intent_seq,set_glitch_filter / set_signal_invert / submit_clear_* 每次受理提交各递增一次,清除任务持锁后核对序号、发现被更新指令覆盖则跳过(既保住原先"运行中的清除会被后续 apply 覆盖"的行为,又不再把清除本身弄丢:双击清除=旧任务跳过新任务执行,先清除后应用=应用生效,先应用后清除=清除生效);request_clear_glitch_filter / request_clear_signal_invert 返回"已排队"布尔,View 据此显示"滤波处理中,完成后将自动清除"而非误导性的"已清除"(filterprocessor.cpp、filterprocessor.h、sigsession.cpp、sigsession.h、view_glitch_filter.cpp) *修复 invert_channel 双重反相陷阱(缺口审计④,API 级):登记表 _inverted_orders 原先只在"表中没有该通道"时登记、从不因"二次反相还原"而移除——若调用方对同一通道连续反相两次(中间不 revert),字节已回到采集原始态而登记仍在,之后任何一次 revert_all_edits() 会把原始数据**再反相一次**(撤销本身变成了编辑)。Core 四条路径每次都先撤销再反相所以用户走不到,但数据层 API 上是真实陷阱。改为登记与数据同进退(toggle):登记表恒等于"当前字节确实处于反相态的通道集合",任何调用模式下撤销都只会还原;撤销 step-3 的注释同步更正(表清空后解除反相会经 toggle 再登记,由表尾显式清空兜回)(logicsnapshot_glitch_filter.cpp) *修复编辑趟复位解除采集层内存守卫的武装(缺口审计第二轮①④,一旗两用):上一轮把 _memory_failed 的清零放进 revert_all_edits() 入口,但该标志并非"编辑趟失败"专用——它同时是采集管线的降级/停采信号:DataFeedParser 在收包入口按它丢弃数据包(feed_in_logic 首查)、append_payload 后据此 set_error(Malloc_err)+session_error() 停采;于是采集中发生一次内存失败(mmap 降级/块分配失败/copy_from 中止)→ 采集管线已按"快照不可用"处理,此后任何滤波/反相/清除趟(采集运行中允许,走整趟 _mutex)的入口都会把标志清成 false → 守卫被解除武装,后续包继续追加进已降级的快照。根因是同一标志表达两种语义:拆分为 LogicSnapshot::edit_pass_failed()(编辑趟专用:撤销重实体化失败/滤波实体化失败置位,revert_all_edits() 与 apply_glitch_filter_all() 两个顶层编辑入口复位——Core 侧初始撤销失败已改为立即中止,故 apply_all 入口复位不会掩蔽同一趟的撤销失败;这也顺带消除了"直接调 apply 不经 revert 仍是粘滞语义"的 API 不对称,适用范围已在声明处注明),_memory_failed 回归"采集层数据降级"原义、编辑路径既不置位也不清除;FilterProcessor 三处趟末终检(rebuild_filtered_state / signal_invert_task / clear_signal_invert_locked)改读新标志;init_all/copy_from 同步新语义(copy 不继承编辑趟状态)(logicsnapshot.h、logicsnapshot.cpp、logicsnapshot_glitch_filter.h、logicsnapshot_glitch_filter.cpp、filterprocessor.cpp) *修复"清除信号取反时重滤失败"留下状态与数据背离(缺口审计第二轮②):clear_signal_invert 路径 un-invert 成功 → 重新应用毛刺滤波失败(OOM)→ 回滚撤销(数据回到采集原始态,即无滤波)→ 但 _glitch_filter_active 从未被清,UI 图标/弹窗初态声称"已滤波"而样本无滤波;signal_invert_task 失败路径同病(回滚后不清 _signal_invert_active,若取反先前已激活则同样背离)。改为回滚撤销**成功**才对账"已应用"状态:clear_signal_invert_locked 清 _glitch_filter_active,signal_invert_task 同时清 _signal_invert_active/_channels 与 _glitch_filter_active(数据已回原始态=两者都不再生效);**只清 applied 标志、保留 _glitch_filter_thresholds/_modes 作为用户配置**——与 SigSession::clear_glitch_filter_state_for_capture、restore_glitch_filter_config 的既有约定一致(配置保留供 auto-apply,只有 _active 描述是否已作用于当前数据),否则一次瞬时 OOM 会把滤波面板滑杆重置;回滚自身再失败(二次 OOM)则**有意保留** applied 标志原样(半编辑快照下"仍声称已应用"才是如实读数,且用户可见 Malloc_err 已弹出),该分支语义已写进注释(filterprocessor.cpp) *修复"待执行的清除滤波"被无关取反静默丢弃(架构复核 C1,意图序号按特性拆分):清除排队功能上线后所有编辑意图共用一把 _edit_intent_seq,于是紧跟"排队清除"提交的**信号取反**会把序号顶掉 ⇒ 排队的清除在拿到 _edit_mutex 后被判过期跳过,而取反任务发现 _glitch_filter_active 仍为真、会按 flag **重新应用**滤波——用户已被 toast 告知"滤波处理中,完成后将自动清除",实际什么都没清且最终仍是滤波态。根因是把"全局串行写入"与"按特性的意图排序"混为一谈:两个特性正交(每趟都从 flag 重新推导"采集→取反→滤波"),一个特性的新意图不应作废另一个特性的待执行清除。改为每特性一把计数器(_glitch_intent_seq:set_glitch_filter / submit_clear_glitch_filter;_invert_intent_seq:set_signal_invert / submit_clear_signal_invert),各清除任务只核对自己那把——既保住 ③b 的"被更新的 apply 取代",又不再跨特性误杀(filterprocessor.h、filterprocessor.cpp) *修复 filter-state 字段的跨线程读写未按文档契约加锁(架构复核 C2,含真实崩溃面):sessiondata.h 声明 _filter_state_mutex 保护 _glitch_filter_active/_thresholds/_modes/_signal_invert_active/_signal_invert_channels 五个字段,但多处读写绕过它——最危险的是 SigSession::glitch_filter_thresholds() 返回 map 的**裸引用**(注释写"callers must copy",而唯一消费者 glitchfilterpopup 直接 find/遍历),而 worker 会在清除成功路径对该 map 执行 .clear() ⇒ std::map 并发 clear 与遍历(可崩);此外提交守卫、清除幂等检查、restore_glitch_filter_config、clear_glitch_filter_state_for_capture、SessionData::clear()、两处 auto-apply 判定(其一在数据馈送线程)均未加锁。改为:两个访问器**按值返回**(锁内拷贝,调用点显式 const auto);上述读写点全部补锁;跨线程但不属于"已应用状态"的 _glitch_filter_auto_apply 改 std::atomic(与快照层 *_failed 同一约定);并在 sessiondata.h 把契约写成显式条款(不得返回容器引用、非该组的跨线程字段用 atomic)。注意 auto-apply 与 submit 路径都是**先锁内取拷贝、出锁后再提交**——set_glitch_filter 自己会取同一把锁,持锁调用会自死锁(filterprocessor.cpp、sigsession.h、sigsession.cpp、sessiondata.h、sessiondata.cpp、datafeedparser.cpp、sessionstatecontext.cpp、glitchfilterpopup.cpp、config_io.cpp、session_service.cpp) *修复 is_glitch_filtered 标志的跨线程 data race(架构复核残留 A,数据层同类问题的最后一处):该标志由编辑 worker 在滤波趟末/撤销时写入(logicsnapshot_glitch_filter.cpp),而渲染侧经 LogicSnapshot::is_glitch_filtered() 在 GUI 线程读取(rasterize.cpp)且从不持编辑锁——普通 bool 的跨线程读写是标准意义上的 data race(字长 bool 实际不会撕裂,但为 UB 且有 TSan 可见性缺口),与本库自身约定不一致(Snapshot::_memory_failed 与上一轮修的 _glitch_filter_auto_apply 都已 atomic)。改为 std::atomic(读写点无需改动;注释写明"worker 写 / 渲染读"的角色与同类约定);分块撤销期间该标志短暂陈旧属另一条已文档化的取舍,未一并处理(logicsnapshot_glitch_filter.h、logicsnapshot_glitch_filter.cpp) *修复 DecoderStack::_is_capture_end 的跨线程 data race(跨线程标志扫描的首个产出):该标志由主/设备线程经 set_capture_end_flag() 写入(session_service.cpp、decodetaskmanager.cpp、capturemanager.cpp),而解码 worker 在 do_decode_work() 的 _data_cond 等待谓词内(持 _data_wait_mutex)与循环末检处(不持锁)读取——普通 bool 的"无锁写 + 加锁读"是标准意义上的 data race(UB,worker 侧没有可见性保证,实际只靠 1 秒超时兜底);同类的 _progress/_is_decoding/_no_memory/_options_changed/_sample_count 在同一个类里早已是 atomic,这一处是漏网。改为 std::atomic(声明处注释写明读写双方线程与同类约定),读写点无需改动;顺带删除全仓零调用者的 is_capture_end() 取值器(与 set_capture_end_flag 成对,但已无任何消费方)(PXView/pv/data/stack/decoderstack.h) *修复 CursorEntry 下移时在 pv/core/cursorregistry.h 留下的注释嵌套缺陷:说明性块注释尚未闭合就直接接了 CursorRegistry 的 /** 文档注释,GCC 对每个包含该头的 TU 报 "warning: '/*' within comment"(构建日志刷屏并掩盖真实警告);改为第一段用 // 行注释、第二段保持 /**(PXView/pv/core/cursorregistry.h) *修复 SessionService::invoke_or_call 的 vestigial 形参 ctx 引发的 -Wunused-parameter(每次编译都刷一条):该形参是早期 QMetaObject::invokeMethod(ctx, ...) 实现的遗留,改用 EventBus::post_async_dispatch + condition_variable 之后已无人读取,而全仓唯一调用点(MCP 配置写入后封送主线程调 apply_device_options)传的正是 nullptr;删除形参与实参(PXView/pv/api/session_service.cpp) *修复 LogicSnapshot::_is_loop 的跨线程 data race(跨线程标志扫描复核的结论,逐字段核对 14 个非 atomic 元数据字段后唯一确认的"读决定走哪条路径"类缺口):该标志由数据馈送线程在每场采集开始时经 set_loop() 写入(datafeedparser.cpp 的 last_ended() 分支),却被 10+ 处**无锁**读取,读者据此决定"走 _mutex 路径还是走 _ring_published 无锁发布路径"(get_sample/get_samples/begin_sample_iteration/边沿扫描/pattern_search/glitch filter);普通 bool 的一写多读没有任何同步,读者可能看到翻转前后的中间态而选错路径(例如对 loop 快照走无锁读)。改为 std::atomic(get/set 的 inline 语义不变),与 _memory_failed/_last_ended 同一约定(PXView/pv/data/snapshot/logicsnapshot.h) *修复毛刺滤波趟对 _ring_sample_count 的无锁读改写(同上复核的另一处):滤波趟在做坐标平移时执行 `_host->_ring_sample_count += _loop_offset`(趟末 `-=` 还原),但该平移对"已结束的有限采集"这条**不持整趟锁**的路径(need_whole_run_lock = _is_loop || !last_ended())同样无条件执行——而 Snapshot::get_ring_sample_count() 是**持 _mutex** 读同一个标量,构成"无锁 RMW + 加锁读"的 data race(值虽不变,load+store 仍可能覆盖并发写入)。改为:进场时取一次 loop 模式快照(local loop_mode),平移两处都按它条件执行——有限模式彻底不再触碰该标量,同时避免标志中途翻转导致 += / -= 不对称而永久污染计数(PXView/pv/data/snapshot/logicsnapshot_glitch_filter.cpp) *修复保存/导出路径无锁读快照元数据:get_block_num/get_block_size/get_block_buf 读 _ring_sample_count/_loop_offset/_ch_data.size(),而前两者由 feed 线程与磁盘写线程在 _mutex 内改写、_ch_data 由 allocate_block 增删(loop 模式还会轮转回收块),这三个方法却完全无锁,且调用方 StoreSession 跑在 std::async 线程上(storesession.cpp)——保存 loop 录波时会读到不一致的块数/块长。改为在 _mutex 内取值(recursive_mutex,调用方已持锁时重入安全);该族只被保存/导出调用,不在渲染热路径上(PXView/pv/data/snapshot/logicsnapshot.cpp) *修复 MCP/WS 请求在 worker 线程上与 GUI 争用 Core 状态(约 20 处未封送的审计结论中最危险的一批读取/写入):经核实 AppControl 把 McpTransport/WsTransport moveToThread 到专用 IO 线程、业务处理提交到 transport 自带 worker 池(mcp_transport.cpp "Phase 2"),而 RpcDispatcher::handle_request 不做线程切换——即 SessionService 的方法**不在主线程**,但 GUI 线程同时用同一批对象做拖拽/重建:① CursorRegistry(get_cursors 在工作线程上"取值 + 遍历" vector,GUI 拖拽光标同时 add/remove/clear;add/remove/clear_cursors 三个写口同样未封送)→ 四个方法全部改为 run_value_on_main_thread / invoke_or_call 封送主线程;② get_math_results 在工作线程上解引用 MathStack 内部 _math 向量(GUI 的 math_rebuild 会换入/销毁该栈,向量重分配会让 assign 读到已释放存储);③ get_spectrum_results 使用 get_spectrum_stacks() 返回的**容器引用**再 front(),而 GUI 的 spectrum_rebuild 会对同一 vector 做 push_back/clear(重分配 → 悬垂引用);④ get_lissajous_results 解引用 unique_ptr 持有的**裸指针**,lissajous_rebuild 会整体替换该 unique_ptr(use-after-free)——②③④ 改为整体封送 run_result_on_main_thread,引用/指针不出 lambda(PXView/pv/api/session_service.cpp;另见 AGENTS.md 新增的 MCP 线程硬约束段) *修正两处与实际不符的线程说明(误导后续维护,也是本轮 MCP 结论的入口):① session_service.cpp 的 helper 注释称"McpTransport 的 QTcpServer 在主线程,因此 MCP 请求在主线程"——与 appcontrol.cpp 的 moveToThread(IO 线程) + worker 池矛盾,原文保留并在其后补注更正(该说明与 moveToThread(IO 线程) 矛盾,已失效);② cursorregistry.h 的 Thread-safety 段称"SigSession 的 cursor override 里取了 SessionStateContext::data_mutex()"——核实 sigsession.cpp 的 get/add/remove/clear_cursors 与 set_cursor_position 全部没有加锁(该契约不存在),原文保留并在其后补注更正(该契约实际不存在;现行方式为 GUI 主线程 + MCP 侧显式封送)(PXView/pv/api/session_service.cpp、PXView/pv/core/cursorregistry.h) *修复"GUI 拖动光标不写回 Core"(死代码扫描的反向收获:看着死、实为调用点丢失):ViewCursors::sync_cursor_position_to_core() 与 View::sync_cursor_position() 的注释三处都写明"由 ruler / viewport 拖拽处理器在 TimeMarker::set_index 之后调用",但全仓**没有任何调用点**——于是"拖动 / 单击重定位光标 → Core CursorRegistry"整条链是死代码:MCP 的 get_cursors 永远读不到 GUI 改动后的位置(反向的 sync_cursors_from_core 在数据源绑定时是通的,正好印证前向那条漏接)。在 Ruler::mouseReleaseEvent 的两条路径补上调用:拖拽释放(_curs_moved && _grabbed_marker)与单击重定位(overCursor>0 分支);拖拽路径必须在 rel_grabbed_cursor() **之前**取标记指针,因为后者会把 _grabbed_marker 置空。同时把 View::sync_cursor_position 的形参由 Cursor* 改为 TimeMarker*——与底层 ViewCursors::sync_cursor_position_to_core(TimeMarker*) 一致(ruler 持有的就是 TimeMarker*,非 Cursor 的标记由底层静默跳过)(PXView/pv/view/component/ruler.cpp、PXView/pv/view/view.h、PXView/pv/view/view.cpp) 【重构】 *DeviceOptionsUpdated 命令/通知拆分(事件语义架构修正:事件只描述"已发生的事",订阅者不得借事件执行状态转换):原实现中 SigSession 订阅 DeviceOptionsUpdated 并在其 handler 内调 reload()(状态转换藏在订阅者里),使 GUI 订阅者隐式依赖"另一订阅者先跑完",只能靠 QTimer::singleShot(0) 时序补偿且时序假设脆弱(reload 若转异步即失效);拆分为命令/通知两段 —— 命令阶段:新增 SigSession::apply_device_options() 显式命令入口(内部 reload(),末尾 signals_changed() 终态广播驱动 View 重建),7 类广播点中 6 类(dock commit_channels/channel_check、samplingbar commit_settings、view_derived_traces 解码器增删/清空、protocoldock 设置变更、MCP notify)在广播前显式调用;MCP 路径经 invoke_or_call 封送主线程(reload 创建 QObject 派生 SignalModel,禁止工作线程执行);通知阶段:DeviceOptionsUpdated 改为纯通知事件(dock 重读驱动 + API 客户端推送),删除 skip_model_reload 字段(TabContext 意图应用路径本就显式 reload,改为纯通知广播)、SigSession 退订、GUI handler 移除 singleShot(0) 与冗余 rebuild_signals/signals_changed(终态链已覆盖,历史上同一状态变更触发两次 View 全量重建)(events.h、sigsession.h、sigsession.cpp、event_dispatcher.cpp、deviceoptionsdock.cpp、samplingbar.cpp、view_derived_traces.cpp、protocoldock.cpp、tabcontext.cpp、session_service.cpp、signalmodel.h);命令/通知约定固化入 AGENTS.md State Sync Conventions(Command / Notice split — HARD CONSTRAINT) *顺带修复解码器添加竞态(文档记载的历史问题):create_decode_traces 原经 broadcast_async 在下一拍才 reload,而 start_all_decode_tasks() 在当拍启动解码线程(reload 重建 SignalModels 与解码线程启动存在窗口竞态,靠 reload 内部快照指针补绑兜底);命令显式化后 apply_device_options() 同步完成于解码线程启动之前,竞态窗口消除(view_derived_traces.cpp) *通知系统审计修复(命令/通知拆分收尾): - SignalsChanged 一致性路径撕掉 50ms 节流:Core 模型列表变更的终态通知原经 50ms 定时器延迟派发,命令完成与 View 重建之间存在陈旧窗口(被禁通道对象仍存活),通知订阅者在窗口内读布局只能得到"恰好正确"的脆弱结果;改为立即执行增量重建(compute_change_event),50ms 节流定时器仅保留给 MCP 解码器连发的 ServiceEvent 路径(event_dispatcher.cpp) - EndDeviceOptions 事件退役:其唯一消费者(GUI dispatcher)在订阅者内执行 demo pattern 状态转移(clear_view_data/init_signals/start_capture),违反命令/通知约定;逻辑迁入 MainWindow::apply_end_device_options(),由 DeviceOptionsDock::device_options_committed() 信号直连调用(用户交互入口调用链);外部路径(MCP 写 PATTERN_MODE)的转移仍由 DemoModeChanged 事件驱动(events.h、event_dispatcher.h、event_dispatcher.cpp、mainwindow.h、mainwindow.cpp、deviceoptionsdock.h、deviceoptionsdock.cpp、dock_manager.cpp) - StoreConfPrev 事件退役:保存前置提交(采样栏设置写入驱动,保证 sig_store_session 读到最新值)原经 async 事件派发——"前置"保证名存实亡,提交落在读取之后,保存的 .pxc 可能捕获陈旧采样设置(潜在 bug);改为 FileBar::store_conf_pending() 信号直连 MainWindow::commit_settings_before_store() 同步执行(filebar.h、filebar.cpp、mainwindow.h、mainwindow.cpp、event_dispatcher.h、event_dispatcher.cpp、events.h、events_json.h、session_service.cpp) - *Prev 事件族语义文档修正:CurrentDeviceChangePrev/StartCollectWorkPrev/EndCollectWorkPrev 实际经 broadcast_async 派发而非文档声称的 broadcast_sync(历史漂移),"前置"保证只剩队列顺序;events.h STATUS 块重写(删除"IEventListener 45 虚函数"腐化描述)、三个事件注释改为如实描述"handler 只允许 UI 预处理"、AGENTS.md 派发模式描述与 broadcast_sync 零调用点现状同步(events.h、AGENTS.md) - DeviceConfigUpdated 空端点删除:GUI dispatcher 的 handler 为空函数(仅 API 桥消费该事件),删除空订阅与声明;同步修正 eventbus.h 合并示例注释(DeviceOptionsUpdated 带 from_external 字段属非空类型不参与合并,带载荷事件合并不丢字段)(event_dispatcher.h、event_dispatcher.cpp、eventbus.h) - SamplingBar commit_settings 删除 DeviceConfigUpdated 冗余广播:其 GUI 端点为空且 API 桥与 DeviceOptionsUpdated 映射到同一 ServiceEvent::DeviceConfigChanged,双广播对 MCP/WS 客户端产生重复推送;MCP 路径实测验证(configure_channel 启停 ×2、set_sample_config 全部成功,日志证实 apply_device_options 命令 + from_external 通知精确生效)(samplingbar.cpp) *毛刺滤波/信号反相改为可逆编辑日志,彻底移除 976MB 全量备份:原实现用 _logic_backup 深拷贝整份快照做撤销(8 通道 × 1 GS/s = 976 MB),每次重新滤波(拖阈值)还要 copy_from(backup) 整趟回灌——480 次 MmapAllocator reserve/commit + 全量 unmap + 1 GB memcpy + 约 25 万次缺页,峰值内存直接 ×2;更严重的是 copy_from 内部的 free_data() → _mmap_alloc.reset() 会把已发布的叶子块整片 unmap,而有限采集下的无锁读者(get_sample / get_display_edges / get_nxt_edge / get_pre_edge / pattern_search 全部经 committed_sample_count() 无锁)正持有这些指针,等于"读的时候内存被抽走"。改为在 LogicSnapshot 内实现可逆编辑日志:每次覆写叶子块字节之前先 record_edit 保存将被覆盖的原始字节(RLE 未实例化块则记录"它原本不存在"连同 tog/first/last 元数据),revert_all_edits() 反序回放恢复(反序是关键:同一块会被多个批反复改写,正序回放会停在中间某一版),tog/first/last 与 mipmap 由还原后的数据重算而无需逐笔记录;撤销代价从"整份快照"降到 O(真正被改写的字节数),且不再触碰分配器状态——已发布块永不释放/不 unmap,无锁读路径"块不会被释放"的不变式重新成立。配套:编辑日志设预算上限(256 MB / 800 万条),超限即中止并整体回滚(不再产生"部分滤波却报告成功");SessionData::_logic_backup 及 sigsession / sessionstatecontext 的释放点一并删除(logicsnapshot_glitch_filter.h、logicsnapshot_glitch_filter.cpp、logicsnapshot.h、logicsnapshot.cpp、filterprocessor.cpp、filterprocessor.h、sessiondata.h、sessiondata.cpp、sigsession.cpp、sessionstatecontext.cpp) *滤波写锁粒度从"整趟"降到"每批",有限采集下渲染帧可插入:apply_glitch_filter 原从函数第一行持 _host->_mutex 直到末尾(含全部 apply_batch 的 mipmap 重建),而 loop/∞ 模式与"采集进行中"两种情况下渲染读路径取的是同一把锁,滤波跑多久 paintEvent 就等多久;改为有限采集(且采集已结束)时扫描阶段不加锁、只在 apply_batch 的写阶段短暂加锁,整趟持锁仅保留在确实需要的两种情况(loop/∞ 模式块会 rotate/释放、采集仍在进行时异步写入线程在锁内 append_payload)(logicsnapshot_glitch_filter.cpp) *新增"编辑可见性"一致性层(就地改写的发布协议):有限采集下渲染读路径本身无锁,而滤波/反相会就地改写已发布块的字节 + mipmap + tog/first/last,读者可能观察到"tog 已清但字节只写了一半"的中间态而画出垃圾波形;引入 std::shared_mutex 编辑可见性 + 编辑版本号(奇数=有批次在写,偶数=当前内容是一份完整自洽的版本):写者以 EditWriteGuard 按批独占并推进版本号,读者 EditReadGuard 走 seqlock 快路径(无批次在跑时只需两次 acquire 载入;绝不能改为无条件取共享锁——实测 shared_lock 在 MinGW 上 1163.9 ns/次,比快路径 18.4 ns 贵 63 倍,放在百万次调用的查询路径上就是可观测的数倍慢),读到版本变化则重做(consistent_read,有界重试后退化为取共享锁从而必然一致);另新增 EditReadPin 供多次调用的长扫描整趟钉住一个版本——get_sample/get_nxt_edge 只保证单次调用一致,而脉冲统计 / CSV 导出 / 模式搜索这类循环会跨版本拼出错误结果(每次读取都对、合起来是错的,且这三处产物是直方图与导出文本);并在 EditWriteGuard 上挂写入深度计数、record_edit / invert_channel 在 debug 下断言"必须在编辑事务内",防止未来新增就地写者静默重开撕裂窗口(logicsnapshot.h、logicsnapshot_glitch_filter.cpp、logicsnapshot_edge_scan.cpp、logicsnapshot_pattern_search.cpp、pulse_analyzer.cpp、waveform_copy_helper.cpp、searchdock.cpp) *LeafBlockPool 的 malloc 移出全局锁:acquire() 原在持进程级池 mutex 的情况下调 malloc(2.03 MB),该尺寸远超 glibc mmap 阈值 / Windows 小块堆路径、会真正进内核,于是所有解码线程与滤波 worker 都排在这把锁后等几十到几百微秒的系统调用;改为自由链表弹出在锁内(O(1))、malloc 在锁外(尺寸不匹配的断言与回退分支也一并移出)(leaf_block_pool.h) *采样编辑后的"重新解码"改为显式命令,消除通知订阅者里的状态转换(对齐 AGENTS.md 的 Command / Notice 硬约束):前一版把重启放在 GlitchFilterCompleted 等通知的订阅者里(先挂 View 的 SessionEventDispatcher,后搬到 Core 的 SigSession::on_snapshot_edited),而该约束明确禁止订阅者在事件处理器内执行 Core 状态转换;改为把解码任务生命周期(停 → 丢弃按旧样本算出的注解 → 逐栈 bump_version → 重启)单源实现为 SessionStateContext::restart_decode_tasks(),由 FilterProcessor 在广播通知之前作为命令调用(覆盖四条改写采样的路径:毛刺滤波任务、信号反相任务、clear_glitch_filter、clear_signal_invert),并删除 on_snapshot_edited 及其四个事件订阅。未把 copy_data_to_document 一并下沉:sigsession.h 里已有阶段13-B/C 的既有结论(标注"勿重复调研")——restart_decoders/copy_data_to_document 属真实编排语义、机械下沉是伪重构;故 SigSession::restart_decoders() 保留"文档重共享"那一半并复用新命令,使同一序列只存在一份实现(sessionstatecontext.h、sessionstatecontext.cpp、isession_coordination.h、sigsession.cpp、sigsession.h、filterprocessor.cpp) *补充跨线程遍历解码栈的加锁快照:restart_decode_tasks() 现在跑在 FilterProcessor 的 worker 线程上,而 decode_traces() 返回的是文档内部 vector 的引用,GUI 线程可能正在 add/remove/clear 同一份栈(原实现只在主线程调用,不存在该竞争);新增 SessionDocument::decoder_stacks_snapshot()(持 _stacks_mutex 返回拷贝,与既有 signal_models_snapshot() 同形),命令改用它取栈列表后再 bump 版本(sessiondocument.h、sessionstatecontext.cpp) *clear_glitch_filter / clear_signal_invert 不做前置暂停的取舍说明:采样只被改写一次,而 clear_all_decode_task2() 会 join 解码 worker,前置暂停 + 事后回放会把这次 join 付两遍;仅保留事后回放(与原先 View 处理器调用 restart_decoders() 的代价相同)。(这条当初基于"两处在 GUI 线程上执行",现已改由 worker 线程执行,见上方"清除/撤销"条目;取舍结论不变)(filterprocessor.cpp) *毛刺滤波撤销/信号反相改为按叶块分块发布编辑事务(兑现"读者等待不得超过一个批次"的既有契约):该契约(logicsnapshot.h)是后台就地改写期间界面仍能刷新的前提,但 revert_all_edits 与 invert_channel 各自只开一个 EditWriteGuard 覆盖整趟(大采集上秒级:重放约一份通道数据 + 每块重建 16.7M 采样的 mipmap),而 EditReadGuard 一旦见到事务开启即 lock_shared() 阻塞等待,于是渲染在 paintEvent 里读到 get_display_edges 会阻塞到整趟结束;改为 EditWriteGuard 抽出 open()/close() 并新增 publish()(关+开、epoch 递增两次;排队读者在下次 close 时获得锁 ⇒ 等待上限 = 一个叶块;前提是同一时刻只有一个写者,由 FilterProcessor::_edit_mutex 保证),revert_all_edits 把原先"先全部逆序回放字节、再逐块重建 mipmap"的两阶段合并为**按块单循环**:先把编辑日志按 (order, idx0, idx1) 分组去重(块内记录索引保持写入序),再对每个块升序执行"该块记录按新→旧回放 → recalc_mipmap → 每 2 块 publish + 进度通报"——升序是硬要求(recalc_mipmap 用前一块的末字 / last 位播种 _last_sample),块内逆序也是硬要求(同一块被多批改写,只有最新记录持有最新那次写之前的状态),两条不变式都写进注释;invert_channel 新增 on_chunk 挂点(由事务持有者提供;块计数只在真正有实体数据、需要重建 mipmap 的块上累加),外部入口 LogicSnapshot::invert_channel 自开事务并逐块 publish,撤销内部的反相解除复用撤销自己的事务;Core 侧反相进度接入既有限流通报(notify_batch_committed),通道结束再补一次收尾通报;另新增只读访问器 edit_revision()(epoch 计数)供测试断言"事务真的被关开过"(logicsnapshot.h、logicsnapshot.cpp、logicsnapshot_glitch_filter.h、logicsnapshot_glitch_filter.cpp、filterprocessor.cpp) *更正分块发布等待语义的注释措辞并补混合修订取舍说明(缺口审计⑤⑥,纯注释无行为变更):EditWriteGuard::publish() 与 logicsnapshot_glitch_filter.cpp 分块粒度注释原先写"排队读者在下次 close 时获得锁 ⇒ 等待上限 = 一个叶块",语气过强——std::shared_mutex(Windows SRWLOCK / glibc pthread_rwlock)没有公平性契约,写者 close 后立即重新 lock 可连续 barge,理论上来者可能连等数块;实测通常一块,但可证明的硬上限需要 open() 前的写者侧握手(检查等待读者数后让出),收益/复杂度比不划算,故改为"典型等待 = 一块(best-effort)"并写明无公平性保证(撤销分块发布的既有测试注释同步更正——该用例断言的是"事务真的被关闭重开"这一确定性事实,不依赖锁公平性);另在 revert_all_edits() 尾部 _published_ranges/_glitch_filtered 清空处补注释,说明"块间读者会短暂看到已还原波形叠加旧红色滤波区间/已滤波图标"属视觉瞬时的刻意取舍、勿按块发布区间表(logicsnapshot.h、logicsnapshot_glitch_filter.cpp、tests/qtest/data/test_logic_snapshot_raw.cpp) *补记同步清除的已知窄边界(缺口审计第二轮⑤,纯注释无行为变更):kClearTaskWaitSeconds 只是把病态挂起降级为告警的上限;清除改为排队后,同步版可能等"当前趟跑完 + 撤销跑完"才返回,超上限则**先返回**(MCP 工具仍报成功)而 worker 继续收尾,此刻紧接着读采样的客户端可能仍看到滤波数据。实测健康场景远在界内(一趟全量滤波的撤销量级为秒级:被触及块数 × 约 10-30 ms 的 mipmap 重建),继续抬高上限只会延长病态等待而非消除该边界,故按"注释记录 + 保持常量"处理(filterprocessor.cpp) *清除提交与会话内清理的去冗余(架构复核 B1/B3/C3 + 死代码删除):① submit_clear_glitch_filter 原有两个逐字重复的提交分支(唯一差别是"运行中需丢弃排队 apply"),且 request_clear_* 先自行读一次 running 再由 submit 内部重读一次——返回"已排队"可能因两次读之间的竞态而不准(只影响 toast 文案);改为单一提交出口,并由新增的 ClearSubmission{fut, queued} 把"是否排队"与 future 一起返回,运行标志只读一次;② 趟末失败判据 edit_pass_failed() || edit_log_overflowed() 三处逐字重复,新增 LogicSnapshot::edit_pass_aborted() 作为"本趟不可用"的唯一定义(两个子状态仍分别可查——超预算另有"禁止记录直到 revert"的持久副作用);③ 编辑趟失败作用域的**唯一**开启点收敛到 revert_all_edits(),apply_glitch_filter_all 不再复位(Core 恒以 revert 开头且在其失败时立即中止,故不存在被掩盖的失败),"复位点有两个"的不变式脆弱性消除;④ 删除零调用死代码 SigSession::clear_glitch_filter_state_for_capture(FilterProcessor 的 _coord 是 SessionStateContext,SigSession 不实现 ISessionCoordination;同逻辑实现已在 SessionStateContext 单点保留并补锁);⑤ has_filter_edits() 声明补注"Core 编辑路径已不再消费,保留作可观测性/测试查询";⑥ 删除另一个零调用死 API LogicSnapshot/LogicSnapshotGlitchFilter::set_glitch_filtered(bool)(该标志只由 apply_glitch_filter_all / revert_all_edits 内部维护,外部只读 is_glitch_filtered(),渲染侧仍在使用),并把 _edit_pass_failed 成员注释里"每个顶层编辑入口都会复位"的过期措辞改为"只由 revert_all_edits 复位",并修正两处以已删的 SigSession::clear_glitch_filter_state_for_capture 作为例证的注释(改指生效的 SessionStateContext 实现,避免注释引用死符号);⑦ 两条采集完成路径的 auto-apply 判定(SigSession 的 RevEndPacket 与 DataFeedParser 的 MSO 分支)各自复写了同一组条件与"锁内取配置拷贝、出锁再提交"的纪律,收敛为 FilterProcessor::auto_apply_saved_filter() 单点实现(该纪律很微妙:set_glitch_filter 自己会取同一把非递归锁)(filterprocessor.h、filterprocessor.cpp、logicsnapshot.h、logicsnapshot.cpp、logicsnapshot_glitch_filter.h、logicsnapshot_glitch_filter.cpp、sigsession.h、sigsession.cpp、datafeedparser.cpp) *View 撤销栈的"乐观弹出"取舍文档化(架构复核残留 C 表最后一格,纯注释无行为变更):ViewGlitchFilter::undo_filter() 在异步(worker 侧)请求尚未确认成功前就 pop_back(),若该请求因 OOM 失败,Core 会保持原状态并弹 Malloc_err,但这一步撤销条目已丢失、用户需手动重新应用上次滤波;要可靠回滚需让完成事件携带"本次请求来自哪个栈条目"的请求 id,对只在内存耗尽时才触发的路径不值得,故接受并就地写明理由(view_glitch_filter.cpp) *FilterProcessor::stop() 清理未服务的排队申请(缺口审计·卫生项,当前运行时不可达,纯防御):stop() 复位两个 running 标志并 shutdown 线程池,但不清 _has_pending_glitch 与待应用阈值/模式——池已死故无人服务它,今天无影响;但若将来出现进程内重启 FilterProcessor 的用法,残留的 _has_pending_glitch==true 会让重启后的第一趟立即服务一个陈旧请求。补一次清理,使"已停止"状态真正不含待处理工作(filterprocessor.cpp) *分层反向边清理(架构复核·低耦合化,含 6 处死包含):删除 decoderstack.cpp 的 core/langresource.h + view/signal/logicsignal.h、spectrumstack.cpp 的 view/signal/dsosignal.h + core/langresource.h、sigsession.cpp 的 ui/msgbox.h,并把 decodetaskmanager.cpp 的 session/sigsession.h 换成它真正需要的 data/document/sessiondata.h。效果:data→core 4→0、data→view 2→0、session→ui 1→0、core→session 9→8。**更正**:本轮的"死包含"是用"符号名出现次数"判定的,编译器证伪了其中 2 处——appcontrol.cpp 与 documentregistry.cpp 通过 session() 指针内联调用 SigSession 方法,文件里从不写出该类型名,名字 grep 判不出这种依赖(后者还依赖被间接引入的 interface/events.h)。两处包含已恢复并补上显式 events.h,同时把这个判定陷阱写进注释(appcontrol.cpp、documentregistry.cpp、decoderstack.cpp、spectrumstack.cpp、sigsession.cpp、decodetaskmanager.cpp) *string_ids.h 由 pv/ui/ 移到 pv/base/(架构复核·低耦合化):core/langresource.h 曾为取字符串页 ID 而包含 View 层头文件,是最后一条 "Core → View 层"的包含边;该头是纯 #define 的 ID 表,移到 base 后 core→ui 边 1→0,3 处包含(langresource.h、event_dispatcher.cpp、dock_manager.cpp)同步更新(pv/base/string_ids.h、pv/core/langresource.h、pv/mainwindow/event_dispatcher.cpp、pv/mainwindow/dock_manager.cpp) *CursorEntry 由 pv/core 下移到 pv/data(架构复核·低耦合化):它是 DataSource::get_cursors()/IMeasureSource::get_cursors() 交出的纯数据值(位置 + 可见性),却被声明在 pv/core/cursorregistry.h,迫使数据层为了写一个返回类型而包含 Core 头(datasource.h、imeasure_source.h)——pxview-data 与 pxview-core 于是互相依赖,形成静态库环。现移入新文件 pv/data/cursor_types.h(namespace pv::data),各引用点(data 侧 datasource.h/.cpp、imeasure_source.h;session 侧 sigsession.h/.cpp;api/view 的注释;tests/qtest/core/test_cursor_registry.cpp)同步改名;cursorregistry.h 保留指向新址的说明。data→core 边归零(pv/data/cursor_types.h、pv/core/cursorregistry.h、pv/core/cursorregistry.cpp、pv/data/datasource.h、pv/data/datasource.cpp、pv/data/imeasure_source.h、pv/session/sigsession.h、pv/session/sigsession.cpp) *滤波趟末"已应用"状态的不变式注释(更正上一轮判断):上一轮列为缺口的"空阈值也能把快照标成已滤波"**前提不成立**——set_glitch_filter() 开头已有内容守卫(遍历要求至少一个阈值 >0,否则直接返回不起趟),排队路径亦以 pend_th.empty() break;因此趟末无条件置 _glitch_filter_active=true 是诚实的。注释写明该不变式,以及"命名了不存在通道、或未发现毛刺"仍算已应用(配置生效即徽标语义)(filterprocessor.cpp) *删除 interface/events.h 指向 data 层的残留死包含,契约层成为零出度叶子(架构复核·低耦合化收尾):该 include 是为 TriggerConfigChanged 当年的 `const data::TriggerConfig*` 载荷而加,而 R3 已把该事件改成无载荷(struct 为空),include 却留了下来,成为包含图上**唯一**的 interface→data 反向边——把"最低契约层"变成必须能看到 data 头。删除后编译零错误,说明没有任何文件依赖这条间接包含(100% 死包含)。结果:interface 出度 1→0,跨子系统包含边 1279→1274;同时把本头的层策略写进注释(不包含任何 pv/ 头:升层类型一律前向声明——文件内既有做法 SessionDocument/SignalModel——或干脆不进载荷),并把注释中引用的字面 #include 去掉,避免被依赖扫描误计为一条边(PXView/pv/interface/events.h) *注释更正:上一条"分层反向边清理"的说明中,data→core 归零有 2 处来自死包含(decoderstack.cpp / spectrumstack.cpp 的 core/langresource.h,二者对该类仅在注释里提及)、另 2 处来自 CursorEntry 下移,此处补充明确(无文件变更) *记录"分层反向边"的处置约定(架构复核收尾,约定条目,无代码变更):本轮清理包含图上的反向依赖时定下的处理优先级,供后续同类问题直接对照——① **先怀疑是残留**:删掉后编译验证即可定论(编译器是唯一权威;名字 grep 判不出"通过指针内联调用、却从不写出类型名"的依赖,本轮 appcontrol.cpp / documentregistry.cpp 就是这样被证伪的);② **事件不带载荷**(notice 模式):订阅者重读 Core 单一真源,如 TriggerConfigChanged 的 R3 化(原先携带 const data::TriggerConfig*);③ **值类型下移到最低公共层**:CursorEntry → pv/data/cursor_types.h;④ **前向声明 + 指针/shared_ptr**:events.h 既有做法(SessionDocument/SignalModel);⑤ **换窄头替代宽头**:core 侧用 data/document/sessiondata.h 取代 session/sigsession.h;⑥ **依赖倒置**(回调/接口):pv/core/ui_hooks.h 模式。**不采用**"复制一份平行类型"。两条判据:其一是"包含边 ≠ 制品环"——interface 是 INTERFACE 库,interface→data 只是清洁度问题,而 data↔core 那种静态库互相依赖才必须修(本轮已消除);其二是"改名或删除后必须编译验证",不能只凭 grep 结论收尾。当前唯一剩余的"向上边"是 pv/session/tabcontext.cpp → pv/view/*(2 处),该文件属 CMake/gui_sources.cmake 的 GUI-only 目标(它驱动 view::View),不是 Core→View 违规——按目录判层会误报(PXView/pv/interface/events.h 的层策略注释、CMake/gui_sources.cmake) *删除 QML 残留(架构复核收尾):全仓已无任何 .qml 文件与 Qt6::Qml 依赖,只剩死文件 pv/qmlbridge/ui_hooks_qml.h(两个 CMake 列表都没收录它、全仓零 includer),连同空目录一并删除;CMake/render_sources.cmake 里指向不存在的 PXVIEW_QMLBRIDGE_HEADERS_MOC 的注释一并清掉,并在该文件头部写明:pxview-render 当初是为第二个 QML/Quick 外壳(PXViewQml)引入的,该外壳已不在项目中,"零 Qt Widgets"约束保留且仍强制(它保证渲染管线可脱离 Widgets 测试、可被无头路径复用),源文件里那些 "Task 3.x / QML 迁移" 注释保留为IRenderView/IRenderViewport/RenderContext 拆分的历史理由(PXView/pv/qmlbridge/ 删除、CMake/render_sources.cmake) *MeasurementValue 由 pv/api 下移到 pv/data(架构复核·最后一条同类反向边):它是 DataSource::get_measurements()/IMeasureSource::get_measurements() 的返回类型(Core 的 MeasureCalculator 生产它、api 只做序列化、View 只读),却被声明在 pv/api/types.h,迫使数据层为写一个返回类型而包含 api 层头(datasource.h / imeasure_source.h 各一处)——与上一轮 CursorEntry 完全同型。现移入新文件 pv/data/measure_types.h(namespace pv::data),13 个文件共 19 处引用同步改名(session/api/core 用 data:: 限定,data 层内不再需要 api 头);datasource.h、imeasure_source.h、measurecalculator.h 三处包含收窄为 pv/data/measure_types.h,api/types.h 反向包含它并删掉结构体定义。结果 data→api 边归零(PXView/pv/data/measure_types.h、PXView/pv/api/types.h、PXView/pv/data/datasource.h、PXView/pv/data/datasource.cpp、PXView/pv/data/imeasure_source.h、PXView/pv/core/measurecalculator.h、PXView/pv/core/measurecalculator.cpp、PXView/pv/session/sigsession.h、PXView/pv/session/sigsession.cpp、PXView/pv/api/isession_service.h、PXView/pv/api/session_service.h、PXView/pv/api/session_service.cpp、PXView/pv/api/rpc_dispatcher.h、PXView/pv/api/rpc_dispatcher.cpp、PXView/pv/view/component/dso_measure.cpp) *AGENTS.md 层映射与约定补全(架构复核收尾):目录→层映射新增 pv/mcp/ 行(MCP server/tool registry/serializers,属 pxview-core,并注明它只依赖 pv/api、无人依赖它——隔离成本最低,同时记录 pv/qmlbridge/ 已删除);pv/data/ 行补注两个共享值类型头(cursor_types.h / measure_types.h);Conventions 新增两条——①共享值类型放最低公共层(并写明 interface 出度刻意为零:事件不带载荷、升层类型只前向声明),②向上 include 必须先证明其存活(名字 grep 看不到经指针内联调用形成的依赖,删除/替换后必须编译验证)(AGENTS.md) *删除已被零拷贝取代的整条"快照深拷贝"链路(生产零调用,属死代码):SessionDocument/SessionSnapshot 改用 share_from_* 共享 shared_ptr 之后,SessionSnapshot::copy_from_logic/analog/dso 的生产调用点归零(其中 analog/dso 两个连测试都没有),而 LogicSnapshot/AnalogSnapshot/DsoSnapshot::copy_from 各自的唯一调用者就是这三个方法——于是整条链成了孤儿,包括 LogicSnapshot::copy_from 里那 150 行"拷贝即服务一次用户发起的保存/拷贝"逻辑(源快照 EditReadPin 钉版、源迭代器守卫防 use-after-free、目标迭代器排空避免 _ch_data 双份、mmap 重建 + 停 prefault 以规避"第二通道滤波后前 2ms 幻影波形")。一并删除的还有只有它调用的 LogicSnapshot::ensure_all_blocks_hot()(空转转发)及 LogicSnapshotDiskCacheWriter 里为之保留的 no-op stub。核实依据:全仓 grep "copy_from(" 的调用点只剩 sessionsnapshot.cpp 三处;并且逐个 grep 被它用到的辅助符号(wait_active_iterators_zero / has_active_iterators / stop_prefault / drain_and_join / free_envelop / EditReadPin)确认它们在别处仍有活消费方,未连带误删。测试侧:唯一消费方 SessionSnapshotCopyFromLogic 用例随之删除——它钉住的是"深拷贝语义",而该语义已被 share_from_* 取代,活的零拷贝契约由 ShareFromLogicIsZeroCopy / ShareFromAnalogAndDso / ShareDefersOldSnapshot 三条用例覆盖(sessionsnapshot.h、sessionsnapshot.cpp、logicsnapshot.h、logicsnapshot.cpp、analogsnapshot.h、analogsnapshot.cpp、dsosnapshot.h、dsosnapshot.cpp、logicsnapshot_diskcache_writer.h、logicsnapshot_diskcache_writer.cpp、tests/qtest/data/test_session_document.cpp) *同步清理因深拷贝链路删除而失效的注释引用:另有 5 处注释仍把已删除的 copy_from 写成"当前调用方 / 调用场景",读者会以为该函数还在——Snapshot::free_data 的调用者清单、LogicSnapshot::free_data 的"防 use-after-free"说明、_byte_fraction 的推导理由、mmap 状态簇的使用者清单、MmapAllocator::stop_prefault 的用途,逐条改为当前真实调用方(clear / first_payload);filterprocessor 里两处 "This replaces copy_from(_logic_backup)"是明确的历史说明(解释撤销方案为何换成可逆编辑日志),保留不动(snapshot.cpp、logicsnapshot.cpp、logicsnapshot.h、mmap_allocator.h) *删除 SigSession::restart_decoders() 死方法并同步文档漂移:该方法在 event_dispatcher 改为直调 Core 的 SessionStateContext::restart_decode_tasks() 之后已**零调用者**(全仓含字符串式 SIGNAL / invokeMethod 均无命中),方法体却仍保留着"stop+copy+restart 编排"的重复骨架与一处 copy_data_to_document 调用,与注释自称的"职责已上移"自相矛盾。删除方法本体与声明,并把类头注释里的"已核实保留"条目、"剩余批次原则"条目以及 AGENTS.md 的 Decode Task Lifecycle 段(原先把它列为四条解码启动路径之一)改为"原文保留 + 补注已删除":启动路径只有 RevEndPacket / CopyToDocDone / rst_decoder,滤波 worker 走 SessionStateContext::restart_decode_tasks()(PXView/pv/session/sigsession.h、PXView/pv/session/sigsession.cpp、AGENTS.md) *删除 DecoderStack 的 6 个零调用方法及随之"只写不读"的字段:remove_decoder_by_handel()、samples_decoded()、get_row_data()、get_max_sample_count()、analog_data_size()(逐个全仓 grep 确认零调用,非同名混淆),删掉 samples_decoded() 后 _samples_decoded 只剩写入点,连同 ctor / init() / do_decode_work() 里的三处写入一并删除——其中 do_decode_work() 那处在解码热循环里**每块取一次 _state_mutex**,顺带去掉一个逐块锁(_state_mutex 保留,它仍保护 _error_message)(PXView/pv/data/stack/decoderstack.h、PXView/pv/data/stack/decoderstack.cpp) *分层 include 图复跑(删代码后确认反向边未被新增):interface 出度 0;data→core / data→view / data→api 均为 0;session→ui、core→ui 均为 0(跨子系统边共 118 条)。顺带修正 AGENTS.md 层映射里"pv/mcp/ 无人依赖"的措辞——View 的 mcpcontroldock.cpp 确实包含 pv/mcp/mcp_instructions_gen.h(读生成的操作说明文本),改为"无 Core/session/data 依赖,唯一依赖方是该 dock"(AGENTS.md) 【构建与工程】 *MCP E2E 测试从 CTest 移除(mcp_e2e_all / mcp_e2e_p0 / mcp_e2e_p1 / mcp_e2e_p2 + pytest_venv 自定义目标整体删除):这些用例依赖运行中的 PXView MCP 端口(10110)与 pytest 虚拟环境,在常规 ctest 流程中恒为 Not Run 且被 86% 通过率噪声掩盖真实回归信号;MCP 端到端验证改用独立脚本(tests/check_mcp_gui_sync.py 交互检查表、tests/suites/ pytest 套件手动运行);CTest 现仅含 24 项纯 C++/Qt 单元测试(无 Python 依赖、无环境门槛、100% 通过),Python3 查找门控与 early-return 一并删除(tests/CMakeLists.txt) *修复 Windows Debug 构建启动即 abort(gdb 现象:unknown target exception 0xC0000409 at ucrtbase!abort,栈顶为 mimalloc mi_add_stderr_output 的 mi_assert_internal(mi_out_default == NULL)):根因是 vendored mimalloc 仅凭 $ENV{MSYSTEM}=UCRT64 识别工具链(mimalloc/CMakeLists.txt),本项目通常在普通 PowerShell/cmd 里 configure、该变量不存在,于是 MI_MINGW_UCRT64 缺失,mimalloc 在 MinGW 下同时启用了两条初始化路径——src/prim/prim.c 的 GCC 构造函数 mi_process_attach(因 MI_PRIM_HAS_PROCESS_ATTACH 未定义而编译)与 src/prim/windows/prim.c 注册的 .CRT$XLB/.CRT$XLY TLS 回调(该注册不受同一宏保护),两者都调用 _mi_auto_process_init() → _mi_options_post_init() → mi_add_stderr_output(),第二次调用触发上述断言;Release 构建 MI_DEBUG=0(NDEBUG)断言被编译掉故该重复初始化长期静默,Debug 构建 MI_DEBUG=2 则直接 fail-fast。改为在 CMake/deps.cmake 用 check_c_source_compiles 探测 _UCRT 后为 mimalloc-static 显式加 -DMI_MINGW_UCRT64=1,使 mimalloc 只保留 TLS/CRT 单一路径;重建后 prim.c.obj 不再含 .ctors,install.dir 下 PXView.exe/pxviewd.exe 启动正常(CMake/deps.cmake) *修正 ninja install 的安装前缀为空导致主程序装到盘根 C:\bin:CMakeCache 中 CMAKE_INSTALL_PREFIX 实际为空(相对路径形式的 prefix 未被解析),cmake install 的全部产物写入 <盘根>/bin——install.dir\bin\PXView.exe 长期收不到新版本(另 discovered:cmake 对已删除的安装目标仍报"Up-to-date",不校验目标存在性,删除后重装不触发、须 touch 源文件);改用**绝对路径** -DCMAKE_INSTALL_PREFIX 重新配置后,install.dir 与 build.dir 的 exe 逐字节一致(md5 核对);盘根 C:\bin 为历史多次错误安装的遗留,本次仅移除本会话写入的 PXView.exe/pxviewd.exe 两个文件(无源码变更:cmake 以绝对 -DCMAKE_INSTALL_PREFIX 重新配置) *修复 macOS CI 构建在 vendored mimalloc 上失败(报错 `static declaration of mi_theap_alloc_new follows non-static declaration`,指向 /opt/homebrew/include/mimalloc.h,自 f1d9ca3 之后的 main 分支必现):根因是 mimalloc 头文件被系统版抢先命中——CMake 生成的 -I 顺序中**目录级** include_directories 排在**目标级** target_include_directories 之前,deps.cmake 前面为 Boost/ZLIB/libusb/FFTW 注入的 /opt/homebrew/include 因此排在 mimalloc 自己的 mimalloc/include 之前,v3.5.0 子模块源码拿着 Homebrew 更新版 mimalloc.h 编译(后者把 mi_theap_alloc_new 声明为 mi_decl_export,而 v3.5.0 的 alloc.c 定义为 static);与具体提交无关,Homebrew 的 mimalloc 一升级就会在任何一次 macOS CI 上暴露(Windows 的 /ucrt64/include 同理埋雷)。改为在 add_subdirectory(mimalloc) 之前用 include_directories(BEFORE ${CMAKE_SOURCE_DIR}/mimalloc/include) 把 vendored 头目录顶到最前,使 mimalloc 自身目标以及经 MIMALLOC_LIB 的 PUBLIC 接口消费 的 PXView/pxviewd 都拿到与所链接库同版本的头(子模块未初始化时给出 WARNING 而非静默错配);同时从两个 macOS 工作流的 brew install 中移除已不再使用的 mimalloc(vendored 源码构建已取代 find_package(mimalloc))(CMake/deps.cmake、.github/workflows/build.yml、.github/workflows/macos-debug.yml) 【测试与 CI】 *新增 MCP→GUI 同步修复人工检查表脚本:逐项发送 MCP 命令(configure_trigger/set_config/configure_channel/configure_probe/set_sample_config/switch_work_mode 等)并给出 GUI 核对点,交互式按回车记录通过/失败/跳过,结束输出汇总(失败退出码 1);支持 --url/--no-send(tests/check_mcp_gui_sync.py) *新增可逆编辑日志回归测试(覆盖本次重构风险最高的部分,此前零覆盖):test_revert_all_edits_restores_capture_samples 造 1 采样宽毛刺每 20 采样一个、N=200 万(10 万个毛刺,超过 apply_batch 的 65536 刷写阈值,使同一叶子块在多个批里被反复改写——正是"反序回放"才能正确还原的关键路径),断言链路为:滤波确实改动了数据(反同义反复)→ 撤销后逐位回到采集原始态 → 撤销幂等 → 撤销后可再次滤波并再次撤销;test_revert_all_edits_invert_round_trip 覆盖反相/撤销往返与"再撤销一次不得把已还原数据又翻回去"(_inverted_orders 清空时机的回归护栏)(tests/qtest/data/test_logic_snapshot_raw.cpp) *新增编辑可见性守卫成本 A/B 测试,并把跨会话计时不可比这一教训写进用例注释:test_edit_guard_cost_ratio 在同一进程内交替测 seqlock 快路径与无条件 shared_lock 两种写法各 7 轮、各取最小值(最小值最能代表无干扰状态),比较比值而非绝对时间——起因是首版用跨会话的绝对时间对比得出了错误结论(同一台机器同一条命令在不同时段能差 1.8 倍);实测 seqlock 18.4 ns/call vs shared_lock 1163.9 ns/call(比值 61–63 倍,两次构建稳定复现),并留有 1.25 倍余量的结构性门禁(快路径不得比取共享锁更贵)。顺带记录 QtTest 的一个静默坑:槽名以 _data 结尾会被当作数据提供者(foo_data() 约定)而从不执行,测试"全绿"但断言根本没跑(tests/qtest/data/test_logic_snapshot_raw.cpp) *适配 test_session_document:删除的 _logic_backup 断言改为等价不变式"新快照不带任何待撤销的滤波/反相编辑"(has_filter_edits() / edit_log_overflowed() 均为 false)(tests/qtest/data/test_session_document.cpp) *QTest 桩适配 ISessionCoordination 新增的纯虚函数 restart_decode_tasks()(MockSessionState / StubSession 各补空实现——两个桩都没有文档/解码接线,空实现即正确语义)(tests/qtest/core/test_capture_manager.cpp、tests/qtest/core/test_decode_task_manager.cpp) *新增密集毛刺回归测试 test_dense_glitch_train_merges_edit_records(覆盖"大文件滤波无变化"的两个根因):信号取每 10 采样一个 5 采样窄脉冲(与现场日志同形),断言 ① 一趟在预算内的密集滤波不得中止(edit_log_overflowed()==false)、② 编辑日志的记录数必须远小于毛刺数(<200,而"每毛刺一条"是 ~419430;直接锁住退化机制,不依赖把规模放大到 800 万条)、③ 滤波确实改动了数据(反同义反复)、④ 撤销后逐位还原;并加块级状态断言——被 RLE 折叠释放的块在撤销后必须重新实体化且 tog 位为 1,否则读者会走常量分支(这正是"撤销无效"的可见症状);测试需包含 logicsnapshot_glitch_filter.h 才能访问 _edits(logicsnapshot.h 只做前向声明)(tests/qtest/data/test_logic_snapshot_raw.cpp) *新增渐进刷新回归测试(此前"滤波后界面不更新"这类问题零覆盖):test_batch_callback_publishes_each_committed_batch 断言"多批滤波必须每批发布一次通知、单批恰好一次、没有提交批次(threshold==0 立即返回)时零次"——数据取每 20 采样一个 1 采样窄毛刺、N=200 万,共 10 万毛刺跨过 apply_batch 的 65536 阈值,从而必然出现多批;test_revert_publishes_progress 断言"真正做了事的撤销至少发布一次进度通知、已无可撤销编辑时零次"(并注明回调在独占写事务内发出,里面读快照会在同一线程上取 EditReadGuard 而自死锁)(tests/qtest/data/test_logic_snapshot_raw.cpp) *新增撤销/反相"分块发布"回归测试(覆盖分块行为本身;此前只有单块用例,无法覆盖):旧版 test_revert_publishes_progress 用 N=200 万(单块)断言"至少一次通知",分块后单块撤销不会中途发布、该断言即失效;改为 5 块(N = 5 × LeafBlockSamples = 83,886,080,每块一个 5 采样窄脉冲、threshold=20 全部抹平 ⇒ 5 个块都被编辑日志触及 ⇒ 5/2 至少两次发布),断言 ① 撤销期间进度通报 ≥2 次、② epoch 增量 ≥4(每次 publish = 2 次递增 + 首尾各一次,直接锁住"事务被关闭并重开"而不是仅"发了通知")、③ 撤销后脉冲/块首/块尾/均布抽查点逐位还原(不做 8400 万采样全量比较,保持用例秒级)、④ 已无可撤销编辑时零通知;并补反相分块覆盖(带回调的 invert_channel 同样 ≥2 次发布、反相覆盖末块、撤销反相后还原并要求编辑/反相登记清空)(tests/qtest/data/test_logic_snapshot_raw.cpp) *补充缺口审计①②④的回归测试(三个此前零覆盖的缺口):test_invert_channel_double_call_is_idempotent 钉住"二次反相逐位还原 + 不留撤销状态 + 其后撤销为真正空操作(零通知、返回 true)"(登记 toggle 语义);test_revert_publishes_in_uninvert_branch 用"只反相、不滤波"的 5 块数据使撤销的全部发布都来自 step-3 反相解除分支,断言其进度通报 ≥2 次、epoch 增量 ≥4(该分支的发布节奏此前没有断言——若改坏嵌套 invert_channel 的 on_chunk 传递,撤销后半段会退化成整趟单事务、读者又得等整趟);test_revert_resets_stale_memory_failed 以测试内直接置位 _memory_failed 的方式覆盖三场景:A 无编辑+粘滞标志→撤销返回 true 并复位标志、B 有编辑+粘滞标志(模拟上一趟遗留瞬时 OOM)→照常撤销且逐位还原、C 标志再次置位后下一趟撤销仍从干净作用域开始(tests/qtest/data/test_logic_snapshot_raw.cpp) *新增 Core 级 FilterProcessor 离线单测(缺口审计第二轮③:清除排队/意图序号/同步等待此前仅手工 MCP 套件覆盖、CTest 零覆盖,两个 tests/qtest/core 桩的 filter_processor() 恒为 nullptr):tests/qtest/core/test_filter_processor.cpp 用与 test_capture_manager 同一离线外壳(FakeDispatcher 排队异步事件 + 测试内 drain 显式投递,不需要 Qt 事件循环;全接口 StubSession 持真实 SessionData/LogicSnapshot),四个用例——①运行中提交清除 → "已排队"返回值 → 以 GlitchFilterCleared(仅在清除成功尾部广播)作完成判据 → 状态/数据后验;②运行中先清除后应用 → _edit_intent_seq 最新写者优先:清除跳过、新阈值生效、数据保持滤波态、零 Cleared;③无活动时请求/同步清除零广播零状态翻转;④趟完成后同步清除返回即已还原。确定性设计:提交窗口只依赖"set_glitch_filter 同步置位 running 才返回",不依赖趟时长;完成检测不用非单调的 active==false;数据判别一律用采样 20(采样 0 是状态机初始基准、永不被抹平——首版用例在此踩坑,注释已写明)(tests/qtest/core/test_filter_processor.cpp 新增、tests/qtest/CMakeLists.txt,CTest 24→25 项) *适配 test_logic_snapshot_raw 的粘滞标志用例为拆分后语义:test_revert_resets_stale_memory_failed 更名 test_revert_resets_stale_edit_pass_failed,并新增断言"revert/apply_all 复位编辑标志、绝不触碰采集层 _memory_failed"(场景 A-D:无编辑+粘滞→成功复位;有编辑+粘滞→照常撤销逐位还原且采集标志原样;再次置位后下一趟撤销仍从干净状态开始;apply_glitch_filter_all 不再是作用域开启点(架构复核 B2 后契约:作用域由 revert_all_edits 单点开启))(tests/qtest/data/test_logic_snapshot_raw.cpp) *新增/强化回归测试(覆盖本轮 C1 与 B2/B3 语义,并做变异验证):① test_clear_queued_then_invert_honours_both_intents——滤波趟运行中先提交清除(排队)再提交取反,断言两个意图都兑现(GlitchFilterCleared 恰一次、取反 active、滤波不再 active),并做**数据级判别**:采样 20 是采集里的 1 采样窄高脉冲,清除生效 ⇒ 数据 = 取反(采集原始) ⇒ 该点为低;若清除被丢弃,取反趟会重新应用滤波、把取反后的 1 采样低凹抹平 ⇒ 该点为高。用例对任务执行顺序不敏感(清除先/取反先的终态相同)。**变异验证**:临时让 set_signal_invert 也顶掉滤波计数器(复现旧缺陷)后该用例立即失败,确认它真能拦住 C1;② 数据层用例场景 D 改为钉住 B2 契约(apply_glitch_filter_all 不再是作用域开启点、revert 开启新作用域后同趟健康滤波不报失败),新增场景 E 断言 edit_pass_aborted() 的聚合语义(分配失败 ∨ 超预算;超预算时 edit_pass_failed 仍为 false)——直接置位私有标志与 _edit_log_overflow,无需真实 OOM(tests/qtest/core/test_filter_processor.cpp、tests/qtest/data/test_logic_snapshot_raw.cpp;CTest 仍 25 项,FilterProcessor 7 用例全通过) *补充"滤波标志语义"断言(覆盖本轮 atomic 化的那个标志,此前只有渲染侧隐式消费):test_revert_resets_stale_edit_pass_failed 新增场景 F——应用前 !is_glitch_filtered()、一趟成功的 apply_glitch_filter_all 后为真、撤销后为假(该标志由 worker 写、GUI 读,语义错位会让渲染侧给未滤波波形画上"已滤波"提示或反之)(tests/qtest/data/test_logic_snapshot_raw.cpp;该测试 17 用例全通过,总 CTest 仍 25 项) *补充采集完成后 auto-apply 的用例(覆盖上一轮收敛出的单一实现,此前零覆盖):test_auto_apply_saved_filter_conditions 钉住四个条件——(a) auto-apply 关闭不起趟、(b) 阈值为空不起趟、(c) 刚 clear 无数据不起趟、(d) 三条件齐备起一趟并成功完成。其中 (b) 经核实具判别力:趟成功末尾是**无条件**置 _glitch_filter_active=true,若守卫漏放行,空阈值也会把快照标成"已滤波"而实际零编辑;负例用"同步提交 + 短等待"判定,用例耗时 1.09 s(tests/qtest/core/test_filter_processor.cpp;FilterProcessor 8 用例全通过,总 CTest 仍 25 项) *新增"空/全零阈值为无操作"用例并修正等待信号:test_empty_or_zero_thresholds_are_a_noop 钉住 set_glitch_filter 的内容守卫语义——空集合与全零阈值既不起趟(Started/Completed 计数不变),也不清掉已有滤波(清滤波须走专用 clear 接口)。首版用例把等待信号写成 is_glitch_filter_active(),而该标志在趟内成功尾部即置真、Completed 事件才走 EventBus 异步派发,于是读到 completed==0 误判失败;现已改为等待 Completed,并把该时序陷阱写进注释(tests/qtest/core/test_filter_processor.cpp;FilterProcessor 9 用例全通过,总 CTest 仍 25 项) *新增跨线程标志审计工具 tests/scan_cross_thread_flags.py:把"哪些非 atomic 标量成员会被 worker 线程访问"从一次性人工 grep 变成可重复扫描——枚举 19 个共享头(快照/文档/栈/模型/各 registry 与管理器/SigSession)里的非 atomic 标量成员,对每个成员列出全部引用点并归属到所在函数,再按线程角色表(main / datafeed / dx_worker / filter_worker / device_worker)标注该引用是否落在 worker 文件;--workers 只输出"至少有一处 worker 引用"的那一档(本次全量 84 个成员 → 18 个候选)。工具头部写明两条已踩过的粗筛局限:-w 按名字匹配会混淆不同类的同名成员(SessionDocument / SessionSnapshot / SearchResultModel 的 _samplerate),所属函数靠"向上找最近定义行"猜测会在大函数上猜错(曾把 sigsession.cpp:2798 报成 get_capture_owner_document,实际属 on_rev_end_packet)——因此命中候选后必须回读代码。本轮据此逐项判定的结论:① DecoderStack::_is_capture_end 是真实缺口,已改 atomic(见【问题修复】);② SessionDocument::_samplerate/_samplelimits/_trigger_pos **无需** atomic——写入方 copy_data_to_document 只由 RevEndPacket 等 broadcast_async 订阅者与 event_dispatcher 直调触发(均主线程),DocumentRegistry 的拷贝线程已随零拷贝改造删除(documentregistry.cpp 的 "join_copy_thread removed"),而跑在 worker 线程的 SessionStateContext::restart_decode_tasks() 并不触碰这三个字段;③ DecoderStack::_mark_index 仅被 protocoldock/decodetrace(GUI)与 init() 访问,全在主线程内;④ DecoderStack::_samples_decoded 由 _state_mutex 保护(写入点与 samples_decoded() 同锁);⑤ Snapshot/LogicSnapshot 的 _sample_count/_total_sample_count/_ring_sample_count/_loop_offset/_byte_fraction/_ch_fraction/_is_loop/_able_free/_lst_free_block_index/_max_blocks_per_channel/_capacity/_unit_size/_unit_bytes/_unit_pitch 属既定契约而非新缺口:loop/∞ 走 _mutex(粗粒度锁是已文档化的取舍),有限采集走 _ring_published 的 release/acquire 发布协议(数据馈送线程写完元数据后才发布,无锁读者先 acquire-load 该 atomic 再读其余字段)(tests/scan_cross_thread_flags.py 新增) *Conventions 增补"与 worker 线程共享的标志必须是 std::atomic"条款,并把上述扫描工具写成它的常规复核手段(列出已 atomic 的同类标志作参照,同时注明该工具是粗筛:按名字跨类匹配、所属函数靠猜测,命中后必须回读代码)(AGENTS.md) *死代码扫描出全量候选清单(含一次失败尝试的教训):对 PXView/pv 下 3139 个成员定义做全仓(含 tests)引用计数并剔除 576 个 virtual/override,分出 UNUSED(全仓仅声明+定义)与 TEST-ONLY(仅测试消费)两档。结论:测试专用 API 约 40 个(share_from_*、pattern_search、get_sample、edit_pass_aborted、auto_apply_saved_filter 等)钉的是活的不变式,保留;UNUSED 约 86 个。**这批 UNUSED 最终不做机械删除**:曾按该判据批量删了 86 项,结果连带走火——一行 `;` 结尾的静态成员定义(const QColor View::CursorAreaColour(220,231,243);)被脚本当成"函数体起点",一路删到下一个列 0 的 `}`,把紧邻其后的 View 构造函数(含两个合并更新定时器的创建与连接)整块删掉;更糟的是我据此一度得出"View::_delayed_view_update_timer 从未创建 → request_delayed_update() 会空指针解引用"的**错误结论**(那是删出来的假象)。已全部回滚(git checkout 87 个文件后重贴本会话的正当修改)。教训:该判据的结构性误报还包括"无缩进风格的函数体"与"旧式基类里不带 virtual 关键字的虚函数"(View::scrollContentsBy 靠另查 Qt6 头文件才拦住),所以扫描只能产出**候选清单**,逐项删除必须人工确认,且收益(可读性)远小于代价(断链风险)——清单留档,不落刀(无源码变更:扫描脚本为一次性工具,已删除) PXView V1.6.3修改 时间:2026年9月14日 变更如下: 【问题修复】 *修复等待触发窗口(采集首包到达前)多处显示异常: - 逻辑渲染模式下等待触发窗口无数据流驱动重绘,进度圈与三点动画不显示:等待触发定时器(333ms)原先仅在 DSO 模式触发 update(),而 LOGIC 等待期没有 DataUpdated 事件驱动重绘,刷新条件扩展为 DSO 或逻辑渲染模式(viewport.cpp) - 修复等待触发期间显示上一次采集的旧波形:本 ctx 为采集 owner 时,文档中残留的上一代数据即将被本帧零拷贝覆盖,属陈旧数据,显示源裁决 document_is_display_source() 须返回 false 以走 running/进度分支;repeat 模式帧间依赖显示上一帧数据(paintSignals),明确豁免(view_data_sync.cpp) - 修复首包到达前未进入 Running 态:capture_init() 在本文档为本次采集 owner 时立即置 per-tab 显示状态为 ST_RUNNING(此前停留在上次采集结束的 Stopped,而渲染先判 is_stopped_status() 再判 display_doc,会走 paintSignals 画旧波形,进度圈与 "Waiting for Trigger!" 不出现)(view_data_sync.cpp) *修复混合格式(SPLIT/CROSS)捕获开启触发时第二帧 64 对齐断言失败并弹出崩溃框:软触发的前触发数据以 LA_SPLIT_DATA(采样交织)到达,把 _ring_sample_count 推进任意值(触发落在块中时如 16 个样本),随后的 LA_CROSS_DATA 破坏对齐不变式;改为在 append_cross_payload() 中重锚到块边界(丢弃 <64 的部分前触发样本)并输出告警日志,保持后触发 CROSS 块连续(logicsnapshot.cpp) *修复遍历 _signal_models 期间修改容器导致的崩溃:通道数统计 get_ch_num()、DSO 测量电压系数同步、触发同步 sync_trigger_to_libsigrok() 三处改为遍历 signal_models_snapshot() 快照(sessionstatecontext.cpp) *修复 PXLogic 设备无法连接(报“无法打开设备。请检查USB连接、驱动程序,以及设备是否被其它程序占用。”,日志 Failed to claim interface D: LIBUSB_ERROR_NOT_FOUND.):设备活动配置仅声明 1 个 USB 接口(bNumInterfaces=1,14 个端点 0x01..0x07/0x81..0x87 全位于接口 0),不存在接口 1;驱动仍多 claim 了一个不存在的 USB_INTERFACE_D,WinUSB 后端对该调用走 GetAssociatedInterface(handle, iface-1) 分支,接口不存在时返回 ERROR_NO_MORE_ITEMS→LIBUSB_ERROR_NOT_FOUND。1.4.x 未检查 claim 返回值故该失败被忽略,新增的严格返回值检查(失败即 libusb_release_interface + libusb_close + return SR_ERR)使其升级为设备打开失败;接口 D 未被任何传输路径使用(usb_ctrl.c 仅用接口 0 上的端点),直接移除该多余 claim 及其失败回滚分支,同步清理 hw_usb_close() 的 release 调用与 USB_INTERFACE_D 宏定义(pxlogic.c、pxlogic.h) *修复 DeviceOptionsDock"触发前采样比例"修改不生效:该控件与 TriggerDock"触发位置"写的是同一个驱动键 SR_CONF_CAPTURE_RATIO,采集启动时 sync_trigger_to_libsigrok() 无条件用 Core TriggerConfig.trigger_pos() 覆盖驱动值,Dock 侧修改在下次采集开始时被冲掉;改为 DeviceOptions 绑定提交成功后同步写入 Core TriggerConfig 并广播 TriggerConfigChanged,TriggerDock 滑条自动回填,两个 UI 保持一致(deviceoptions.cpp) 【构建与工程】 *版本号提升到 1.6.3(CMakeLists.txt、README 版本徽章、NSIS 安装脚本、CI 默认版本) *修复 macOS 构建 libzip 依赖查找失败(CMake 配置报 "Please install libzip!"):zlib 为 keg-only,zlib.pc 不在 pkgconf 默认搜索路径,PKG_CONFIG_PATH 补充 $(brew --prefix zlib)/lib/pkgconfig(build.yml、macos-debug.yml) 【测试与 CI】 *macOS CI 运行器由 macos-14 迁移至 macos-15(macos-14 将于 2026-11-02 退役),同步更新工作流内注释(build.yml、macos-debug.yml) *修复 build.yml 开头双重 UTF-8 BOM 导致的工作流校验失败(报 "Required property is missing: jobs"):第二个 BOM 变成首行前不可见字符,删除重复 BOM、保留单个编码标记 *修复 macOS 构建 dmg 时 hdiutil 偶发 "Resource busy" 导致工作流偶现失败:添加 5 次线性退避重试,并在构建前清理残留挂载与旧 dmg 文件(build.yml) *增强 CI 测试稳定性与可调试性:新增 pytest-rerunfailures 实现测试失败自动重试;PXViewProcess 新增日志文件输出参数并支持用环境变量自定义守护进程日志路径,便于 CI 事后调试;重构测试夹具新增守护进程管理模块实现自动重启;延长 CI 任务超时时间并优化测试流程(release-ci.yml、conftest.py、process.py、tests/requirements.txt) PXView V1.6.2修改 时间:2026年9月12日 变更如下: 【新功能】 *新增 widget-free 渲染库 pxview-render(CMake/render_sources.cmake):标尺、光标、信号绘制等渲染代码脱离 Qt Widgets 独立编译,渲染层与 GUI 层解耦 【重构】 *渲染层与 GUI 层解耦重构(渲染资产单一编译、多前端壳复用的架构前提):新增 IRenderView/IRenderViewport 抽象接口(iview_delegates.h,覆盖几何/游标/主题色/缓存桥等视图状态),View/Viewport 实现接口,Trace/Signal 系渲染对象对 View/Viewport 的具体指针引用改为接口注入;17 个渲染翻译单元(render_pass/viewport_painter/trace 系/signal 系/cursor 系/dso_measure/dso_trigger_config)迁入 pxview-render 并经 nm 扫描验证零 Widgets 符号;Ruler 刻度绘制本体抽取为 widget-free ruler_paint(Ruler 委托调用);decodetrace 的 create_popup 弹窗构建拆至 GUI 侧独立翻译单元 decodetrace_popup.cpp,解码绘制本体保持可无 Widgets 编译;DecoderModel(纯 QtCore QAbstractTableModel)从 pv/view/trace 平移进 pv/data/model(moc 随实现编入 pxview-data);selectableitem/dockfonts 的 QApplication::palette()/font() 依赖改为 provider 注入(main.cpp 启动时安装,表达式与原实现一致)——pxview-render 仅链接 Qt6::Gui,直调 QApplication 为 incomplete type 编译错误 *Core 层 Widgets 依赖清零:appconfig.cpp 的 QApplication → QCoreApplication(organizationName/applicationName 系 QCoreApplication 静态成员,7 处 QSettings 键读取点行为等价)——此前为 Core 层唯一 Widgets 泄漏点,使 pxview-core"零 Qt Widgets"硬约束(无头产物与多前端壳共链的架构前提)名存实亡 *TabContext 无渲染层路径(view==nullptr)支持:restore_view_data/finalize_view/apply_device_intent 三处 _view 解引用点补最小空守护——无 View 的 tab(无头/远控场景)激活时跳过显示层操作,不扰动全局显示状态;Widgets 版恒传真 View,行为不变 *数据模型重构步骤7(数据代状态机澄清收敛,documentregistry/sigsession/sessiondocument): - 数据代 DataGeneration 访问器全部经 _capture_state_mutex 串行化,data_generation() 返回锁内值拷贝(写点分布 GUI 主线程与 worker 线程) - 新增数据代回边 on_capture_frame_started(Frozen→Live 唯一入口):repeat 连续帧首包(CollectStart 事件)按 owner 归属幂等回边,对齐"执行缓冲正被新一帧重写"的实时语义 - copy_data_to_document 改返回 bool 门控状态演进:RevEndPacket 仅在数据实际落地时置 Stopped/Frozen,维持 "Stopped ⇒ has_data()" 不变式;release_capture_owner 的 Live 出口按 owner doc 是否持有完整数据收敛为 Frozen/Empty *统一数据绑定裁决器 DocumentRegistry::resolve_data_binding:tab 激活绑定(restore_view_data)与渲染数据源(document_snapshot_source)共用唯一裁决表,消除双轨实现漂移;None 判定下外来 ctx 采集返回 nullptr(空白 tab 恒定空白);tab 渲染身份无条件绑定,修复首次采集进度条/ruler 整场空白 *显示/执行状态双轨分工(数据模型澄清):per-tab ViewDataSync::_display_status(显示层:本视图正在显示什么)与全局 _device_status(执行层:采集引擎是否在跑)语义分离;tab 激活绑定(restore_view_data)不再写全局 ST_*(删除原 !is_working() 门控的 set_stopped_status/set_init_status 双写——tab A 采集中切到 tab B 时 B 的显示状态迁移被跳过,是双轨未收敛的根因),改维护 per-tab 状态并由帧事件延续(frame_began→Running、receive_end→Stopped);交互判定(viewport_interaction/viewport_drag)有意保留全局执行态——"设备忙(任何 ctx 采集进行中)禁止缩放/拖拽/边沿导航"是执行层语义(阶段 9 既定决策),显示/执行分工注释固化于 pxvdef.h *SessionDocument::SessionState 状态机澄清(状态机 B):删除死状态 Copying(零拷贝删除拷贝线程后全仓无任何转移点);语义契约固化——Idle=无可显示数据(clear() 回此态)、Collecting=本 doc 是采集 owner 且采集进行中(唯一写入点 start_capture)、Stopped=最近一代数据已落地可显示;两条不变式成立并写入注释:"Stopped ⇒ has_data()"(RevEndPacket 仅在拷贝实际落地时置位,修复导入路径"有数据但 Idle"破洞的同时不引入"Stopped 但无数据"反向破洞)、"数据清空 ⇒ Idle"全路径成立(clear() 是唯一清快照方法,无私改路径);_state 原子化(std::atomic),消除 Collecting 主线程写/Stopped worker 线程写/is_collecting 主线程读的跨线程无锁读写 *渲染管线显示判定收敛:ruler_paint/render_pass/viewport_painter/decodetrace 的 stopped/init/running 判定统一改走 IRenderView per-tab 接口(接口新增 is_stopped_status/is_running_status/is_init_status 虚函数,View 转发 ViewDataSync),不再读全局 session 执行态,其他 ctx 采集时本 tab 完整测量/游标标签不再被错误抑制;SessionDocument 覆写 DataSource 的 is_stopped_status/is_running_status,doc 绑定的信号视图(dsosignal/analogsignal/mathtrace/dso_measure)获得真实显示状态而非恒 false 的 stub;is_run 类硬件/拨盘执行检查保留全局(执行语义正确) *数据源消费端空守护(15 处):document_snapshot_source() 在"外来 ctx 采集 + 本 tab 无历史数据"场景返回 nullptr 后,历史无判空直接解引用的消费点全部补齐——view_data_sync 的 pixel2index/capture_init/show_region/mode_changed/auto_set_max_scale/get_index_delta/resizeEvent/receive_end、View::set_trig_pos、ViewLayout 的 limit_scale_offset/update_scale_offset/get_scroll_layout/get_max_offset、ViewCursors 的触发/搜索游标与 set_cursor_middle/get_cm_time/get_cm_delta;判空策略为跳过或回退空值,正常单机路径行为不变(nullptr 仅在原会读到他人实时数据的污染场景出现) *借用与文档绑定收敛修复:CopyToDocDone 处理(event_dispatcher)改绑渲染文档而非所有权文档——借用态数据落在被借的文件池槽(=渲染文档),绑所有权文档会使本 tab 视图指向旧数据/空文档(双轨失同步;owner 态两者相同、行为不变);rebind_tab_to_fresh_document 绑新空文档而非 nullptr(空文档绑定对全部 document_ptr() 消费点与 nullptr 行为等价且同样清残留快照,此后 document_ptr() == render_document() 恒成立——统一裁决器的 owned_by_me/doc_collecting/gen_owner_is_render_doc 三项身份输入不再有"绑 nullptr 期间整场采集被误判外来"的失真窗口);TabContext::activate 借用回落后重试恢复本 tab 自身设备(TabSwitch 语义,避免本 tab 通道配置被 apply_device_intent 写到当前全局外来设备;仍失败才置 NULL_HANDLE 走 reload 兜底);headless 无 owner doc 采集定义为合法行为(数据仅存会话执行缓冲,doc 保持 Idle——定义行为而非遗漏);文件池槽重放走同一采集管线,数据代 Live/Frozen 语义对借用 tab 完全适用(claim_active_document 定义行为注释) 【问题修复】 *修复 tab 栏右键菜单"关闭其他/关闭右侧"无效:信号转发链在 DraggableTabWidget→TabManager 断裂(仅连接了 tabCloseRequested),补齐 tabCloseOthersRequested/tabCloseRightRequested 连接并新增批量关闭处理器(自最高索引向下关闭避免列表移位)(draggabletabwidget.cpp、tab_manager.cpp) *修复采集进行中切换 tab 触发断言失败与 SIGSEGV:SamplingBar::bind_context 恢复 per-tab 采集模式时触发 set_collect_mode 断言,断言对话框模态泵事件使排队的 DataUpdated 在 dock 重绑定间隙重入派发,MeasureDock 悬空 _view 空指针解引用;set_collect_mode 改为采集进行中拒绝+告警,MeasureDock update_dist/update_edge/update_cursor_info 加空视图守护(capturemanager.cpp、measuredock.cpp) *修复功能坞的字符串资源页面参数(dock_manager.cpp) 【清理】 *删除 DataGeneration 冗余 device_handle 字段 *移除 copy_data_to_document 在正常路径必触发的 Tripwire 断言:拷贝(零拷贝共享)发生在数据代 Live 期属设计内时序(RevEndPacket 唯一落地路径 + restart_decoders 等),"Live 期禁止拷贝"的判定前提与设计矛盾;改为在函数头固化结构约束注释(doc 快照唯一写入路径、只在 RevEndPacket 后调用——release_capture_owner 的 Live→Frozen/Empty 判定依赖此保证) PXView V1.6.1修改 时间:2026年9月7日 变更如下: 【新功能】 *新增 pxviewd 无头守护进程:headless 能力提升为一等公民产物,与 GUI 共享 pv::run_headless 运行时(不链接 Qt Widgets,控制台子系统),随包分发并在 CI 中接入(main_daemon.cpp、CMake 工程) *pxview-cli 免 pip 随包分发,新增开始菜单 CLI 入口,CI 打包接入 pxviewd/cli 产物(packaging/) *打包并加载 sigrok 固件文件(packaging 与固件加载路径) *解码器数据导出:All 多栈模式下支持同时导出全部解码器数据(storesession/导出流程) 【重构】 *Session-Centric 多 tab 架构演进(阶段1-11)与 SigSession 收缩第一批(阶段13):采集/解码/数据摄入/文档注册/测量/光标等职责拆分为独立管理器,SessionStateContext 打破 SigSession 与各 manager 的环依赖(core/、session/) *数据模型重构(核心原则:"Document 是唯一数据真相与所有权单元;tab 只是视图窗口;设备只是文档的数据来源适配器",步骤1-7): - 设备键控数据池 + 文档层引用计数化 + 设备身份单一失效点 FileDeviceClosed(documentregistry、sessiondocument) - TabContext 收缩为视图句柄:所有权不可变(文档与设备身份创建时定死),新增渲染借用语义(borrow_document/release_borrow,tab 标题"⇢ 来源"角标,切回非文件设备或属主关闭时自动解除)——"导入文件后其他标签页被静默变成文件设备"一类混乱根除 - SignalModel 列表归 SessionDocument 所有,全局访问器转发到活动/渲染文档,stash/take 搬运机制删除,"全局模型列表空窗期"消除 - 显式数据代状态机(Empty/Live/Frozen + owner_doc + device_handle):数据落文档仅由 RevEndPacket 拷贝路径负责,视图绑定裁决查表化(历史绑文档/实时绑缓冲/否则清空),新建空白标签恒定空白 - DeviceManager 执行租约仲裁(acquire/release,libsigrok 单 sr_session 独占的显式化,忙则拒绝);_saved_device_handle 单槽记忆机制退役 - 模型写入守卫:借用过渡期(渲染文档设备≠当前设备)禁止 init_signals/reload 写入,杜绝跨设备模型污染 *新增多 tab 集成契约测试(tests/qtest/core/test_multi_tab_session) 【问题修复】 *修复文件设备 "keeping" release 误调 sr_dev_close 销毁 virtual-session 会话上下文,导致 pxl 重放报 "(null)" 失败(deviceagent.cpp) *修复执行租约获取顺序(须先交还旧设备租约再获取新租约),此前任何 set_device(含打开 pxl)都被误判 "lease busy"(sigsession.cpp) *修复新建空白标签无通道且无法采集:模型原位恢复要求文档实际持有 SignalModel,空列表回退 reload 构建(tabcontext.cpp) *修复借用中关闭属主标签的堆损坏(c0000374):渲染文档改用 weak_ptr 持有,remove_tab 在列表移位前捕获当前 tab(sigsession.cpp、tab_manager.cpp) *修复设备下拉选中项时 SIGSEGV:DsComboPopup(WA_DeleteOnClose)在 setCurrentIndex 同步触发设备切换时被销毁,on_item_clicked/eventFilter/changeEvent 三条 close 路径加 QPointer 自保守卫(dscombobox.cpp) *修复文件 tab 切换到 demo 设备后整页空白且数据孤立:删除 CurrentDeviceChanged 中换绑残留块、模型写入守卫前置到任何清空动作之前、设备下拉统一路由(文件会话上选择 demo/硬件跳转对应会话 tab)(event_dispatcher.cpp、sigsession.cpp、mainwindow.cpp) *修复 Windows 包内 pxview-cli 源码路径错位(packaging) *修复 sdcc 4.6.x 在 MSYS2 下无法解析系统头文件与找不到运行时库(固件构建) *修复 ui_hooks.h 被 .gitignore 吞掉导致的 CI 编译失败,固件验证降级为警告 【清理】 *移除 Application/SlidingDrawer 事件级 profiler 临时埋点 PXView V1.6.0修改 时间:2026年9月4日 变更如下: 【新功能】 *设备意图协议与设备变更裁决重构(Phase 1-5 多阶段重构,从"启发式补丁 + 时序巧合"升级为"显式语义 + 架构保证"):事件语义化——新增 DeviceChangeReason 枚举(FirstInit/UserSelection/TabSwitch),CurrentDeviceChanged 携带 {reason, handle}、DeviceModeChanged 携带 {mode, reason},SigSession::set_device/set_default_device 增加 reason 参数(默认 UserSelection,既有调用点零改动);删除 has_signal_config() 启发式,事件调度改为显式 reason 裁决,正确性不再依赖事件时序巧合(events.h、sigsession.cpp、event_dispatcher.cpp、mainwindow.h) *配置单一真相来源:标签页设备/通道状态通过 TabContext::apply_device_intent()/harvest_device_state() 在各自文档中持久化;设备 profile 自动加载只在"设备身份变更"(FirstInit/UserSelection)时发生,TabSwitch 绝不加载 profile(tabcontext.cpp/.h、event_dispatcher.cpp) *设备意图协议正式化:TabContext::activate/deactivate 收敛为意图协议调用,契约写入头文件注释 *时序确定性:新增 loading_device_profile 标志 + 排队清除,抑制 profile 加载内部 CHANNEL_MODE 连锁导致的双重 profile 加载(即历史日志中"一次设备选择双份刷新"的根因)(mainwindow.h、event_dispatcher.cpp) *清理:删除 on_tab_changed 的 restore_previous_device() 兜底(所有建签路径均已记录 per-tab handle),并更新三处过时注释 *事件瀑布收敛:为 DeviceOptionsUpdated 事件增加 skip_model_reload 标志,仅由 TabContext::apply_device_intent() 在已显式重建后置为 true,SigSession 据此跳过二次全量 reload;其余广播点行为不变,GUI 的通道名/布局刷新照常执行(sigsession.cpp、tabcontext.cpp) 【问题修复】 *修复多标签页下设备/通道状态错乱:文件设备不再随全局设备切换被释放,改由所属标签页主动释放;每个标签页绑定自己的设备句柄,切换标签时自动恢复对应的设备与配置(deviceagent.cpp/.h、storesession.cpp、file_ops.cpp、tabcontext.cpp) *修复启动时 demo 标签页被错误绑定设备句柄的缺陷:on_load_device_first() 改为先完成默认设备初始化再记录句柄,避免把残留的 VCD 文件设备句柄记到 demo 标签,导致切回 demo 时恢复成错误的设备与通道数(mainwindow.cpp) *修复采集结束后波形轨道被整体清空的渲染缺陷:on_signals_changed() 原先误用快照源读取空的 SignalModel 列表,重建时删光已有信号却生成 0 个,导致波形轨道消失;现改为始终以 SigSession 持有的 SignalModel 为准(view_signal_sync.cpp) *通道名称、启用状态与类型统一以 SignalModel 为唯一可信源:设备选项面板与 .pxc 配置序列化均改为经 SignalModel 读写,重命名后的通道能在设备选项界面和保存的配置中正确显示自定义名称(deviceoptionsdock.cpp/.h、deviceoptions.cpp、storesession.cpp、signalmodel.cpp/.h) *修复信号过滤(毛刺滤波)的通道遍历顺序错误:glitch_filter_task 改用 SignalModel 列表(按 index 排序)遍历,确保通道索引与反相映射对应正确(filterprocessor.cpp) *修复切回已配置标签页时自定义设置被 profile 覆盖:此前每次 CurrentDeviceChanged(异步 queued)都会触发 load_device_config() 重新加载 demo0.pxc 等默认 profile,把标签页已保存的通道数/header 名覆盖回默认值("切回 demo 后通道数和 header 名被还原"的真正根因);现以 ev.reason != TabSwitch 守卫 on_current_device_changed 与 on_device_mode_changed 两处 load_device_config() 调用,TabSwitch 跳过自动 profile 加载、保留标签页自定义配置,首启/新设备首次选择因尚无 config 仍正常加载 profile(event_dispatcher.cpp) *修复 Linux 已安装 WebKit 仍误报未检测到的问题 *修复 Linux 卸载脚本(pkexec)提权失败:执行失败时回退 sudo 而非直接退出,避免从桌面快捷方式调用时无提示失败 *卸载桌面项的图标由通用的 edit-delete 改为 PXView 自带图标,确保正常显示并与主菜单风格一致 *修复 Linux 在虚拟机 / 无 GPU 环境下 WebKit 初始化白屏:GTK/WebKit 初始化前按运行环境自动选择合成模式,虚拟机或无 GPU 时退回软件合成,其余默认硬件加速(web/src-tauri/src/lib.rs) 【构建与工程】 *用自解压 .sh 安装器取代 AppImage/linuxdeploy 的分发方式:新增 packaging/bundle-runtime-libs.sh 以白名单方式拷贝 Qt 运行时与插件、GCC 运行时、Python 标准库、libsigrok/libpython 等私有依赖,刻意不打包 WebKitGTK/GTK/GStreamer/GLib/X11/Wayland 等桌面栈,避免 ABI 混搭造成窗口白屏;并重写 packaging/check-webkit-stack.sh(安装树黑名单递归扫描去重),删除 AppImage 专用的 packaging/pxview-AppRun *新增系统安装器与打包脚本:packaging/install.sh + make-installer.sh 产出自解压安装器,安装到 /opt/PXView,自动处理 udev 规则、图标、.desktop、CLI 封装、桌面快捷方式与 uninstall.sh;CMake install_packaging.cmake 现在一并发装 libsigrok 的 udev 规则,使安装器成为 USB 设备权限的唯一归属,不再单独上传 udev 压缩包;CMakeLists 为 Linux 增加 INSTALL_RPATH,安装树靠 RUNPATH 解析自带库;build_linux.sh 不再捆绑 WebKit 栈 *自解压安装器产物后缀由 .sh 改为 .run;升级改为工业级暂存切换(staged swap):旧版本树原子改名暂存→安装新树→自检→提交删除,任一步失败自动回滚到升级前状态;安装前缀之外的所有文件(udev 规则/CLI 封装/.desktop/图标/桌面快捷方式)记入 .pxview/manifest.txt 清单,卸载与升级按清单精确清理并以通配兜底;.pxview/install.json 版本标记防止误接管非 PXView 目录(PXVIEW_FORCE=yes 强制接管);检测到运行中的 PXView 默认拒绝升级以保护采集中数据(PXVIEW_KILL_RUNNING=yes 自动结束);PXVIEW_OVERWRITE=yes 纯覆盖、PXVIEW_KEEP_OLD=yes 保留旧版备份;补全卸载入口与桌面项(.desktop/快捷方式)清理 *NSIS 安装器补充安装目录保护:拒绝盘根与系统目录(C:\、Windows、Program Files 等)作为安装目标,防止卸载时 RMDir /r 误删 *Linux 自解压安装器改用标准 makeself 生成:仅嵌入 MD5 校验(--nocrc 去除 CRC,运行时单遍校验最快;自动校验,亦可 --check 手动触发)、支持 --target/--noexec/--keep 等标准运行参数、解包前预检 root 权限;产物仍为 .run 单文件,packaging/install.sh 的安装与升级逻辑不变 *修复严格 umask(如 077)环境下安装的图标/.desktop/udev 规则/资源文件为 600,普通用户无法读取导致桌面图标不显示、应用菜单异常:安装器固定 umask 022 并对安装树与系统集成文件显式归一化权限;GNOME 下 gio 的 trusted 标记补传用户会话总线地址 *更新应用图标(logo.png / logo.svg),并同步更新 README/INSTALL 文档措辞以反映新的 .sh 安装器分发方式 *版本号提升到 1.6.0(CMakeLists.txt、README 版本徽章、CI 默认版本、NSIS 安装脚本) 【测试与 CI】 *更新 GitHub Actions 依赖版本:actions/checkout v4→v7、actions/upload-artifact→v8、actions/setup-node v4→v6;为 Homebrew 配置 HOMEBREW_NO_REQUIRE_TAP_TRUST,消除 tap 信任警告 *放宽 ASan/TSan 看门狗与单测超时(方案 A):ASan 看门狗 120→600、pytest 超时 120→600、任务超时 30→60 分钟;TSan 看门狗 120→900、pytest 超时 300→900、任务超时 30→90 分钟,保留完整 capture 端到端覆盖,避免慢速 sanitizer 进程被误杀 *macOS-debug 默认不再自动构建:push 到 main 时不再在 macos-14 上完整编译并跑 lldb,以节省 macOS runner;需要抓取崩溃 backtrace 时仍可通过手动 workflow_dispatch 构建并上传产物 *修复 CI 渲染检查的依赖包安装与提示:修正 xwd 实际属于 x11-apps 而非 x11-utils,更新 apt 包列表与错误提示;新增 tests/ci/check_window_render.py 做像素级窗口渲染校验 *CI 新增 Linux 安装/卸载冒烟测试与安装器制品上传;随后又移除了 build.yml 中 PXView/PXView-Agent/窗口渲染的安装树冒烟测试步骤以简化 CI 流程 *新增单元测试覆盖 SignalModel 与 libsigrok 结构体的双向同步(tests/qtest/data/test_signal_model.cpp) PXView V1.5.9修改 时间:2026年9月3日 变更如下: 【新功能】 *新增 Linux AppImage 统一入口分发器 pxview-launcher:单个 AppImage 只能暴露一个 AppRun,新增的分发脚本按首个参数路由——无参数启动经典 PXView GUI,`agent`/`--agent` 启动 PXView-Agent(Tauri 桌面端),另支持 --print-target(供 CI 校验解析目标)与 --help;配套新增 packaging/pxview-AppRun 作为 AppImage 的 AppRun 入口,负责设置 APPDIR/PATH/LD_LIBRARY_PATH 后把参数转交分发器 *Linux 桌面文件 PXView.desktop 新增多模式启动项:TryExec/Exec 改为 pxview-launcher,并新增 Classic / Agent 两个 Desktop Action,可在应用菜单中直接选择启动经典界面或 Tauri Agent 【问题修复】 *修复导入文件(VCD/CSV/WAV/STF 等)后主界面卡在"停止"状态无法恢复:此类采集不产生任何数据包,永远走不到 SR_DF_END,而 CollectEnd 是唯一能复位 View 运行态的事件,导致 Start/Stop 按钮被永久锁死。新增 CollectEnd 投递守卫(SessionStateContext 的原子标志 _frame_end_pending,配 arm_frame_end_pending()/consume_frame_end_pending()):采集开始时置位、frame_ended() 时清除;on_session_stopped_event() 的自动停止路径发现标志仍置位,即说明本次采集未经 SR_DF_END 就结束,遂合成 CollectEnd 与 ST_STOPPED *EndCollectWork 事件补充侧边栏按钮复位:与 StartCollectWork 把 Start/Instant 按钮锁进"运行"态相对称,on_end_collect_work() 现在同步执行 setItemRunning(false),使这一闩锁不可能泄漏 *拒绝输入模块设备的无效采集请求:CaptureManager::action_start_capture() 在 device_agent().is_input_module() 时直接返回 false 并打印告警——输入模块(VCD/CSV/二进制/Saleae 导入)没有 libsigrok 驱动,sr_session_run() 立即返回且不产生数据包,此时发起采集只会清空 view_data 丢掉已导入波形并让 UI 卡死;SessionService::can_start_capture() 同步对输入模块返回 false。.pxl/.sr 会话文件走虚拟会话驱动,保留其回放/采集能力,不受影响 *修复 reload() 在设备报告无通道时误清空已导入数据:输入模块 cleanup 会转移 sdi 通道列表,导致 reload 读到空通道,原先无条件 clear_signals() 会清空整个视口并丢弃导入的波形;改为仅在确实没有任何 signal_models 时才 clear_signals(),否则保留既有模型并打印告警 *修复窗口缩放后底部状态条错位:新增 View::reposition_status_bar(),把底部状态浮层(ViewStatus)重新贴到时间视口底边(它是手工维护 geometry 的普通子控件,不随 QAbstractScrollArea 的 viewport 自动调整) *修复纯纵向缩放时表头/标尺不跟随高度变化:ViewDataSync::resizeEvent 在宽度未变(仅高度变化)的分支补充调用 _view->update_margins() 与 reposition_status_bar()。Header/Ruler/DevMode 的 geometry 由 ViewLayout::update_margins() 手工维护,少了这一步,窗口拉高后表头仍停在旧高度、底部新露出区域无任何绘制而直接透出背景色,反向缩小则表头过高、底边越界 *修正 MCP 工具数量与文档不一致:实际注册 45 个工具(Tier0 模式管理 3 / Tier1 核心工作流 17 / Tier2 配置 12 / Tier3 高级特性 13),同步更新 mcp_instructions.txt 与 mcp_tool_registry.cpp 的各 Tier 注释;新增"已移除工具及替代方案"章节,明确 get_sample_config→get_session_status(include="config")、get_decoder_class_names→get_analyzer_results(includeMetadata=true)、get_math_results/get_spectrum_results/get_lissajous_results→get_measurement_results(types=[...])、set_save_range→重命名 set_export_config(参数与能力 1:1 不变) *libsigrok 子模块:修复 sr_input_release_sdi() 丢失 sdi 通道列表的问题——vcd/csv/wav/stf/protocoldata 的 cleanup() 都会调用 keep_header_for_reread(),把 sdi->channels 与 sdi->channel_groups 转移到模块私有上下文并置 NULL。对 sr_input_free() 而言这是对的(sdi 与 input 一同销毁),但对 release_sdi() 是错的:sdi 所有权即将交给调用者,丢失通道列表会让调用者持有的 sdi 不可用且无法释放(每个通道都泄漏)。改为在 cleanup 前后保存并恢复这两个列表 【构建与工程】 *AppImage 打包纳入 PXView-Agent:linuxdeploy 增加 -e install.dir/usr/bin/PXView-Agent;install_packaging.cmake 安装 pxview-launcher 到 bin 目录,并为 Tauri Agent 的 file(INSTALL) 显式指定 OWNER/GROUP/WORLD 执行权限(原默认 0644,落地后无执行权限) *自定义 AppRun 作为 AppImage 的真实启动入口:打包前复制 packaging/pxview-AppRun 到 install.dir/AppRun 并 chmod +x,linuxdeploy 不会覆盖已存在的 AppRun,路由逻辑因此得以保留 *打包流程显式 chmod +x PXView / PXView-Agent / pxview-launcher,修复 AppImage 内二进制无执行权限 *修复 AppImage 运行时 "Failed to import encodings module":Python 标准库必须打包成与二进制实际链接的 libpython 相同的小版本。改为用 ldd 读取 PXView 真实链接的 libpython 版本(不再依赖默认 python3 或 CMake 缓存,两者都可能与 libsigrokdecode/PXView 实际链接的版本不一致),再定位对应解释器复制其 stdlib;读取失败时回退 python3,找不到解释器或 encodings 未打包则直接报错退出,避免静默产出坏包 *.gitattributes 为 packaging/pxview-AppRun 与 PXView/pxview-launcher 强制 LF 行尾(CR 会破坏 shebang 与 exec);.gitignore 放行 packaging 目录 *Tauri 端跨平台适配:find_pxview_exe 按平台选择 PXView.exe / PXView,使 Linux/macOS 打包后 Agent 能找到同目录的无头 PXView *版本号提升到 1.5.9(CMakeLists.txt、README 版本徽章、CI 默认版本、NSIS 安装脚本) *README 修正 PX-Logic 各型号流模式最大采样率,与缓存模式保持一致(PX-Logic 32 与 16 Pro 均 1 GSa/s,16 Plus 500 MSa/s,16 Base 250 MSa/s) 【测试与 CI】 *CI 新增 AppImage 启动器双模式验证:分别以 `--headless` 与 `agent`(xvfb-run + webkit2gtk/GTK 运行库)启动 AppImage,轮询 MCP 端口 10110 确认经典 GUI 与 Tauri Agent(含其拉起的无头 PXView)均能独立正常启动 *macOS CI 移除 x86_64 交叉编译:Homebrew 6.0.19+ 已将 Intel x86_64 标记为 Tier-3 并停发 x86_64 bottle,GitHub 也已停用 macos-13 Intel runner,x86_64 构建在托管 runner 上已不可实现(期间尝试过的 HOMEBREW_DEVELOPER=1 绕过 Tier-3 弃用诊断、--force-bottle 强制预编译包、改用 macos-13 原生 Intel runner 等方案均随之撤销)。build.yml 矩阵简化为仅 ARM64,删除 Rosetta 2 交叉编译步骤、CMake OSX_ARCH_FLAG 与 headless 测试的 cross_compile 分支、workflow_dispatch 的 mac_arch 输入;republish-release.yml 删除 macOS x86_64 DMG 的下载与重打包环节 *QTest 桩(test_capture_manager / test_decode_task_manager)适配新增的 arm_frame_end_pending()/consume_frame_end_pending() 接口;MCP 协议测试改为精确断言 45 个工具,并把 set_active_session 补入必需工具清单 PXView V1.5.8修改 时间:2026年8月22日 变更如下: 【新功能】 *新增 Tauri 桌面端打包支持:新增 web/src-tauri(Tauri v2)项目结构,Rust 后端提供 pxview_status/pxview_restart 无头进程管理命令,前端新增 tauri-bridge 桥接层与窗口控制按钮(最小化/最大化/关闭),关闭原生窗口装饰 *新增 TDM/PWM 模拟解码触发与音频播放功能:新增 DecoderAnalogData 模拟数据管线(TDM/PWM 解码器模拟输出样本、二分查找、整周期测量、抽取渲染、工程量映射)、解码后模拟显示触发(Repeat 模式,原子翻页显示锁定)、基于 Qt Multimedia 的解码音频播放器(DecoderAudioPlayer,多通道混音矩阵、输出设备选择)、模拟波形点测/区间测量(Shift+拖拽,含周期频率/占空比/过冲统计)、TDM/PWM 解码器专用选项对话框、模拟音频 WAV 导出(8/16/24/32bit PCM) *新增自定义 API 端口配置能力:--port(MCP,默认 10110)与 --ws-port(WebSocket,默认 10430)命令行参数,运行时动态调整 MCP 与 WebSocket 端口 *完整 MCP SDK 实现(mcp_server.h/cpp):initialize/ping/tools/list/tools/call 协议处理、参数与 schema 构建、异常驱动分发;新增 mcp_tool_registry 用 Builder/Struct API 注册 49 个整合工具,替代手工 tool_schemas.inc *MCP 工具新增注解(annotations)支持:read_only()/destructive()/idempotent()/open_world() 链式声明 *解码器自动生成标签(auto_label)与自定义标签功能:DecoderStack::auto_label() 无自定义标签时按首通道名自动生成,协议列表/导出文件/MCP API 统一显示;新增 list_row_description/Row::description 原生行描述 *无头(headless/MCP)模式解码器持久化:.pxl/.pxc 文件加载时解析 decoders 条目并重建解码栈(restore_decoders),保存时序列化解码器配置 *get_analyzer_results/get_decoder_annotations 新增按注解类过滤(ann_class 参数),避免早期大行注解饿死后续行 *导出接口尊重 set_export_config 保存区间(get_save_start/get_save_end) *后台光栅化渲染:新增 RenderWorker 后台渲染线程(三重缓冲)与 SchedulerThread 专用捕获时序线程;DSO/模拟/逻辑信号波形光栅化提取为纯函数 rasterize_logic/dso/analog_channel,交互驱动同步渲染、数据驱动异步渲染 *每个 DecoderStack 创建专属注解堆(annotation_heap + HeapAllocator),缓解多解码线程进程堆锁竞争 *libsigrokdecode 批量注解解码路径(srd_pd_output_callback_add_batch + ann_batch.c) *可选解码性能检测:perflog.h 性能日志系统(PXVIEW_DECODE_PERF 宏 + ENABLE_DECODE_PERF 编译开关,pxv_decode_perf.log 落盘) *xlog 新增 xlog_get_level API,运行时获取当前日志级别 *LogDock 新增 init_log_receiver 提前注册日志接收器,启动日志(设备扫描等)不丢失 *Snapshot 基类继承 QObject 并新增 completed() 信号,支持多段采样(get_segment_count/create_new_segment/get_segment_sample_count 等 API) *新增崩溃信号处理器 signalhandler(Win32 CaptureStackBackTrace+dbghelp / Unix backtrace) *解码器菜单按自然顺序排序(strnatcmp/strnatcasecmp) *新增内存/磁盘缓冲指示(get_logic_memory_bytes/get_logic_disk_bytes/get_mmap_total_bytes),状态栏磁盘占用显示精确化 *FFT 计算移入 worker 线程并新增 fft_updated 信号 *新增采集诊断 PathDiagWindow 与渲染诊断 RenderDiagRec(>100ms 告警) *新增 parallel.h 并行线程池 *新增 clear_cursors RPC/MCP 工具与游标操作封装(get_cursors/add_cursor/remove_cursor/clear_cursors) *pxview-automation 新增 dump-schema 命令、types.py 类型安全 API(CaptureState/ChannelType/ExportFormat 等枚举与数据类)、Windows 作业对象子进程自动清理(异常退出自动终止 PXView 子进程) 【性能优化】 *解码渲染性能系列优化:无锁渲染快照(RowDataSnapshot 不可变快照 + atomic 发布,消除渲染路径对 _rows_mutex 竞争)、增量式快照发布(分段 AnnotationSegment 复用,发布开销 O(新增))、解码前沿 LOD 色块渲染、按屏幕像素列聚合绘制(O(N)→O(屏宽))、发布节流(约 16ms/200ms 门控) *解码通知批量化:request_decode_notify + 100ms 批量定时器合并多栈 new_decode_data 通知,新增解码专用重绘路径跳过信号缓存重建 *RowData::get_visible_range 结束边界二分查找(upper_bound),新增 for_each_index 单次共享锁批量遍历 *解码时 GUI 卡顿系列优化:错误消息去重、异常注释日志频控、密集行渲染缓存(_dense_row_cache)、frozen_snapshot 大段拆分(kMaxSegAnnotations=500000) *append_cross_payload 分块转置主路径(chunk-aligned 单次顺序遍历),ingest 吞吐从约 1620MB/s 提升到接近 memcpy 带宽 *有限(非 loop)采集无锁快路径:committed_sample_count() 原子读取,get_samples/get_display_edges 等读取函数免锁分支 *FFT 计算移入 worker 线程;DSO 测量缓存(按 data 指针/channel/ring count/视图高度缓存,消除主线程重复计算) *毛刺滤波主循环改用 find_first_different_raw 原始字节扫描(u64 差分 + ctz)替代 mipmap 树搜索 *文档复制改为零拷贝(移除后台 copy_thread 与 join);SSE wait_capture 进度推送改用独立线程+原子标志 *网络传输重构:WsTransport/McpTransport 移入专用 IO 线程,JSON-RPC 业务处理卸载到 2 线程 worker pool,socket 写入统一 post_to_self 避免跨线程 *文件导入异步化:import_file 主线程不阻塞,后台线程执行读取与 sr_input_send 循环,导入中渐进刷新波形 *ProtocolDock 图层增量更新(按指针 identity 比对,O(N²)→O(N));SignalsChanged 事件 50ms 节流合并;broadcast_async 空类型事件 coalescing *DecoderStack 解码改用迭代器协议(SegmentDataIterator 增量推进,begin/continue_sample_iteration 跨 chunk 复用) *TDM/PWM 解码内存优化:DecoderAnalogData 几何扩容 + 增量 min/max 缓存;按采样量自适应分块(>1000 万样本或 >10s → 64K,否则 32K) *MmapAllocator 页面回收逻辑修正:SEC_RESERVE 下保持 written 标志复用后显式 memset,Linux madvise 返回 true 跳过 memset *DeviceAgent 常驻会话线程替代每帧 std::thread,避免 Windows 频繁线程退出造成的 GLib/CRT 状态撕裂 *移除后台渲染 Worker(RenderWorker/渲染三重缓冲),波形渲染改为 GUI 线程同步渲染,消除交互缩放/平移 1~2 帧延迟 【问题修复】 *修复采样率异常时解码崩溃:DecoderStack 捕获 srd_session_metadata_set 返回值,失败销毁 session 提前返回(修复堆损坏 0xC0000374) *修复多标签页切换后的多个核心问题:文件设备跳过 reload 避免数据丢失、切回旧 tab 恢复 ST_STOPPED 使波形重新渲染、设备切换只清活动文档解码器、导出样本对齐基准 64→8、全局设备句柄保存/恢复(save_current_device_handle)、流模式 samplelimits 用实际样本数、事件结构由裸指针改槽索引(size_t/SIZE_MAX)、EventBus 非主线程广播重定向 *修复范围保存 .pxl 文件异常:save_file 应用 SetDataRange、meta_gen 按 64 对齐计算 saved_samples/saved_trig_pos、config JSON 按字节长度读写避免 NUL 截断 *修复重复采集进度显示:repeat 模式用单帧目标采样数(get_sample_limit)计算,进度条每帧达 100% *修复解码器存储/加载:按 base/stacked 区分解码器,恢复每个解码器的 probe 映射与 options(避免栈内解码器覆盖同一字段) *修复导出块范围过滤与光标偏移:先按 start_block/end_block 过滤再计 written_bytes,位索引对齐 actual_start/actual_end 语义 *修复 stream 模式视图时间轴限制在 0-1s 与内存持续增长:改用 get_ring_sample_count 而非 get_sample_limit(demo 设备 loop 模式不再清 limit_samples) *修复捕获状态卡死导致无限等待:新增 force_release_capture_state 应急接口、EventBus 重入事件延迟入队(_deferred_broadcasts)、wait_capture_complete 超时兜底强制释放 *修复重复采集手动停止误触发 CollectEnd(按 is_repeat_mode 抑制);Repeat 重启失败导致 UI 冻结(50ms 重试 40 次后干净停止) *修复解码线程堆释放后仍被访问的崩溃:remove_decoder 先 wait_for_task_finished 等待解码线程结束 *修复 rst_decoder 竞态:wait_for_task_finished 轮询带 10s 上限,超时中止本次 reset,避免 worker 访问已释放 snapshot 导致 SIGSEGV *修复 find_first_different_raw 未实例化常量块 out_pos 错误回跳(返回真实块起点) *修复解码器类别信息解析:get_decoder_class_names 的 annotations 按 char** 配对数组解析(长描述回退短标签) *修复非 Windows 平台注解堆分配器失效:annotation_heap_alloc/free 改用 malloc/free 回退(原返回 nullptr 抛 bad_alloc 崩溃) *修复信号重建后的悬垂指针:Signal 新增 sig_released 信号、Header/Viewport 清缓存指针、MathTrace 源信号失效处理 *修复解码轨迹注释渲染丢失与密集模式判定(get_min_annotation/min_annWidth,密集模式统一矩形色块) *修复文件设备路径无法获取:DeviceAgent 新增 _file_path 保存真实磁盘路径(release/open_by_handle 不再丢失) *修复事件总线/网络传输 use-after-free:EventBus _alive_shared 共享原子标志、WsTransport QPointer 守卫、mcp_transport disconnected 立即移除 socket、SSE 客户端清理 *修复 StoreSession 忙标志竞态(调用线程设置)、快照析构泄漏(显式 free_data + drain_and_join)、MmapAllocator prefault try/catch 防崩溃 *修复探针因子 vfactor=0 导致断言崩溃(钳制为 1,按通道类型而非工作模式决定 DSO/模拟字段保存) *修复下拉框无限闪烁(_bReady 延迟就绪 + 重入守卫);Windows urllib 代理导致 127.0.0.1 连不上(空 ProxyHandler) *修复多线程并发访问:DecoderStack 错误消息/行数据加锁、StoreSession 计数器改 atomic、SignalModel 快照改 shared_ptr、EventBus 先拷贝回调列表再锁外调用 *将大量 assert 替换为带 pxv_warn/pxv_err 日志的安全处理(越界 clamp/返回默认值),避免 Release 下断言失效直接崩溃 *修复 get_capture_status state 字段统一为字符串(MCP/RPC 共享 capture_state_to_str 映射) *修复 SR_CONF_INSTANT 仅对 demo/DSLogic/DSCope 下发,避免 PXLogic 刷日志/失败 *修复 MSYS2/MinGW 编译缺 nlohmann_json 头、viewport.cpp 死代码 windows/psapi include 破坏 Linux/macOS 构建 *修复 demo 设备采集视图时间轴与内存占用、wait_capture 超时未被 timeoutSeconds 尊重 *修复 Group3 回归测试段错误崩溃(test_36 毛刺滤波 i2c):毛刺滤波后台任务按原始指针读取当前逻辑快照,与采集/配置边界 init_signals 重建信号模型并发,导致 LogicSnapshot::copy_from 读取已释放的 mmap 块而 SIGSEGV。修复:init_signals 重建前等待滤波任务空闲(FilterProcessor::wait_idle);新增 _backup_mutex 串行化滤波/反相/清除对 live↔backup 快照的 copy_from;copy_from 执行期间锁定源快照迭代器(_iterator_count),使并发 free_data 延迟释放 *修复长跑采集后内存耗尽(std::bad_alloc):采集边界排空上一采集解码线程仍持有的活动迭代器后再执行 free_data(wait_active_iterators_zero),使旧采集 mmap/leaf 块真正被回收,避免多采集累积;修复 begin_sample_iteration 首个分块读取缺少 _ring_published acquire,消除解码线程 get_samples 与采集线程数据写入的 TSan 数据竞态 *修复添加/移除解码器的内存泄漏:打破 DecoderStack 引用环(decode_task_status::_decoder 由 shared_ptr 改为 weak_ptr,回调经 lock() 取强引用),根除每轮 add/remove 约 80MB 泄漏与 MCP 超时 *修复 macOS 无头启动崩溃:禁用 mimalloc 全局 malloc override,mimalloc 改为 vendored 子模块源码构建(libsigrokdecode 注解堆继续使用 mi_heap) *修复 load_capture 设备切换并发清除解码器栈导致的堆损坏崩溃(Windows 全页堆 verifier stop/SIGSEGV):GUI 主线程与 MCP worker 并发清空销毁同一 SessionDocument::_decoder_stacks,std::vector 双线程 clear+析构数据竞争导致悬垂/双释放。修复:_decoder_stacks 增加 _stacks_mutex 并统一走加锁且幂等的 clear_decoder_stacks();set_device 销毁解码栈前先 clear_all_decode_task2() 等待解码 worker 真正结束;修复 main.cpp 消息处理器临时 QByteArray::constData() 悬垂指针 UAF *修复解码终快照尾段缺注解(SWD/I2S/MIPI 尾段无解码):libsigrokdecode 批量收集器仅内部数组达 SRD_ANN_BATCH_MAX(1024) 时自动 flush,尾部分批仅在 srd_session_destroy 时补投递,晚于 publish_snapshot(),导致低注解量解码器发布快照只含整批倍数而尾批丢失(swd_c=1024 等)。修复:srd_session_end 之后、publish_snapshot() 之前显式调用 srd_ann_batch_flush(session);同步修复 i2c_c 无条件发射 start=end=UINT64_MAX 的无效 ATK 着色注解 *修复 FilterProcessor::wait_idle 的轮询空窗:改为委托线程池 wait_for_idle 真正 join 等待 glitch/invert 任务完成(覆盖 submit→执行空窗),使 init_signals 重建前的滤波任务可靠结束 *修复卸载残留文件:卸载时 taskkill /F 强制结束 PXView/PXView-Agent 进程并 Sleep 等待句柄释放,所有 RMDir /r 增加 /REBOOTOK,解决旧主程序未退出导致的文件锁定与卸载后目录残留 *修复多线程竞争读写的潜在问题,为_work_mode变量添加atomic修饰符 【重构】 *完成 Plan B 架构重构:引入 ThreadPool 统一线程管理、SharedState 同步原语(cv.wait_for 取代 QEventLoop 轮询,消除事件队列死锁)、EventBus 改为 subscribe+RAII Subscription(移除 IEventListener 45 个 on_event 虚函数) *移除旧 EventObject 基类及其约 17 条死连接,EventBus 支持注入式 IAsyncDispatcher(QtAsyncDispatcher/SyncAsyncDispatcher) *完成 MCP 工具集整合:create_mcp_server 拆分为 4 个分层注册函数,工具合并(get_measurement_results 合并 math/spectrum/lissajous、configure_cursors 整合游标四件套、export_raw_data 合并 csv/binary、get_samples 合并 logic/analog/dso、configure_trigger/probe/glitch_filter 等),新增 outputSchema 支持,add_decoder 拆分为 prepare_decoder/apply_prepared 两阶段 *重组 PXView/pv 目录结构为层级布局(162 文件迁移到 base/session/data/view/mainwindow/platform 等子目录,统一 include 为 pv/ 绝对路径,顶层无直接 .cpp/.h) *用 std::unique_ptr 替换大量裸指针(LogicSnapshot 迭代器、StoreProgress、SubMainFrame、SessionManager、LangResource 等),移除手动 delete *依赖倒置接口解耦:IDeviceConfigPort/ISessionHost/ISignalModelSource 抽象接口,SessionDocument/DecoderStack/MathStack/SpectrumStack 不再依赖具体 SigSession *抽离纯格式化函数:measure_format(电压/时间/频率)、ruler_format、ViewContext 坐标映射;多处坐标换算统一从 document_snapshot_source 取采样率,viewport cur_preX/aftX 改 double *新增 change_event.h 纯变化判定函数(ChangeEventKind + compute_change_event_pure) *逻辑快照模块拆分:LogicSnapshotPatternSearch/LogicSnapshotEdgeScan 独立子系统;Snapshot 基类简化(移除 QObject 外的多段 API 与手动 moc,改 AUTOMOC) *提取 atomic_shared_ptr 到独立头文件(pv/utility/atomic_shared_ptr.h),解决 macOS libc++ 不支持 C++20 atomic_shared_ptr 编译 *AppConfig 全部配置键重构为 keys::* 常量并新增设置变更监听(SettingChangeListener);Binding 新增 ConfigChangedCallback 聚合回调 *MCP 系统提示词单一数据源(mcp_instructions.txt 编译期嵌入 mcp_instructions_gen.h) *通道 view_index 归一化重构为唯一赋值入口 normalize_view_indices(按类型优先级+channel index 排序) *libsigrokdecode 上游借用(upstream borrow)+ vtable 分发适配(srd_get_last_error/srd_session_inst_list_get 等新 API) *解码器选项 prop::Bool 支持眼睛按钮与 int64 存储模式;DecodeTrace 模拟波形渲染与命中测试(hit_test_analog_channel/auto_fit_visible_analog) *EventBus 重构为 Copy-on-Write 订阅表(_snapshot + rebuild_snapshot + version()) *移除无用的调试日志和冗余警告日志,降低日志噪音 【构建与工程】 *MSYS2 工具链切换到 UCRT64 替代 MinGW64(CI/构建脚本/打包脚本/deps 全量适配) *完善 mimalloc 依赖:find_package CONFIG/pkg-config/Homebrew/bare 多级查找,统一 ${MIMALLOC_LIB} 变量,替换硬编码链接,为 sigrokdecode_static/主程序/qtest 添加依赖 *Windows 构建自动查找并安装 libffi-8.dll(Python _ctypes 需要),未找到给出提示 *改进 MSYS2 UCRT64 Python 检测逻辑:探测 /ucrt64/bin、C:/msys64/ucrt64/bin、MSYSTEM_PREFIX 等多路径,优先标准 GIL Python 规避 python3.14t ABI 问题;新增 PEP 703 自由线程 Python 检测(SRD_FREE_THREADED_PYTHON=1) *libsigrok/libsigrokdecode 子模块多次升级/回退到兼容锚点(355f4d41 非 RLE 时代;a7aa591d 等) *新增 Tauri 桌面打包目标(ENABLE_TAURI + tauri-desktop,依赖 Rust cargo+npm)与 Qt Multimedia 组件 *clang-tidy 配置全面启用(clang-analyzer/cert/cppcoreguidelines/concurrency 等),新增 .clang-tidy-ci 与 NOLINT 抑制、run_tidy.ps1/clang-tidy-check.bat 脚本 *Debug 构建启用 _GLIBCXX_ASSERTIONS;mmap 分配器 ASan 中毒检测(ASAN_POISON_MEMORY_REGION);关闭 UBSan alignment 检查(minilzo 兼容) *Windows 便携包打包优化(zip -r -9 最高压缩并列出内容校验);移除 Python embeddable 下载(依赖 UCRT64 mingw-w64-python 的 stdlib+.pyd) *ENABLE_TESTS 改为 CACHE BOOL 修复 CMP0077 冲突;source 文件清单(core/gui/data)全量适配新目录布局;PCH 路径更新 *NSIS 打包脚本适配 PXView-Agent:根据是否构建 Agent 通过编译期条件动态生成桌面快捷方式,/DPXVIEW_AGENT 参数由脚本随版本同源一致传入,修复重复定义问题 【测试与 CI】 *测试系统迁移到 QTest+CTest 架构(tests/qtest 分 core/data/api/view 四层,QT_QPA_PLATFORM=offscreen 无头运行),移除旧自定义框架与 Google Test *新增 qtest 单元测试:test_decode_task_manager、test_rasterize(光栅化纯函数)、test_rowdata_batch(批量/逐注解等价性)、test_logic_snapshot_raw/query/pattern_search/edge_scan、test_analog_snapshot、test_dso_snapshot、test_signal_model、test_disk_cache、test_session_document、test_eventbus(COW/退订中广播/非主线程重定向)、test_change_event、test_binary_codec、test_result_types、test_trigger_config 等 *新增 E2E 测试套件:test_24_real_decode(PWM/GrayCode)、test_25_pwm_mixed_stress(多解码器并发压力)、test_26_i2c_real_decode、test_27_cursor_export(游标区间导出往返)、test_28_real_decode_spi_uart、test_29_work_mode、test_30_pxl_format(含范围保存回归)、test_31_decoder_persistence、test_34_demo_waveform_check、毛刺滤波定量/触发 E2E 等 *新增 release-ci.yml:4 并行 job(Release 冒烟/ASan+UBSan 核心子集/TSan 两阶段/Clang-Tidy 快速),Release 构建跑全量 E2E,替代 e2e-linux.yml *CI 测试 3 路并行:3 个 PXView 实例(MCP 10110/10111/10112、WS 10430/10431/10432)分 19 个测试文件并行执行并合并报告 *E2E 崩溃诊断演进:catchsegv→gdb→libSegFault LD_PRELOAD→curl 探测 MCP 端口判断就绪(修复 stale PID 误报) *CI 构建修复:Ubuntu24.04 C 扩展复制(_struct 定位+fallback)、UBSan alignment、绝对路径测试报告、setup-python 移到打包前、Python 3.14(deadsnakes PPA)、qtmultimedia/nlohmann-json 依赖 *新增 republish-release.yml 重发布资产工作流;test-gates.yml 分层门禁工作流(后移除);release-ci 测试分组扩容(test_29~36 共 8 个新用例) *新增三份压力测试脚本:c4_three_load_stress(稠密/稀疏×长时采集+渲染+解码并发压测) *可选 8 位 sweep 采样率测试导出左游标改为范围级语义校验(断言导出行数 ≤ 区间长度且均为合法逻辑位),去掉依赖随机位的首行精确断言,消除 flaky *修正 pxl 保存范围断言按 8 采样对齐(对齐 64→8),匹配当前正确实现 *CI:release E2E 超时 30→60 分钟;macOS 构建触发逻辑与说明更新 *macOS 调试工作流(macos-debug.yml):新增手动 lldb 崩溃回溯工作流、修复 lldb on-crash 命令导致堆栈未打印、headless 采集改用 'run --headless -l 4'、优化日志输出与 debug info 收集、更新工作流配置 *CI 作业新增主线程卡死监控与崩溃转储:Ubuntu 构建作业安装 gdb,看门狗脚本检测 PXView 主线程无响应超过 120 秒时通过 gdb 导出所有线程栈并触发 SIGABRT 保留崩溃现场 PXView V1.5.7修改 时间:2026年8月9日 变更如下: 【新功能】 *新增二进制编解码器(BinaryCodec),支持高效的波形数据传输,包含逻辑边沿编码、模拟/DSO包络编码和视口重置帧 *实现WebSocket订阅机制,支持主题订阅/取消订阅、视口订阅/更新/取消,客户端可按需接收特定类型的事件 *实现版本化状态通知,每次状态变更分配单调递增版本号和时间戳,客户端可检测过期状态 *新增视口数据定时推送(约30fps),WebSocket客户端订阅视口后自动接收增量波形数据 *新增批量RPC调用接口(batch_call),单次请求可执行多个JSON-RPC调用,减少网络往返延迟 *新增二进制视口数据获取接口(get_viewport_binary),支持以二进制帧返回逻辑边沿或模拟包络数据 *新增解码器自定义标签功能,支持为同类型解码器的多个实例设置区分标签(如"SPI(CH2.SPI)"),标签在协议列表、导出文件、MCP API中统一显示 *协议解码列表新增"All"模式,支持多解码器同时显示,按时间排序合并所有注解 *协议解码列表新增解码器切换下拉框,支持在单个解码器和所有解码器视图间快速切换 *协议解码列表新增Ctrl+F搜索快捷键,Enter键跳转下一个匹配,Shift+Enter跳转上一个 *协议解码表头新增右键菜单,支持显示/隐藏列 【问题修复】 *修复多解码器并发时的空指针竞态问题:在reload()和init_signals()中创建SignalModel后立即设置快照指针,解决添加解码器时其他解码器间歇性失败的问题 *默认启用Dock测量功能(dock_measure_fen_enabled从false改为true) *优化协议表格列宽计算逻辑,同时考虑表头文字宽度和数据内容宽度 【重构】 *重构协议解码列表UI:替换PopupLineEdit为标准QLineEdit,添加清空按钮,优化搜索栏布局 *重构搜索过滤器为AnnotationProxyModel,仅在注解数据列中进行文本匹配,排除预设的Start/Duration列 *优化表格选择模式为多选(ExtendedSelection),调整默认行高为28px适配UI设计 *DecoderModel新增多栈(All)模式架构,使用前缀和实现O(stacks)顺序行映射,可见范围使用惰性合并排序 *DecoderModel新增Start和Duration预设列,显示注解起始时间和持续时间 -------------------------------------------------------------------------------- PXView V1.5.6修改 时间:2026年8月8日 变更如下: 【新功能】 *发布pxview-automation Python自动化包,包含CLI命令行工具和三层API(低层McpClient、高层PXView、CLI),封装全部61个MCP工具,零运行时依赖 *新增协议表格右键菜单功能,支持复制单元格、复制行、复制列、复制全部和全选操作 *新增多语言翻译:复制单元格、复制行、复制列、复制全部、全选(简体中文、英文、繁体中文) *新增Clang-Tidy静态分析配置文件(.clang-tidy),针对项目崩溃模式配置检查规则 *新增ASAN/TSAN/UBSAN编译选项,支持AddressSanitizer、ThreadSanitizer、UndefinedBehaviorSanitizer运行时检查 *新增ASAN/TSAN构建脚本(build-debug-asan.sh) 【问题修复】 *修复重复采集模式下的SIGSEGV崩溃:在first_payload中停止解码线程后再修改快照,建立严格的快照生命周期协议(可变→不可变→可变) *修复重复采集模式下MmapAllocator重建导致的SIGSEGV:复用现有MmapAllocator而非创建新的,避免UnmapViewOfFile使解码线程的裸指针失效 *修复重复采集模式下leaf block decommit导致的SIGSEGV:first_payload中不再释放leaf block,保留映射供并发解码线程安全访问 *修复calc_mipmap中decommit常量值leaf block导致的SIGSEGV:仅在_able_free为true时才decommit,保护解码线程的裸指针 *修复first_payload中_dest_ptr悬垂指针导致的SIGSEGV:重置位对齐状态(_dest_ptr/_ch_fraction/_byte_fraction/_last_ended) *修复append_cross_payload中move_first_node_to_last和free_head_blocks后_dest_ptr悬垂:在所有decommit操作后重置位对齐状态 *修复append_cross_payload尾部阶段_dest_ptr为空时崩溃:新增空指针防御检查 *修复DiskWriteThread::stop()中submit()永久阻塞:在等待谓词中加入_stopping条件,并通知_cv_full唤醒阻塞的submit()调用 *修复MmapAllocator文件删除线程泄漏:将detached线程改为可join线程,在析构函数中join确保缓存文件实际删除 *修复McpTransport::stop()中SSE客户端和pending socket的use-after-free:先断开信号再abort和delete,最后销毁server *修复McpTransport::on_service_event中对已断开socket的use-after-free:单次遍历中分离存活和死掉的socket *修复WsTransport::stop()中on_client_disconnected的stale iterator问题:先断开信号再清理客户端 *修复DecodeTaskManager::stop()中的死锁:采用"lock-swap-unlock-join"模式,不在持锁状态下join工作线程 *修复DecodeTaskManager::add_decode_task()中的锁竞争:先创建线程再移入向量,避免新线程阻塞 *修复DecodeTaskManager::decode_single_task()中的自detach问题:不再detach当前线程,由stop()路径统一join *修复EventBus::broadcast_async()的alive标志被重复设true的问题:仅在构造函数设true、析构函数设false,lambda中用acquire序检查 *修复FilterProcessor::set_glitch_filter()中的自join死锁:检测递归调用时detach而非join,引入launch mutex防止TOCTOU竞态 *修复FilterProcessor::glitch_filter_task()中的递归创建线程问题:改为在循环中处理pending请求,不再递归调用set_glitch_filter() *修复FilterProcessor::set_signal_invert()中的TOCTOU竞态:引入launch mutex保护启动路径 *修复SearchDock::search_worker()中的空指针访问:新增_data空指针防御检查 *修复LogicSignal的use-after-free:新增shared_ptr引用(_data_ref)保持快照生命周期,防止主线程访问已释放的快照 *修复非流模式停止采集后画面空白问题:非流模式不再clear capture_data,保留最后一次采集数据供查看 *修复MCP重启时的资源释放竞态:graceful restart先stop清理所有客户端,再延迟500ms释放端口后start *移除demo设备循环采集限制,demo设备支持所有图案模式的循环采集 *修复采样栏重复模式对demo设备的限制:移除is_demo()判断,demo设备与硬件设备一致处理 【性能优化】 *移除release构建中的调试符号(-g)编译选项,减小二进制体积并加快编译速度 *优化WebSocket和RPC服务端的资源释放顺序,减少shutdown时的竞态窗口 【重构】 *重构Windows MSYS2/MinGW Python运行时检测和打包逻辑:强制在MSYS2环境下使用MinGW Python,避免错误链接python.org MSVC Python *改进appcontrol.cpp的Python路径设置,支持MSYS2的lib/python3.X/ stdlib布局 *重构copy-deps.sh脚本,添加参数校验、错误处理和计数输出 *重写package.sh的Python依赖处理逻辑,修复版本检测和stdlib复制 *移除MSYS2/MinGW Python的遗留处理代码,简化构建和打包流程 【构建与CI】 *修复Web UI构建时npm源和node路径问题:指定官方npm源,添加nodejs执行路径到环境变量 *CI构建Web UI从可选改为必需,构建失败则CI失败 *CI新增Python DLL和stdlib校验步骤,检查MinGW Python正确链接和encodings模块存在 *为NSIS安装脚本配置.gitattributes二进制属性,防止Git移除UTF-8 BOM *更新Web包名称为pxview-automation-web -------------------------------------------------------------------------------- PXView V1.5.5修改 时间:2026年8月7日 变更如下: 【新功能】 *新增入口文件导入功能,基于libsigrok输入模块实现文件导入,工具栏新增导入按钮与国际化文案,支持dark/light主题图标 *新增函数面板Dock(FunctionDock),支持FFT、数学运算和李萨如图形的可视化控制,从弹出菜单改为可停靠面板 *新增波形图案模式支持(正弦波、方波、锯齿波、三角波、随机) *实现李萨如波形的轴索引和百分比设置功能 *新增DSO通道配置面板,支持调整每通道参数 *添加退出时提示保存数据的配置选项 *新增统一多格式原始数据导出接口(export_raw_data),支持csv/binary/vcd/hex/bits格式导出 *新增RPC工具schema和MCP客户端接口,统一原有csv/binary导出流程 *新增配置类型审核CI脚本(audit_config_types.py),增强配置项类型校验逻辑 【问题修复】 *修复实时捕获模式下波形显示冻结的问题,优先返回实时数据源 *优化重复流捕获逻辑,保留单缓冲模式避免界面卡顿,正确维持滚动和视图更新 *重构触发配置同步逻辑:移除旧的fork版libsigrok触发API兼容代码,新增完整的高级触发配置同步(SR_CONF_TRIGGER_ADV_*系列键值对) *修复触发位置(capture_ratio)不生效的问题 *修复二进制导出时未按有效样本数裁剪导致多导出陈旧数据的问题 *使用稳定的单调计数器分配文件设备句柄,解决release后句柄失效的问题 *保存并恢复Mode区域设备级配置键,防止切换选项卡时丢失选择 *修复采样率变更导致的波形闪烁问题 *修复触发值同步更新问题 *修正水平触发位置配置的数据类型错误 *修复导入文件时的内存泄漏问题 *修复无可用通道时的空白视图问题 *修复李萨如追踪的UAF(Use-After-Free)问题,重新获取快照数据 *修复输入模块设备启动崩溃问题 *修复datafeedparser中isFile参数错误 *实现DeviceAgent get_config_int64接口 *修复回放文件时间戳显示错误 *修复循环模式下捕获停止后画面空白问题 *修复MCP传输连接挂起问题 *完善设备ID异常处理与测试用例 *修复默认设备选择逻辑,跳过无通道的输入模块设备避免UI崩溃 *优化StoreSession二进制导出逻辑,修复UTF-8转换和换行符问题 *修复逻辑通道导出时的样本计数对齐问题,确保保存加载无损耗 *修复SessionData采样率注入时机,消除波形平窗问题 *修复缩放范围限制、MSO模式波形不显示等bug *修复View析构时游标对象内存泄漏 *完善Windows安装包:添加多语言提示、进程检测、自动卸载旧版本和强制覆盖安装文件 *将SR_CONF_CLOCK_EDGE绑定为字符串下拉框以匹配驱动支持 *修复跨工作模式通道异常启用问题 【性能优化】 *优化捕获结束后的解码和数据拷贝逻辑,针对流重复捕获跳过耗时的数据拷贝,直接复用实时缓冲 *重构会话数据零拷贝共享机制 *全面替换dynamic_cast为as_*()窄cast方法,新增trace_visitor实现visitor模式消除RTTI开销 *使用std::vector替代手动new[]/delete[],将裸指针替换为std::unique_ptr管理资源 *新增预编译头文件(common_pch.h)优化编译速度 【重构】 *完成项目现代化迁移:将boost/optional替换为std::optional,旧式C头文件替换为标准版本,NULL替换为nullptr,dsvdef.h重命名为pxvdef.h并更新所有引用,volatile标志替换为std::atomic,统一C++标准为C++23 *将MainWindow代码按职责拆分为独立委托类:MainWindowConfigIO(配置序列化)、MainWindowDockManager(Dock管理)、MainWindowEventDispatcher(事件分发)、MainWindowFileOps(文件操作)、MainWindowSignalConnector(信号槽连接)、MainWindowShortcutManager(快捷键)、MainWindowStatusBar(状态栏)、MainWindowTabManager(标签页)、MainWindowThemeManager(主题管理) *提取ViewLayout、ViewCursors等委托类,将状态和行为从View剥离,新增IViewCursors、IViewSignalStore接口解耦视图渲染依赖 *新增RenderPass独立渲染Pass管线,将信号绘制、解码渲染和光标叠加逻辑从ViewportPainter迁移至独立组件 *将测量叠加层和触发状态信息提取为独立的MeasureOverlayPass和TriggerInfoPass *将dsosignal.cpp中的所有绘图方法提取到dsosignal_paint.cpp,提升模块化程度 *将CaptureManager所有跨线程访问的成员变量替换为std::atomic,修复数据竞争、空指针解引用、悬垂指针及线程不安全的容器访问 *重构属性绑定生命周期:将属性删除职责移入Binding基类析构函数,移除boost::bind改用lambda表达式,引入GVarPtr自动管理GVariant生命周期 *将DataSource胖接口拆分为IDataSource/ISignalSource/ICaptureControl/IMeasureSource/IDecoderHost五个细粒度接口,遵循ISP原则 *废弃ISessionCallback体系,改用统一的IEventListener事件总线 *新增ISessionState接口,打破Manager与SessionStateContext之间的循环依赖 *全面迁移容器为std::unique_ptr智能指针管理,移除手动delete代码 *将GlitchFilterMode改为强类型枚举,移除全局枚举依赖 *新增SignalListModel实现信号列表Qt模型封装 *新增IViewLayout接口和MockViewLayout用于单元测试 *移除RenderPipeline,改为显式调用渲染流程 *统一派生轨迹、游标、滤镜等资源的清理逻辑 *拆分LissajousModel和MathStack的实现至独立cpp文件 【测试】 *重构测试体系,迁移至pytest自动化测试框架 *实现完整的MCP E2E测试套件,包含23个测试套件覆盖协议、设备管理、采集、数据完整性、导出导入、解码器(I2C/SPI/UART/CAN/PWM)、信号数据、配置、滤波反转、会话高级、错误处理、压力测试等模块 *新增Windows批量测试脚本与调试工具 *新增pytest-timeout依赖并配置全局超时 *添加Google Test C++单元测试子项目 【构建与CI】 *补充Windows/Linux/macOS全平台本地构建脚本 *新增Windows依赖复制与打包脚本 *新增GitHub Actions构建流水线,支持view_and_data分支触发构建 *新增Windows E2E测试CI流水线 *调整C++标准配置,为CI安装GCC14以支持C++23所需特性 *添加libsigrok子模块初始化步骤 *优化版本号构建方式,支持从命令行覆盖PRODUCT_VERSION *CI升级至ubuntu-24.04,GCC14 runtime bundling,分层codesign签名 *修复AppImage TMPDIR问题,移除绝对OUTPUT路径 *修复手动构建时ref为空导致失败的问题 *将Python运行时二进制和废弃脚本移出git追踪(本地保留) *修复NSIS BOM问题,CI下载Python embeddable时删除python314.zip *更新libsigrok和libusb子模块到最新版本 *同步脚本白名单新增tools目录,优化子模块同步逻辑 -------------------------------------------------------------------------------- PXView V1.5.4修改 时间:2026年7月31日 变更如下: 【新功能】 *实现完整的电平相关参数测量功能,支持DSO模式下电压、频率、占空比等电平参数的自动测量 *适配Linux AppImage运行环境,自动设置Python home路径 *为macOS平台添加Python home设置逻辑,修复嵌入式Python路径问题 【问题修复】 *修复滚动动画期间控件鼠标交互被阻断的问题,移除WA_TransparentForMouseEvents冗余优化逻辑 *修复手动停止采集后按钮状态异常的问题 *修复MSO会话文件加载后仅显示逻辑通道不显示模拟通道的问题 *修复多类UI显示异常与进度条动画问题 *修复高DPI下文档提示图模糊的问题 *修复文档预览图片过大导致弹窗异常的问题 *修复macOS下多标签页重复显示关闭按钮的问题 *修复mmap分配器prefault线程与copy_from的竞态写零问题 *修复采集结束时异步写入器未完成处理导致波形数据丢失的竞态问题 *修复非循环模式下prefault尾部decommit导致Linux平台波形消失的问题 *修复first_payload中直接置零leaf block导致并发解码器数据破坏的问题 *修复Apple Silicon(ARM64)平台PREFAULT_PAGE_SIZE硬编码4096导致页大小不匹配的问题 *修复macOS下Python home路径使用Resources而非Versions/Current的问题 *安装脚本改用LICENSE文件直接替代LICENSE.txt *修复pxv_info调用遗留的孤立格式参数 【性能优化】 *大幅提升DSO波形绘制性能并添加帧计时功能 *重绘模拟信号绘制逻辑,提升Windows平台绘制性能 *优化示波器波形整体绘制性能 【重构】 *重构采集状态UI更新逻辑,简化状态同步流程 *调整demo设备的硬件类型处理逻辑 *替换边框组件为通用QWidget并优化属性 *清理移除全部PWMDBG诊断日志代码 【构建与文档】 *为macOS添加rpath配置以修复Qt框架加载问题 *补充完整手动编译流程文档(fx2lafw固件/Python标准库/qtwayland/DMG打包/Rosetta2交叉编译/Universal二进制) *macOS部署目标改为14.0 *完善并重构两份安装指南文档 *删除macOS和Linux的GitHub Actions构建脚本 -------------------------------------------------------------------------------- PXView V1.5.3修改 时间:2026年7月25日 变更如下: 【新功能】 *新增混合信号示波器(MSO)工作模式,支持逻辑通道与模拟通道同屏显示,新增设备模式切换、数字/模拟通道分组显示与主题色适配 *全新毛刺滤波功能:新增脉冲分析工具、直方图可视化、阈值调节、滤波区间叠加渲染、撤销栈(Ctrl+Z)、配置持久化与自动应用、滤波提示叠加层 *实现USB设备热插拔支持,跨平台热插拔API,500ms重连宽限期,事件自动通知UI *新增波形数据复制功能,右键菜单复制通道/解码器波形数据,支持同时导出逻辑通道原始数据与解码注释 *协议列表跟随波形可视范围:协议面板底部新增跟随视口开关,仅显示当前波形窗口内的解码条目 *实现信号可见性持久化,修复切换标签后信号显示状态丢失问题 *支持libsigrok浮点模拟信号,升级C++20标准,修复ADC整数路径适配上游float电压数据 *新增大量硬件驱动:Saleae Logic Pro、Chronovu LA、ASIX Sigma、锐科达DS、FTDI LA、零PLUS逻辑立方、IKALOGIC Scanaplus、恒昌DSO-2100、福禄克DMM、Gwinstek GDS-800、罗德与施瓦茨SME 0x、ipdbg LA、uni-t UT32x、Lascar EL-USB、Tondaj SL-814、PCE-322A、横河DLM、树莓派Pico、dcttech-usbrelay、devantech-eth008、greatfet等 *新增硬件存储深度接口,用于采样深度选择上限校验 *扩展MCP API:新增完整的光标/测量/会话状态接口,支持无头模式获取真实测量数据 *实现全量状态同步:触发配置持久化、设备代理通知一致性、采集归属生命周期管理、MCP事件推送 *支持在MSO模式下添加协议解码器 【问题修复】 *修复MSO模式下会话文件存储与解析的通道类型不匹配问题 *修复DSO模式多项显示与交互问题:光标自动覆盖用户滚动、信号耦合配置未实时同步、采样率配置导致的无限循环 *修复DSO视图交互、时基显示和UI卡顿问题 *修复DSO模式体验问题:实现自由缩放和平移、Windows滚轮节流、滚动条异常、触发位置显示错误 *修复多线程下段错误和线程安全问题:包括Qt线程数据竞争、Windows工作线程退出SIGSEGV崩溃、触发器内存释放错误 *修复解码完成后界面刷新和绘制异常 *修复Stream模式下采集解码卡死及界面刷新异常 *修复热插拔设备检测和固件升级重连逻辑,改用连接ID替代设备指针避免扫描重分配导致指针失效 *修复示波器缩放方向逻辑,匹配常规滚轮操作习惯 *修复DSO模式下时基选择逻辑与配置获取 *修复工作模式切换时通道使能不同步问题 *修复侧边栏移除信号处理选项及模拟通道过滤和旋钮同步问题 *修复多项DSO功能问题:屏蔽默认libusb日志、logic通道除零和空指针、表头交互、探头分压比配置 *修复DSO通道在MSO模式下不正确显示,为无vdiv配置的设备提供标准电压档位fallback *修复macOS链接问题并优化流模式处理 *修复Windows模态断言弹窗死循环与EventBus护栏问题 *修复广播同步异步竞态,根治DSO模式切换崩溃 *修复两处内存悬空和栈溢出崩溃 *修复多处空指针访问与兼容性问题 *修复D7通道假边沿和mipmap计算错误 *修复不选任何通道打不开软件的问题 *减少冗余日志噪音,优化多设备兼容性 *系统性修复状态同步漏洞,补全全链路通知 *修复触发状态不同步问题 【性能优化】 *优化渲染性能:移除热路径调试日志、调整DSO信号包络绘制阈值、复用绘图内存缓冲区、缓存探头系数避免频繁调用驱动接口 *优化模拟信号绘制:复用QPointF缓冲减少内存分配 *优化Windows滚轮缩放:添加节流机制避免密集重绘,提升滚动流畅度 【其他】 *完成核心层现代化重构,替换旧式消息广播为类型安全事件系统 *将SigSession核心逻辑拆解为独立管理器类(EventBus、CaptureManager、DecodeTaskManager等) *完成数据层与视图层解耦,引入SignalModel/DecoderStack等纯数据类作为单一数据源 *将libsigrok/libsigrokdecode改为git submodule引用 *全面替换裸指针为std::shared_ptr管理堆内存 *移除驱动层fork配置键,改用应用层封装接口 -------------------------------------------------------------------------------- PXView V1.5.2修改 时间:2026年7月22日 变更如下: 修复了若干bug -------------------------------------------------------------------------------- PXView V1.5.1修改 时间:2026年6月27日 变更如下: *调整border颜色值适配样式需求 *为控件添加WA_StyledBackground属性修复Fusion风格白边 *重构samplingbar成员为指针避免提前初始化问题 *改进MainFrame背景色设置逻辑 *修复libsigrokdecode解码器内存管理问题 *优化QSS样式表适配新的UI渲染逻辑 *为StoreSession添加导出通道和类型配置,实现导出数据筛选逻辑 *在web端新增捕获状态轮询,更新应用的捕获状态管理 *移除viewport.cpp中重复的dbp.scale(dpr, dpr)调用,合并高DPI修复 *修复启动时由于未启用通道导致的除以零崩溃问题(SIGFPE) -------------------------------------------------------------------------------- PXView V1.5.0修改 时间:2026年6月13日 变更如下: *升级Qt5到Qt6,升级qt版本到6.11 *将ribbon的选项移动到标题栏处,加入标题栏折叠功能 *为侧边栏和ribbon加入动画 *侧边栏改为推挤模式,替换原覆盖模式 *加入惯性滑动 *加入滤波和日志面板 *加入波形分组以及分通道颜色标识 *加入逻辑通道高度配置与渐变背景绘制 *加入波形高度缩放与垂直滚动 *加入信号表头右键行高管理 *加入搜索时间跳转功能 *加入高级触发模式 *加入可撤销的滤波(双倍空间存储) *重写主题系统,能够自定义颜色,字号等 *重写svg,使其能够跟随主题变化 *更改浮动测量样式 *加入FreeType字体渲染引擎,修复渲染像素偏差 *加入解码器注释字号配置 *增加多标签页功能,支持拖拽标签到独立窗口 *增加c解码器分支,迁移了215个解码器 *加入磁盘缓存功能,分层存储(内存+磁盘) *逻辑信号宏观块状合并渲染,大幅提升渲染速度 *解码通道pixmap双缓冲渲染 *解码注解mmap磁盘存储优化 *优化采集后UI卡顿,内存池与异步拷贝 *优化侧边Dock展开卡顿 *加入快捷键设置,取消括号中快捷键显示 *加入Toast提示组件 *新增繁体中文翻译 *加入MCP(Model Context Protocol)服务器,支持AI编程工具控制设备 *加入MCP网页端聊天界面,支持自然语言控制设备 *加入侧边栏MCP控制面板,显示服务状态和AI工具连接命令 *支持15个MCP工具:设备管理、采集控制、协议解码、数据导出等 *Web客户端支持Claude Code、Codex、OpenCode等AI工具接入 *若干代码架构层面的重构 -------------------------------------------------------------------------------- PXView V1.4.9修改 时间:2025年9月26日 变更如下: *修复不同dpi下窗口拖动问题,比如1080P拖动到4K下 *更新解码库,优化和新增70多种解码库,总解码库超过220种 -------------------------------------------------------------------------------- PXView V1.4.8修改 时间:2025年8月18日 变更如下: *修复废弃1.4.7版本带入的32通道开多于16通道有问题(废弃1.4.7版本) *适配新硬件PX Logic 16 Pro *适配新硬件PX Logic 16 Plus *适配新硬件PX Logic 16 base *USB的VID与PID更换为开源免费的 -------------------------------------------------------------------------------- PXView V1.4.7修改 时间:2025年8月11日 变更如下: *适配新硬件PX Logic 16 Pro *适配新硬件PX Logic 16 Plus *适配新硬件PX Logic 16 base *USB的VID与PID更换为开源免费的 -------------------------------------------------------------------------------- PXView V1.4.6修改 时间:2025年7月15日 变更如下: *修复stream模式buff大小配置无效问题 -------------------------------------------------------------------------------- PXView V1.4.5修改 时间:2025年6月12日 变更如下: *优化触发光标与实际触发位置准确度,在8采样点内 *修复触发时未采集到预设值百分比时,以实际采集百分比开始上传 -------------------------------------------------------------------------------- PXView V1.4.4修改 时间:2025年6月10日 变更如下: *修复触发位置偶发出现不准,偏移过大问题 *修复buff模式触发后采集满ddr内存后不停止采集导致数据被覆盖问题, 该问题在V1.42,V1.43均有,废弃这两个版本 -------------------------------------------------------------------------------- PXView V1.4.3修改 时间:2025年6月8日 变更如下: *修复触发等待时上位机显示采集百分比一直为0问题 *修复触发偶发卡在触发位置,不上传数据 -------------------------------------------------------------------------------- PXView V1.4.2修改 时间:2025年6月7日 变更如下: *增加触发可调节百分比功能 -------------------------------------------------------------------------------- PXView V1.4.1修改 时间:2025年6月3日 变更如下: *优化usb2.0,解决usb2.0丢包问题 *优化usb3.0 *增加win7 x64支持 -------------------------------------------------------------------------------- PXView V1.4.0修改 时间:2025年4月11日 变更如下: *修正USB2.0模式通道如果不是2的倍数会出现数据错位问题 *新增流模式buff大小可配置 -------------------------------------------------------------------------------- PXView V1.3.9修改 时间:2025年4月11日 变更如下: *修正steam模式的滚动模式,改为不停止滚动 *USB2.0模式通道如果不是2的倍数会出现数据错位问题暂未能解决,推荐客户使用usb3.0模式进行连接 -------------------------------------------------------------------------------- PXView V1.3.8修改 时间:2025年3月18日 变更如下: *修正中英文切换图标 *修正PWM频率设置过低导致的错误问题 *优化USB2.0与USB3.0的读取 -------------------------------------------------------------------------------- PXView V1.3.7修改 时间:2024年12月1日 变更如下: *修正Linux/mac下usb3.0无法使用问题 -------------------------------------------------------------------------------- PXView V1.3.6修改 时间:2024年10月7日 祝大家国庆快乐 变更如下: *废弃1.35版本,v1.35版本变更带入v1.36 *将saribbon引起的关闭程序出现范围非法内存引起的崩溃问题 *新增1路PWM0输出,输出频率1hz~1Mhz,占空比0~100%,(后续会进行优化pwm输出更高频率) *因PWM1在硬件走线靠近ddr时钟走线,对ddr有影响,pwm1不再开启,只有pwm0可以开启 -------------------------------------------------------------------------------- PXView V1.3.5修改(废弃) 时间:2024年10月7日 祝大家国庆快乐 变更如下: *将saribbon引起的关闭程序出现范围非法内存引起的崩溃问题 *新增2路pwm输出,输出频率1hz~1Mhz,占空比0~100%,(后续会进行优化pwm输出更高频率) -------------------------------------------------------------------------------- PXView V1.3.4修改 时间:2024年9月10日 变更如下: *fpga io 电平由sstl修改为hstl *修复usb2.0模式下采样率高,上位机usb读取超时无数据问题 -------------------------------------------------------------------------------- PXView V1.3.3修改 时间:2024年9月2日 变更如下: *fpga 修复stream模式1.32版本带来的低采样率数据错乱问题 -------------------------------------------------------------------------------- PXView V1.3.2修改 时间:2024年9月1日 变更如下: *fpga 修改采样率100m以下数据采样存储出现错位问题 *fpga 移除采样前平滑滤波功能,避免干扰真实信号 *fpga 解决dma预取数据命中失败问题,导致数据上传错误 -------------------------------------------------------------------------------- PXView V1.3.1修改 时间:2024年8月28日 变更如下: *优化fpga时钟树(可能可以解决杂波问题) -------------------------------------------------------------------------------- PXView V1.3.0修改 时间:2024年8月28日 变更如下: *修复采集过程中usb断开导致上位机卡死无响应问题 *更新FPGA固件,降低ddr IP AXI时钟为100m(可能可以解决杂波问题) -------------------------------------------------------------------------------- PXView V1.2.9修改 时间:2024年8月26日 变更如下: *修复buff模式满带宽8G运行,最后一个通道采样点提前64个周期问题 -------------------------------------------------------------------------------- PXView V1.2.8修改 时间:2024年8月25日 变更如下: *增加每次采集进行初始化ddr,避免因为温度差异训练导致出现的ddr时序问题 *修正采集数据每个通道有64个采样点为0的数据问题 *加入低功耗,采集完毕关闭ddr时钟与通道采集时钟,降低功耗 *修复1.27用错fpga版本问题 -------------------------------------------------------------------------------- PXView V1.2.7修改 时间:2024年8月25日 变更如下: *增加每次采集进行初始化ddr,避免因为温度差异训练导致出现的ddr时序问题 *修正采集数据每个通道有64个采样点为0的数据问题 *加入低功耗,采集完毕关闭ddr时钟与通道采集时钟,降低功耗 -------------------------------------------------------------------------------- PXView V1.2.6修改 时间:2024年8月23日 变更如下: *更新fpga固件,修复时序问题,解决偶发数据错误 -------------------------------------------------------------------------------- PXView V1.2.5修改 时间:2024年8月21日 变更如下: *更新fpga固件,修复时序问题,解决偶发数据错误 *修复github开源url错误问题 -------------------------------------------------------------------------------- PXView V1.2.4修改 时间:2024年8月15日 变更如下: *修复流模式采集数据超过512MB后,数据异常问题 -------------------------------------------------------------------------------- PXView V1.2.3修改 时间:2024年8月14日 变更如下: *逻辑分析仪序列号增加到64位,一台电脑可同时连接多台设备 *解决usb芯片与fpga芯片通讯因为时序问题出现的误码 -------------------------------------------------------------------------------- PXView V1.2.2修改 时间:2024年8月11日 变更如下: *更新解码库,增加解码协议数量 *加入保存通道模式配置,解决关闭程序后,通道配置模式无法保存 -------------------------------------------------------------------------------- PXView V1.2.1修改 时间:2024年8月10日 变更如下: *修复解码结果多字节搜索导致的崩溃问题 *开发stream 模式到2通道1G -------------------------------------------------------------------------------- PXView V1.2.0修改 时间:2024年8月8日 变更如下: *增加触发前预先缓存4k数据,解决触发时刻概率丢失第一个触发数据 *修正第32通道数据偏移64个采样点问题 -------------------------------------------------------------------------------- PXView V1.1.9修改 时间:2024年8月6日 变更如下: *修正采样时间显示不全 *修正usb2.0模式电流不够,配置为500ma -------------------------------------------------------------------------------- PXView V1.1.8修改 时间:2024年8月5日 变更如下: *修正解码数据与指示延时不对应 *修正E快捷键模式选择不生效 *修正修改通道颜色后,点击文件-默认配置无法生效,无法恢复默认通道颜色配置 -------------------------------------------------------------------------------- PXView V1.1.7~1.1.2修改 时间:2024年8月4日 变更如下: *修正解码数据与逻辑数据不对应问题 *修改通道颜色与信号不对应问题 *修正采集完毕丢了第一次触发数据,采集数据多了一些导致 *修正因为usb导致通道数据串到另外一个通道问题 -------------------------------------------------------------------------------- PXView V1.1.1 时间:2024年8月1日 变更如下: *将数据默认颜色修改为彩色 -------------------------------------------------------------------------------- PXView V1.1.0 时间:2024年7月27日 变更如下: *修正界面被遮挡问题 *将stream模式2G带宽降低为1.6G,确保usb3.0的hub都能满足 -------------------------------------------------------------------------------- PXView V1.1.0 时间:2024年7月27日 变更如下: *修正界面被遮挡问题 *将stream模式2G带宽降低为1.6G,确保usb3.0的hub都能满足 -------------------------------------------------------------------------------- PXView V1.0.0 PXview基于DSview 1.3.1 与sigrock 2.0项目,感谢他们的贡献开源贡献 [dreamsourcelab.com](https://www.dreamsourcelab.com) [sigrok.org](https://sigrok.org) 变更如下: *加入SCI Logic 支持,usb3.0 32通道1G采样率 *PXview加入PWM输出功能,基于SCI Logic