# 46: 一级模式图层计划与选择态矩阵 Type: implementation Status: resolved Blocked by: 42 Labels: ready-for-agent ## What to build 按规格 18 建立唯一的模式图层计划纯函数(建议 `domain/modeLayerPlan.js`),统一输出当前模式的 markers、blocked、legend、locateTargets、degradedNotes。引导态和四个一级模式使用已裁决矩阵;地图引擎和组件只消费计划,不做第二次过滤。一级模式切换保持 Viewer、选择身份和快照;总览与分项切换暂停/恢复选择,分项之间清空。 ## Acceptance - [x] 引导态不显示业务标记、图例、计数、详情,但允许预取。 - [x] 以注入的可绘制对象验证完整矩阵:总体概览可见监测点、未关联设备、选中点的有效关联设备、设施点和管线;基础设施仅设施点和管线;运行监测为监测点、未关联设备、有效关联设备和管线;预警处置为异常点、预警/报警未关联设备、关联设备中 `ALARM/WARNING` 和管线。实际设施/管线源未就绪时只验计划与诚实降级,不宣称这些图层已落地。 - [x] 异常点只依据设备状态事实回联,不借用监测点聚合状态;跨端点不同步如实提示。 - [x] 设施或管线数据未就绪显示 degraded note,不伪装为空;设施点不污染监测点计数/图例;事件只回联定位不建独立标记。 - [x] 管线四模式常驻;地面/管线视图只改变相机和地表/瓦片透明度,不控制管线层显隐。 - [x] 图例与实际模式标记同源,摘要计数不含关联设备,设备图例独立。 - [x] 总览↔分项保留选择并按可见性暂停/恢复;分项↔分项清空;模式切换不换快照、不重建 Viewer、不动镜头。 - [x] 纯逻辑覆盖五态矩阵、异常判定、图例一致性、引导门控、降级说明和回联目标;外壳走查覆盖模式切换、图例和引导态。 ## Out of scope - 设施点拾取/详情/视觉词表、管线数据源、设备图层底层生命周期、设备状态特效和重叠展开。 ## 进度:100%(2026-09-30 收口) **已完成并关闭。** 领域断言 **28/28**、变异 **8/8 全部打红**、真机走查 **10/10(连跑两次稳定)**, 全量零依赖回归绿(`t04`/`t28` 两处既有红与本票无关,均已核实为并发/环境问题)。 ### 交付物 - `domain/modeLayerPlan.js`(新):**「一张图什么时候显示什么」的唯一领域接缝**(规格 18)。 `planModeLayers()` 输入 `{ mode, objects, abnormalDevices, facilities, pipelines, associatedDevices, selection, entered }`, 输出 `{ markers, blocked, legend, byStatus, locateTargets, degradedNotes, facilities, pipelines, associatedDevices, layerStates, selection }`;另有 `planModeSelectionTransition()`(总览↔分项保留 / 分项↔分项清空)与 `isAssociatedDeviceVisible()`(关联设备模式判据的唯一住址)。 - `services/mapService.js`:`syncPointMarkersForSummary(objects)` 改为 **`syncPointMarkersForPlan(plan)`** —— 服务层只消费计划,不再自己调 `planPointMarkers` 重判(规格:引擎和组件不做第二次过滤)。 - `components/MapCanvas.vue`:接收外壳算好的 `plan` prop;图例/降级说明直接渲染计划输出; 引导态门控图例(`v-if="entered"`)与计数浮标;新增 `data-map-degraded-note` 走查断言面。 - `domain/deviceLayer.js`:`isVisibleInMode()` 改为**别名转发**到 `modeLayerPlan.isAssociatedDeviceVisible` (判据唯一住址;t42 既有断言入口不变)。 - `stores/deviceLayerStore.js`:`syncForSelection({ entered })` —— 引导态关联设备图层整体撤下(数据仍可预取)。 - `CockpitShell.vue`:**外壳是唯一计算计划的地方**(握有 mapStore/设备行/异常设备/选择态全部输入); `modeLayerPlan` computed 一次计算,MapCanvas/详情浮层只消费;模式 watcher 执行 `planModeSelectionTransition` 的结论(分项↔分项 `closeDetail()`,唯一的关闭入口); `modeAbnormalDevices` 来自 `/monitoring/situation`(运行概况分区的 `abnormalEquipment[]`), 未取到时传 `null`(计划输出降级说明,不猜异常点);`introVisible` watcher 联动设备层重算。 - `verify/t46-mode-layer-plan.mjs`(新,**28/28**)+ 变异 **8/8 全打红**; `verify/t46-mode-layer-walkthrough.mjs`(新,**10/10**)。 ### 关键取舍(都来自已 resolved 的规格 18 / 决策票 13,不在本票发明) 1. **计划只算一次、在外壳**:MapCanvas 不再 import 计划函数(避免"顺手自己再过滤一遍"的诱惑), 它只消费 `plan` prop。服务层入参从 objects 改为 `{ markers, byStatus }`,模式裁决无第二条路径。 2. **数据可用性先于模式可见性**(票 42 的既有纪律):关联设备的 `classifyDevice` 先判 `pending/inconsistent/no-location/unavailable/terrain-pending`,计划层只对**已 drawn** 的行做模式过滤。 3. **异常点只认设备状态事实**:`WARNING/ALARM` 之外的 `WORK_ORDER/OFFLINE/UNKNOWN` 不构成异常点 (A6 钉住);异常设备数据未就绪时预警处置**不画任何点**并给 `abnormal-devices-unready` 降级说明 (A7:不猜、不借用点位聚合状态)。 4. **degraded ≠ hidden ≠ empty**:设施/管线契约未交付传 `null` → `degraded` + 降级说明; 契约就绪(哪怕空数组)→ `ready`;模式不显示 → `hidden`。管线未就绪在四模式都是 `degraded` 而非 `hidden`(A8b:常驻 ≠ 数据就绪才常驻,不把缺数据伪装成"该模式没有对象")。 5. **设备级选择优先看设备**:设备级选择保留 `pointCode` 供"返回点位"用,判据若先查 pointCode 会把"设备被本模式隐藏"误判成"点位可见 ⇒ 显示"(本票自查抓到,见下)。 6. **详情暂停 = 隐藏但保留选择身份**:`selection.visible=false` 时浮层隐藏(`v-if` 摘除), `selection` 状态一个字节不动;切回目标模式可见时**自动恢复**——恢复就是"计划重新判定为可见", 不需要额外状态机。 ### 本票自查抓到并修掉的 2 处真问题 1. **设备级选择的可见性判据顺序错误**:初版 `selectionVisible()` 先查 `pointCode` 再查 `deviceCode` —— 设备级选择带着 `pointCode`(返回点位要用),于是"选中的 NORMAL 设备在预警处置应暂停" 被判成"其点位可见 ⇒ 显示"(F4 逮到)。修法:设备级**优先看设备**,点位级才看点位标记。 2. **走查 W6 曾是恒真空转**:初版把"降级可见性"写在受控有效坐标的第一趟(两台设备都上图, 走不到降级分支),`blockedRows.length === 0 ? true : …` 恒真却显示 PASS。修法:另跑一趟 **默认样例**(现网边界 `renderable=false`),要求降级行确实存在。 ### 真机走查(已执行,10/10,连跑两次稳定) 驱动:`verify/cdp-driver.mjs`(票 44 交付的零依赖 CDP 驱动;本机 `puppeteer-core` 仍缺失), `document.hidden === false`(Cesium 真实渲染)。 - **W0 引导态门控**:进入前图例 0 行、标记计划 0(数据可预取)——外壳 `introVisible` → 计划 `entered=false` 的门控链路真实生效; - **W1–W2**:进入系统后总览上图 2(`device-layer` 受控样例的两个监测点); - **W3 基础设施**:标记计划 0、上图 0(监测点/未关联设备/关联设备全隐藏;图例 8 行是票 09 的 "0 不隐藏行"既有契约,**矩阵的落点在标记计划**); - **W4 运行监测**:上图 2,与总览一致; - **W5 预警处置**:标记计划 0 + `pipelines-unready` 降级说明(该样例 `operationOverview` 未取数 ⇒ 异常设备数据未就绪 ⇒ 不猜异常点,如实降级); - **W6 图例与标记同源**:标记 2、图例 8 行(含 0 计数行),形状 2 种; - **W7–W8 快照与 Viewer**:四模式切换后快照标记逐字节一致、引擎全程 `ready`(不换快照、不重建 Viewer); - **W10**:全程无 pageerror / console.error。 ### 与下游票的边界(不在本票实现) - **票 45**:重叠设备的聚合符号、展开环与逐台拾取。 - **票 47**:状态特效;并请明确 `deviceStatus.js` 的 `deviceLegend()` 去留(票 44 移交)。 - **设施点/管线数据契约**:计划已就位(`facilities/pipelines` 入参 + degraded 说明), 数据到位后只需把真实集合传进 `planModeLayers`,无需再动矩阵。 ### 回归 零依赖闸门 **35 道**全绿(含本票新增 t46;t10 的 F5/F7 守卫按票 46 结构更新 —— 意图不变:`planPointMarkers` 唯一调用点在计划层、MapCanvas 只喂计划、浮层仍是 main 兄弟节点)。 两处红与本票无关:① `t28-fill-container.mjs` 需 puppeteer(模块已不在本机); ② `t04-config.mjs` 的「不该硬编码后端地址」指向 `services/cockpitApi.js`(并发会话正在改)。 **按 `AGENTS.md`:本会话未跑 build;`yarn dev` 未由我启动(8000 端口已有服务在跑)。 浏览器仅用于本次走查(维护者授权的提权通道,同票 44)。**