# 还原 V2X 实时路口信号灯与车辆展示
## Goal
把 `v2x_web` dashboard「实时路口」(`HologramCross`) 的实时数据行为完整搬到本项目的 Cesium 预览中,使
`outputs//…-cesium-preview.html` 在登录后能持续、正确地显示 **信号灯相位灯色与倒计时** 和
**OBU / 目标识别车辆**。
当前实现(Codex 移植)接口地址是对的,但数据处理与渲染层基本失真,实际效果是:灯永远红、车可能一辆都不出现、
出现后也不消失、连接几十秒后被服务端断开。本任务修复这条链路。
## Source of Truth
参照实现(只读,不修改):
`/Users/que01/Project/v2x_web/applications/dashboard/src/views/dashboard/components/Content/Map/HologramCross/`
- `components/CrossTrafficLights3D.vue` — 信号灯:link 相位、WS 订阅、灯色/倒计时
- `components/utils.ts` — 灯色状态字典、航向角计算
- `components/CrossCars/hooks/useObuCars.ts` — OBU 车辆
- `components/CrossCars/hooks/useTargetCars.ts` — 目标识别车辆
- `components/CrossCars/type.ts` — 车辆数据结构
## Requirements
### R1 WebSocket 连接层对齐
- 三个 socket(`signal` / `obuPosition` / `targetPosition`)均按源项目发送心跳
`{"heartBeat":"ping"}`,间隔 30000ms。
- 断线自动重连,重连后重新发送各自的订阅消息(signal 发 `junctionId`,target 发 `deviceId`,obu 发 bounds)。
- 页面 `visibilitychange` 回到可见时行为不得导致重复连接堆积;`dispose()` 必须清掉心跳与重连计时器。
### R2 信号灯灯色映射修正
- 采用源项目 `utils.ts` 的状态字典:`11=灭灯 21=红 22=黄 23=绿 31=其他`。
- 现有 `lampColor()`(overlay)与 `lampColorName()`(`cesium-preview.js`)中基于 `2`/`3` 的判断必须移除。
- 未知状态码按「其他/灭灯」处理,不得静默落到红色。
### R3 相位到原生信号灯的映射
- `traffic-signals.json` 中不存在 V2X `phaseNo`(只有 `phaseGroup: 0`),当前回退到 `signal.id` 导致永不匹配。
必须建立一条真实可用的 V2X `phaseNo` → 原生信号灯头的映射。
- 映射结果必须可诊断:面板要能显示「N 个相位已绑定 / M 个未绑定」。
- 允许区域配置提供显式覆盖映射。
### R4 一个相位可点亮多条进口道
- 同一 `phaseNo` 对应的所有 link / 信号灯头都要被点亮,当前 `Map` 的覆盖行为必须消除。
### R5 倒计时
- 使用推送中的 `countDown` 字段(源项目 `phaseValue['time'] = signal.countDown`),驱动预览中已有的
countdown 资产或等效显示。
### R6 车辆订阅正确建立
- OBU socket 连接后必须发送订阅消息(源项目 `send(bounds || '')`),相机视野变化时发送
`{"bounds": ""}`。
- target socket 连接后发送 `{"deviceId": ""}`,无配置时发送 `{"deviceId": null}`。
### R7 车辆生命周期
- 按源项目实现超时隐藏:超过 `interval * 1.5` 未更新的车辆隐藏并可复用槽位;`interval` 取自推送体,
`0` 时回退 `500`。
- socket 断开时清空车辆列表。
- 车辆位置更新使用推送间隔做平滑插值,避免逐帧跳变。
### R8 车辆模型命名对齐
- OBU 固定使用 `car_obu.glb` 语义的模型槽;目标识别车辆使用 `${type}${subType}.glb` 语义的模型槽。
- 包内缺少对应模型时回退到默认车模型,并在面板提示,不得静默显示错误车型。
### R9 推送体解析健壮性
- 源项目对两个车辆 socket 使用 `saferEval` 而非 `JSON.parse`,说明载荷不保证严格 JSON。
- 解析必须容忍非严格 JSON,且解析失败要计数并在面板可见,不得整条静默丢弃。
### R10 范围纠正
- `/facilities/api/FlowTravelRatio/queryListWeek`(周流量比)不属于「实时路口」,从实时加载链路移除。
## Non-Goals
- 不移植 AMap 底图 / 矢量瓦片图层(`linkVectorHdMapTileUrl`)。
- 不移植 `CrossDevices/Model3D.vue` 的 3D 杆件模型;设备维持点位显示(可后续独立任务)。
- 不移植 `useV2XEvents` 的 V2X 事件气泡。
- 不改变原生包产物(GLB / metadata / traffic-signals.json 的生成逻辑)。
## Constraints
- 不得提交上游地址、账号、令牌、AMap key;`v2xPreview` 配置保持非敏感。
- 令牌只存 `sessionStorage`,页面始终从登录门进入。
- 坐标契约不变:V2X 数据 GCJ-02,进 Cesium 前转一次 WGS84;原生数据不得二次转换。
- `v2xPreview.enabled` 默认 `false`,关闭时生成页不含 V2X 面板。
- 生成的预览为静态页,无构建步骤,overlay 保持零依赖 IIFE。
## Acceptance Criteria
- [ ] AC1 三个 socket 均可见 30s 心跳;模拟服务端在 60s+ 后连接仍存活。
- [ ] AC2 单测覆盖灯色映射:`21→红 22→黄 23→绿 11→灭 31→其他`,且 `2`/`3` 不再被当作黄/绿。
- [ ] AC3 单测覆盖相位映射:给定 V2X link 相位与 `traffic-signals.json`,输出的绑定数 > 0,
未绑定项被明确列出。
- [ ] AC4 单测覆盖同相位多进口道:一个 `phaseNo` 对应 2 条 link 时,两条都被点亮。
- [ ] AC5 单测覆盖车辆超时:注入两次推送并推进时间超过 `interval*1.5`,车辆转为不可见。
- [ ] AC6 单测覆盖 OBU/target 订阅消息:连接后发出的首帧内容与源项目一致。
- [ ] AC7 单测覆盖非严格 JSON 推送体能被解析,且失败计数可读。
- [ ] AC8 `npm run test:v2x-cesium-preview`、`test:v2x-preview-server`、`test:preview-assets` 全绿。
- [ ] AC9 真机(`npm run serve:v2x-preview` 接真实上游)登录后可见:信号灯随相位变色并显示倒计时、
车辆出现并移动、离开后消失。此项由开发者人工确认。
- [ ] AC10 `v2xPreview.enabled=false` 重新生成后,页面不含 V2X 面板,包产物字节不变。