# 47: 设备状态视觉特效与门控 Type: implementation Status: claimed Blocked by: 38, 44, 45, 46 Labels: ready-for-agent ## What to build 按决策票 07 在票 44 的独立设备状态词表上实现特效计划,不复用监测点聚合状态。设备五态为 `ALARM`、`WARNING`、`WORK_ORDER`、`NORMAL`、`OFFLINE`,未知状态落 `UNKNOWN`;报警扩散脉冲、预警闪烁、工单旋转环,正常/离线无特效。特效随关联设备图层生命周期门控,选中设备强化,关闭详情随图层销毁;超过 30 台异常设备降级为静态异常标记并显示计数。 ## Acceptance - [x] 消费票 44 已落地的独立 `deviceStatus` 静态词表,并添加 effect 维度;设备状态不从 `pointStatus` 推导,未知不猜正常。 - [x] 三种需处置状态有各自动效,正常/离线无动效;颜色/形状/文字沿设备状态语义。 - [x] 特效随关联设备图层出现/消失,选中设备可强化,详情关闭后无残留动画。 - [x] 超过 30 台异常设备按规则降级为静态异常标记 + 异常计数,不静默删点;降级后仍看得出异常。 - [x] 特效与雾气、透明管线和重叠聚合共存,不能破坏拾取;沿既有雾规则随远景衰减,不开穿雾例外。 - [x] `map.effects` 配置缺省 `null` 跟随构建形态;关闭动效不隐藏静态状态表达。 - [ ] 纯逻辑覆盖状态映射、未知、门控和并发降级;真实帧差与人工走查分别记录结果,未获人工确认不关闭本票。 - [x] 对受控的 `coordinateValidation.renderable=true` 设备样例做场景跨帧快照证明逐点动画确实变化;真实环境仍为清单-only 时不得从纯逻辑断言推断现场特效已生效。 > 唯一未勾的一项=**人工目视裁定**(本票第 7 条的"人工走查"半)。自动证据已齐,见下方进度。 ## 进度:实施完成,**待人工确认**(2026-09-30) **未关闭**:本票验收第 7 条明写「真实帧差与人工走查分别记录结果,**未获人工确认不关闭本票**」。 自动证据(纯逻辑 20/20、跨帧快照 9/9、降级 6/6、真实帧差已取)已齐,**缺的只有维护者目视裁定** (脉冲强度是否合适、正常/离线是否够可辨、30 台降级是否仍读得出「有异常」)。 ### 交付物 - `domain/deviceStatus.js`:按决策票 07 给设备独立词表**新增 `effect` 维度** (`ALARM: pulse` / `WARNING: blink` / `WORK_ORDER: spin` / `NORMAL: null` / `OFFLINE: null` / `UNKNOWN: null`)。正常、离线、未知**一律无动效** —— 未知状态更不编一个"正在报警"的样子。 - `domain/deviceEffects.js`(新,纯函数):`deviceEffectOf()` 映射、`effectPhaseAt()` / `pulseStrengthAt()` / `blinkStrengthAt()` / `spinAngleAt()` 相位、`planDeviceEffects()` 门控与降级、 `degradedAbnormalNote()` 计数文案。**「什么算异常」复用票 46 的 `isAttentionDeviceStatus` 单一住址**(`ALARM`/`WARNING`),不另造一份定义。 - `services/cockpitConfig.js`:新增 `map.effects { enabled, abnormalLimit }`,与票 32 的 `map.ui.debug` **完全同构**(`enabled: null`=跟随构建形态;脏值退回默认并记 issue)。 - `services/cesiumEngine.js`:`applyDeviceEffects()` / `stopDeviceEffects()` / `readDeviceEffects()`(只读跨帧读回)。动效**乘在票 43 反应量的基准上**(`deviceBaseStyle`), 不与选中/hover/弱化互相覆盖;载体是场景内 billboard,**不是后处理级** ⇒「先特效、后雾」自动成立,不占 `PostProcessStageCollection`、不开穿雾例外。 - `services/mapService.js` + `stores/deviceLayerStore.js` + `components/PointDetailCard.vue`: 计划随图层下发、图层撤下即停(特效随图层消失)、降级时面板显示「异常设备较多(N 台)· 动效已暂停」。 宿主新增只读字段 `data-map-device-effects`(`on|off|degraded`)、`data-map-device-effects-abnormal`、 `data-map-device-effects-animated`。 - `mocks/contractMocks.js`:两个受控样例 —— `device-effects`(五态各一台,坐标各自错开)与 `device-effects-degraded`(31 台报警)。**受控可上图样例**是决策票 07 的硬要求 (真实环境仍是清单-only 时不得从纯逻辑推断现场特效已生效)。 ### 三层证据(决策票 07 问题 7) 1. **领域纯逻辑**:`verify/t47-device-effects.mjs` **20/20** —— 映射唯一、未知不猜、 门控、恰好 30 台不降级/31 台降级、阈值可调、`map.effects` 默认与脏值。 相位函数按时间采样(脉冲 8 个不同取值、闪烁两段、旋转单调推进),**不是拿两份 JS 数据互比**。 2. **场景图跨帧快照**(关键层):`verify/t47-device-effects-walkthrough.mjs` **9/9** —— 8 帧采样下,报警 `scale`/`alpha` 各 8 个不同取值、预警 `alpha` 2 个取值(闪烁两段)、 工单 `rotation` 8 个取值;而**正常与离线全程只有一个取值**(完全静止)。真实鼠标点开点位。 3. **真实帧差**:同一视角两帧截图**字节不同**(走查新增 `cdp-driver.screenshot()`; 字节级判据,未做逐像素解码 —— 无依赖可解 PNG)。 4. **降级场景**:`device-effects-degraded` **6/6** —— `data-map-device-effects=degraded`、 动效台数 0、8 帧全部无动效、异常计数 31 可见、**31 台静态标记仍画着**(不静默删点)。 ### 一处需要维护者确认的解读(我做的判断,已如实标注) 决策票 07 说「同屏**异常设备**超过 30 台时降级」,而它同时把 `WORK_ORDER` 列为有动效的状态。 我按**票 46 的「需处置」判据**(`ALARM`/`WARNING`)计异常台数,而不是按"所有带动效的设备"计 —— 理由是票 08 的现场校准记录正是「23 台异常 ⇒ 已降级、动效台数 0、异常台数 23 仍在」。 若维护者认为该按"带动效台数"计,这是**回票 07 重议**的事,不由本实现自行改。 ### 下一步(未关闭的原因) 请维护者目视裁定三件事:① 脉冲/闪烁/旋转的强度是否合适(当前幅度 0.34、选中 0.55); ② 正常/离线**完全静止**是否够可辨;③ 31 台降级时是否仍一眼读得出「有异常」。 确认后即可关闭本票。 ### 回归 零依赖闸门 **37 道**(含本票新增 t47)全绿;三处红与本票无关: `t11`(并发会话给 `PointDetailCard.vue` 新增了 `ref(`,撞票 10 的"组件不自己存状态"守卫)、 `t15`(并发会话在改快照/推送语义)、`t28`(需 puppeteer,模块不在本机)。 守卫同步更新 2 处(都是本票**有意变更契约**):`t44` 的 F11 原写「动效留给票 47」,现按新契约 允许 `effect` 并钉住取值域与"正常/离线/未知无动效";`t43` 的 F12 正则允许新增的 `effects` 选项。 **按 `AGENTS.md`:本会话未跑 build;`yarn dev` 未由我启动(8000 端口已有服务在跑)。 浏览器仅用于走查(维护者授权的提权通道)。** ## Out of scope - 监测点级扩散环/监测点自身特效仍未被设备五态决策裁定,需另立决策票,不得在本票自行实现;设备图层触发、资产选择、双向联动、重叠聚合、设施/管线/事件图层和后端状态来源改造。