# 原生鼠标软件呈现节奏验收 `NativePointerFrameProbe` 默认关闭,由 Kernel 的 F12 raw make `0x58` 开始/停止。固定 4096 帧分为 16 个结构体数组,每块约 14 KiB;正常输入和 Present 热路径不分配、不打印。只记录真正增长的 `CanvasAdapter.FramesPresented` 以及实际拷贝的起止时间。停止后才分块输出串口,单块不超过 16,000 个字符。 协议为 `XFE_POINTER_TIMING_BEGIN|1`、一条 `META`、若干 `FRAME`、`XFE_POINTER_TIMING_END`。META 包含实际 Present/Observed/Consumed/Lost 的起止计数、时钟频率、起止 ticks、捕获帧数、漏帧/溢出和输入范围。FRAME 包含实际 Present 序号、起止 ticks,以及最新被 UI 消费的输入序号、设备报告 ticks、分发 ticks、位置。遗漏帧、时间倒退、ring overflow、输入丢失、未来输入、错计数等不能通过解析/验收。 `TestPointerCadence.py` **只连接已经启动、已知 UUID 的私有 QMP**,不启动或退出 QEMU,不复制系统镜像/ELF,不修改 DOM/guest 内存。独立入口先验证 UUID、正在运行的状态、精确源镜像 backing 链、镜像 SHA256 和已通过启动记录。`--require-usb-mouse` 要求当前活动输入设备明确是 USB,不能把 PS/2 的结果当作 USB 验收。实体鼠标兼容性仍需实体电脑测试。 ```powershell python Tools/TestPointerCadence.py --qmp-port --qemu-uuid --image --boot-result --serial-log --pointer-x --pointer-y --seconds 5 --input-rate 1000 --target-hz 240 --require-usb-mouse ``` 已有 QMP 连接的专用测试可直接导入 `run_pointer_cadence(qmp, read_log, screenshot, width, height, pointer, output, seconds=5, input_rate=1000, target_hz=240)`,避免同时连接 QMP。由原测试负责启动/关闭其自身的私有 QEMU。`pointer` 是当前由该测试追踪的原生坐标可变列表;`screenshot` 必须返回实际 QMP framebuffer 图片路径,不得提供宿主截图。 测量窗口只发送有界的真实仿真鼠标报告,不截屏、不逐帧打印。F12 停止后才取两张原生分辨率 PNG;每张的临时 PPM 在转换并关闭图片后删除。PNG 不缩放,不添加宿主光标。截图差异只表示 framebuffer 变化(可能包含时钟),光标外观仍要目视检查;实际鼠标消费与呈现使用 native FRAME 绑定独立确认。 结果明确区分 QMP 排队/ack 速率、guest 设备报告/消费、软件 framebuffer Present、实体屏幕扫描。240 Hz EDID 只是目标元数据。`SoftwareCadenceGoalMet` 同时要求实际 Present 和新鼠标位置更新达到目标。全窗口 Present 的频率、帧间隔和边界计算保留;新鼠标位置则使用以下独立口径: - 基线固定为连续输入窗口内,最早携带本窗口真实新消费报告的实际 Present。probe 初始化的 `(0,0)` 或旧报告不能充当基线,真实报告位于 `(0,0)` 则仍然有效。基线本身不计移动,也不能为挑选更快时段而后移。 - `FreshPointerPresentations` 只计相对前一实际 Present 同时满足新输入序号和位置变化的真实转换。`FreshPointerPresentHz` 为转换次数除以“固定基线 Present 完成至末次转换 Present 完成”的实测时间;`FreshPointerCadenceWindowSeconds`、基线帧序号/ticks 均输出。至少两个转换才有足够节奏证据,重复绘制或无关动画不能冒充鼠标更新。 - `FreshPointerFrameIntervals` 包含基线到首个真实转换的间隔,也包含之后所有转换间隔;其间重复位置、复用输入序号和停顿都不能跳过。首输入到基线的 `FreshPointerFirstResponseMs` 与末次转换到末输入的 `FreshPointerTailGapMs` 独立保留在 `FreshPointerBoundaryGapsMs`,分别按目标周期检查,不能用缩短稳态窗口隐藏卡顿。 例如,首个位置在输入后 2 ms 实际显示,之后每 4.1667 ms 发生真实转换,不能把基线排除后,再把首个转换相对首输入的 6.1667 ms 错算成单个移动间隔。若首响应、首个真实转换或尾部停顿超出目标周期,仍然失败。两套频率、全部帧间隔与独立边界必须同时达标,没有降低 240 的阈值。所有慢帧都保留在 p95/p99/max 与超预算帧计数中。软件达标也不等于 VSync/实体 240 Hz:`NativeScanoutHzMeasured=null`、`PhysicalMonitorRefreshMatched=false`,不会因 EDID 或截图改变。 工具另外比较真实宿主单调时钟的首个输入排队到末个输入 ack 跨度,与 guest 首末设备报告 tick / 声明 Frequency 的跨度。这个比较始终使用完整输入窗口,不能换成较短的基线子窗口。容差明确为 50 ms 输入边界/排队误差加宿主跨度的 2%;超出时 `EvidenceValid=false`,禁止据此声称 240。即使偏差在容差内,也按两个完整跨度中的较大值获得保守时间倍率,并将同一倍率用于全窗 Present、基线子窗口的鼠标速率、全部间隔和独立边界;此校正只能降低 FPS、增加 gap,不能把真实 238 FPS 修饰成 240。原始 guest 数值与比较比例、差值、容差都保留。工具口径修复不会改写既有实测日志或结果文件。 离线工具负例:`python Tools/TestPointerCadenceGuards.py`。Probe 的 host 结构/零分配/分块检查在 `XFESystem.NativeInterop.Tests`,均不代表已运行实体硬件。 可见 QEMU 启动器的可选 `Tools/OpenCSharp.ps1 -TraceConsoleRefresh` 只增加 QEMU 的 `console_refresh` trace,保存到该新会话的 `ConsoleRefresh.log`,路径记录在 `Launch.json`/`LaunchPlan.json` 的 `Display.ConsoleRefreshTrace`。桌面 Ready 后会保存当时的 `ConsoleRefreshSummary.json`;这是一份时间点观察,后续跨屏或鼠标活动可能使日志继续增长。可在不操作 VM 的情况下重新读取并另存新摘要: ```powershell Tools/CSharpBoot/ReadConsoleRefreshTrace.ps1 -TracePath -OutputPath ``` 该摘要只列 GUI 轮询计时器**公告的配置间隔**,不计算或宣称可见 FPS、物理刷新率、VSync。没有记录时保持未知;30/10/4 ms 等间隔都不能单独证明最终画面以对应频率显示。开关不会修改宿主显示模式或关闭任何既有 QEMU,辅助读取脚本也不会启动进程。