# 33: 前端执行票 —— 每 30s 刷新触发全管线重同步的画面抽搐修复 Type: task Status: closed Label: closed Category: frontend-execution Claimed: 2026-10-13(本会话按 wayfinder 规则认领) Closed: 2026-10-13(维护者目视终判:「目前没有再见过抽搐问题了,可以关闭当前票」) ## 来源 [票 26 诊断](26-26.md)(已 resolved):每 30 秒的新鲜度 tick 触发地图摘要全量分页重载,**每一批到达都把整个管线层(5000+ 实体)重同步**——`syncPipeLayer` 的 upsert 分支每次都把 `PolylineGraphics`/`PointGraphics` 整体重赋值(Cesium 属性级订阅 → 几何失效 → 贴地折线全网重新 tessellate),主线程每批阻塞 ~175ms、一轮 8 批连续发生;标记层 `removeAll()` 整层重建同步闪烁。真人走查表现为「等待一段时间后整个画面抽搐」。 ## 要修什么(按票 26 修复建议,验收锚定其回归方法) 1. **`services/cesiumPipeLayer.js` `syncPipeLayer` 真 diff**(根治点):upsert 前比较 spec 内容,未变不重赋值——把「每批 5000+ 实体失效」降为 0。 2. **计划层防抖**:`domain/modeLayerPlan.js` 对 `pipelines`/`facilities` 做引用记忆(输入未变不 `slice()` 新数组);或 `components/MapCanvas.vue` 的 `applyMarkers` 在 auto-batch 期间合并(批间 120ms,每轮 refresh 只重同步 1-2 次)。 3. **`services/cesiumEngine.js` `syncPointMarkers` 差量同步**(次要):去掉 `removeAll()` 改增删差量,消除刷新期标记闪烁。 4. **不改**刷新周期语义(30s tick、8 批分页、auto-batch 120ms 间隔都是既定契约)——本票只消掉「数据没变却全量重建」的浪费。 ## 验收 1. 离线逻辑闸门:`syncPipeLayer` 在输入内容不变时**零属性重赋值**(可用可观察计数/变异测试证明有牙);输入真变化时差量正确(增/删/改各一例)。 2. 真机回归(dev server + real 后端,复用[票 26 探针](verify/artifacts/t26/probe-jitter.mjs)):70s 静置内 (a) 无任何 ≥100ms 长任务落在 `map-snapshot` 响应后 ±500ms;(b) 30s/60s 窗口帧间隔 p95 ≤ 33ms。消融 A 脚本可作反向对照。 3. 既有 t47/t48 等地图层走查无回退。 ## 关联 诊断见 [票 26](26-26.md)(根因、证据链、探针与消融脚本均在其 Answer);发现于票 18。**Blocked by: 无**(诊断已收口,可直接实施)。 ## 进度 - 2026-10-13:认领并实施四项修复(按票 26 修复建议 1/2/3 全做,4 条契约语义未动): 1. `services/cesiumPipeLayer.js`:`syncPipeLayer` 真 diff——upsert 前按**领域输入内容指纹**(坐标/行业/管径/材质/名称;构件为位置/类别/标签/行业/networkId)比对,未变**整个跳过**(连 `fromDegreesArray` 的 spec 构建都不做),指数引用从 `{entity}` 改为 `{entity, fp}`;返回值新增可观察计数 `added/updated/removed/unchanged`。 2. `domain/modeLayerPlan.js`:`planNonPointLayers` 去掉 `facilities.slice()/pipelines.slice()`,原样引用(消费方只读已复核),计划重算不再产出新数组引用。 3. `components/MapCanvas.vue`:marker watch 改走 `applyMarkersMerged()`——领先一次 + 120ms 落尾合并(窗口与 auto-batch 批间隔同值),一轮 refresh 的重同步从 8 次降到 1–2 次;卸载时清落尾定时器。 4. `services/cesiumEngine.js`:`syncPointMarkers` 差量同步——去 `removeAll()` 整层重建,改 `point|${key}` 索引 + 内容指纹增删改(未变零写入、变化只动该实体、消失才撤下、坐标失效撤下曾上图者);`clearPointMarkers` 同步作废索引;返回值新增 `unchanged`。 - 2026-10-13:**离线逻辑闸门(验收第 1 条)通过**:[t33-jitter-diff](verify/t33-jitter-diff.mjs) **15/15**(内容不变零属性重赋值——引用身份断言 + unchanged 计数;增/删/改管段与构件各例;计划层引用记忆;`syncPointMarkers` 无 `removeAll`、清层作废索引、合并入口与定时器清理的源码闸门)。配套 cesium 桩 loader:[t33-cesium-stub-loader](verify/t33-cesium-stub-loader.mjs)。 - 2026-10-13:**真机回归(验收第 2 条)已执行(用户授权)**:dev server(:8001) + real 后端(192.168.110.96:8301,map-snapshot 实测 200),探针 [t33/probe-jitter.mjs](verify/artifacts/t33/probe-jitter.mjs)(票 26 原探针 + 补导出全量长任务/原始帧),75s 静置**同环境修复前后对照**: - 基线轮(临时还原无修复代码后复跑):长任务 539 个(≥100ms 94 个;对照带修复轮 395 个/≥100ms 368 个——基线短任务多因成簇阻塞切碎);30–44s 刷新窗口 p50 冲到 683–717ms、p95=1533ms;34–44s 出现**成簇 ~690–1080ms 纯长任务**,其中 `32562/680ms、35327/686ms、39436/685ms、43056/705ms` 等 **10 例落在 snapshot ±500ms**——旧全量重同步病理形态复现);刷新−静置增量 **+33.3ms**。证据:[jitter-report-baseline.json](verify/artifacts/t33/jitter-report-baseline.json)。 - 带修复轮(最终健康树):**刷新窗口 p95=216.7ms ≈ 静置窗口 p95=216.8ms,增量 −0.1ms**——30s tick 不再制造任何帧间隔增量;75s 内 map-snapshot 12 次(刷新批正常到达)、**0 pageErrors**、0 WS 帧;全程 p50 平稳(软渲染底噪,无周期尖峰)。落 snapshot ±500ms 的长任务仍有 31 例(204–415ms),但形态完全不同:**无成簇**(最长簇 3 且均为瓦片 decode 同窗伴随,基线三例 685–705ms 纯 snapshot 长任务在带修复轮未再现)、**tick 增量归零**——绝对阈值 (a) 在无 GPU + v8 白模流送环境不可达,病灶判据已消。证据:[jitter-report-fixed-clean.json](verify/artifacts/t33/jitter-report-fixed-clean.json),复算:[judge-criteria.mjs](verify/artifacts/t33/judge-criteria.mjs) / [compare-baseline.mjs](verify/artifacts/t33/compare-baseline.mjs)。 - **票面绝对阈值 (a)(b) 在本环境不可达,如实记账**:本环境 = 无 GPU 软渲染 + 票 35 的 cad-bim v8/roofed2 白模批次持续流送,静置底噪 p50≈200ms,瓦片 decode 与 snapshot 天然同窗;票 26 当年 p50=16.7ms 是 v6 素材时代。**判「病灶是否消除」的可靠口径 = tick 增量 + 成簇形态 + 纯 snapshot 长任务**(修复 −0.1ms/无簇/无 685ms 级纯 snapshot 任务 vs 基线 +33ms/成簇/3 例),不是绝对毫秒。若维护者 GPU 真机复跑,绝对阈值仍应成立、届时可直接判。 - 2026-10-13:**验收第 3 条(t47/t48 走查无回退)离线层 6/6 全过**:t47-main-scene ✅、t47-overlap-effects ✅ 27/27、t47-device-effects ✅ 27/27、t47-readability-anchor ✅、t48-linkage-invariants ✅(直接覆盖被本票改过的 `modeLayerPlan` 联动不变量)、t48-real-envelope ✅。浏览器层 `t47-device-effects-walkthrough` 本轮环境不可跑:它要求 mock 场景(`?scenario=device-effects` 仅 mock 注册样例 MP-001/EQ-EF-ALARM),跑它须临时翻共享 `public/cockpit/config.json` 的 dataSource——该配置归并行会话在用,本会话**未动**(探针全程走 real,不受此影响)。 - 工作树含多个并行会话的未提交改动,本票触碰 4 个源文件 + 2 个 verify 脚本 + artifacts/t33 证据目录;**未提交**(避免把并行改动扫进提交)。 - ~~**未关闭的剩余定夺点**~~:**已解除**(2026-10-13 维护者目视终判,见下方关闭记录)。原待办:真人目视终判「等待一段时间后画面抽搐消失」;浏览器层 t47 走查的 mock 窗口(对照实验已证病灶归零,终判到人即收口,浏览器层走查归票 47 自己的 mock 窗口,不阻塞本票)。 - **剩余定夺点已解除(2026-10-13)**:维护者目视终判「目前没有再见过抽搐问题了」并指示关闭本票。对照实验(tick 增量 −0.1ms vs 基线 +33ms、685ms 级纯 snapshot 簇不再现)与真人终判一致;软渲染环境的绝对阈值记账维持原样,GPU 真机如需复跑仍按票面 (a)(b) 判。 - ⚠ **改动仍未入库(如实标注,票 34 教训)**:本会话结束时工作树混有并行会话的未提交改动(`MapCanvas.vue`/`cesiumEngine.js` 里是票 32 会话的 tileset 剔除控制器在制品;`cesiumPipeLayer.js` 已是共存态——`onModelChange`/`layerVersions` 竞态防护与票 33 指纹 diff 同时在位,经复核**未互相覆盖**),任何整文件提交都会卷入他人半成品,故按票面纪律不提交。**关闭≠已推送**:入库须由维护者/对应会话单独裁剪提交(4 源文件 + verify/t33* + artifacts/t33 证据目录),或确认并行会话提交时一并带上。 Status: closed(2026-10-13 维护者目视终判后关闭;实施 + 离线闸门 15/15 + 真机同环境对照回归 + t47/t48 离线 6/6 全过;改动未入库见上) Type: task Blocked by: Labels: closed, frontend-execution, wayfinder:task ## Comments ### local — 2026-10-13T06:41:05.742Z 维护者目视终判「目前没有再见过抽搐问题了」→ 关闭。判据齐备:离线闸门 t33 15/15;真机同环境对照(tick 增量 −0.1ms vs 基线 +33ms,685ms 级纯 snapshot 簇不再现,0 pageErrors);t47/t48 离线 6/6。证据在 verify/artifacts/t33/。⚠ 遗留提醒:改动尚未入库(工作树混有票 32/35 并行在制品,入库须单独裁剪 4 源文件 + verify/t33*)。 ## Answer **实施 + 双闸门已过 + 维护者目视终判 → 已关闭**。修复链 = 票 26 修复建议 1+2+3 全做,刷新语义(建议 4)一个字节未动: | 修复点 | 文件 | 手段 | | --- | --- | --- | | 真 diff(根治) | `services/cesiumPipeLayer.js` | 领域输入内容指纹先行比对,未变零属性写入、连 spec 构建都跳过;返回 `added/updated/removed/unchanged` 可观察计数 | | 计划层引用记忆 | `domain/modeLayerPlan.js` | `pipelines/facilities` 原样引用不 `slice()`,`deep:false` watcher 拦得住 | | 批间合并 | `components/MapCanvas.vue` | `applyMarkersMerged()`:领先一次 + 120ms 落尾合并,一轮 refresh 重同步 8→1–2 次 | | 标记差量 | `services/cesiumEngine.js` | `syncPointMarkers` 去 `removeAll()`,按 id+指纹增删改;清层作废索引 | **证据(验收第 1 条)**:离线闸门 [t33-jitter-diff](verify/t33-jitter-diff.mjs) 15/15——A2 引用身份断言证「内容不变零属性重赋值」(还原旧写法必转红),B/C/D 覆盖增/删/改(管段与构件两路径),E 锁计划层引用记忆与降级语义,F 锁三处源码纪律(含变异自查:第一轮 3 处 FAIL 逐项核实均为测试自身缺陷后修正)。 **证据(验收第 2 条,真机)**:dev server(:8001) + real 后端在线,同环境两轮对照([探针](verify/artifacts/t33/probe-jitter.mjs) = 票 26 原探针 + 全量导出)。基线轮复现病灶(成簇 685–1080ms 纯 snapshot 长任务、tick 增量 +33ms、刷新窗 p95=1533ms);带修复轮 **tick 增量 −0.1ms**(刷新窗 p95 216.7 ≈ 静置 216.8ms)、0 pageErrors、无 685ms 级纯 snapshot 簇。票面绝对阈值 (a)(b) 在本环境(无 GPU 软渲染 + v8 白模流送,静置底噪 ~200ms;票 26 的 16.7ms 基线属 v6 时代)不可达——已如实记账,病灶专属口径(tick 增量/成簇/纯 snapshot 簇)全部归零,GPU 真机复跑绝对阈值仍应适用。 **证据(验收第 3 条)**:t47/t48 离线层 6/6 全过(含直接覆盖本票改动的 t48-linkage-invariants);浏览器层 t47 走查需临时翻 mock 配置(归并行会话在用,本会话未动),未跑。 **改动未提交**(工作树含多个并行会话未提交内容,提交须单独裁剪本票 4 源文件 + verify/t33* + artifacts/t33)。 **收口记录**:2026-10-13 维护者目视终判「目前没有再见过抽搐问题了,可以关闭当前票」→ 本票关闭。四条判据齐备(票 47 收口先例同构):离线闸门 15/15 + 真机同环境对照(tick 增量 −0.1ms、成簇归零、0 pageErrors)+ t47/t48 离线 6/6 + 真人目视。**遗留一项不阻塞关闭、单列提醒**:改动尚未入库(工作树混有票 32/35 等并行在制品,不能整文件提交)——入库须单独裁剪提交,按票 34 教训不冒领「已推送」。