fix: restore live V2X signal and vehicle fidelity in Cesium preview

The ported overlay used the correct REST paths but lost the data handling
from the source dashboard's live-intersection view (HologramCross), so
signals rendered permanently red and vehicles often never appeared.

- Lamp status codes now follow the dashboard dictionary (11/21/22/23/31).
  The previous 2/3 reading made every real push fall through to red. The
  dictionary lives only in the overlay; the preview consumes normalized
  {nodeKeys, color, countDown} entries so the two copies cannot drift.
- Bind V2X phases to native signal heads geometrically. The runtime
  document has no phaseNo, so the old lookup fell back to signal.id and
  never matched, leaving the dynamic assembly dark. Travel heading is
  recovered as faceHeadingDegrees + 180, per the generator's
  mast = travel - 90 / face = travel + 180. Verified 7/7 exact matches
  against the fengshu-er-road runtime document.
- A phase now lights every approach it drives; the phase -> single entity
  map silently overwrote all but the last.
- Drive the countdown assets from the push's countDown field.
- All three sockets heartbeat every 30s and reconnect with backoff,
  replaying their subscription frame. Without this the service dropped
  the connection and the scene emptied after about a minute.
- The OBU socket sends its bounds frame on connect and on camera move;
  it previously sent nothing at all.
- Vehicles are swept when a push goes stale and their slots reused, so
  they no longer accumulate as ghosts. Models follow the dashboard's
  car_obu.glb / ${type}${subType}.glb naming.
- Parse vehicle pushes leniently, since the dashboard uses saferEval and
  the payload is not guaranteed to be strict JSON. Failures are counted
  and surfaced rather than dropped; no eval is introduced.
- Drop FlowTravelRatio/queryListWeek, which is not part of this view.

Also corrects a stale spec rule that required vehicleModelNames to be
empty. Live V2X vehicles need packaged models; the real invariant is no
generated routes or traffic simulation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 09:20:50 +08:00
parent bb2a1449ac
commit bc845444bb
17 changed files with 1900 additions and 110 deletions

View File

@@ -317,27 +317,56 @@ npm run build:area -- --config config/areas/<area>.json --stages cesium
### 2. Signatures
```text
WS /network/ws/network/signal?authorization=<token>
WS /network/ws/network/obuPosition?authorization=<token>
WS /network/ws/network/targetPosition?authorization=<token>
```
OBU 消息使用 `carCode|obuCode``lon``lat``angle``speed`;目标识别消息使用
`data[deviceId][]` 内的 `id``longitude``latitude``type``subType``angle``speed`
两类车辆消息的根部还带 `interval`(推送间隔,`0` 视为 `500`)。
信号灯消息使用 `lamps[]` 内的 `phaseNo``status``countDown`
### 3. Contracts
- 两条流均为 GCJ-02必须在实体创建前恰好调用一次 `gcj02ToWgs84`
- 本预览的 `routeName``vehicleModelName` 必须为 `null``vehicleModelNames` 必须为空;
原生 preview 不得生成 `trafficSimulation` 描述符。
- 两条车辆流均为 GCJ-02必须在实体创建前恰好调用一次 `gcj02ToWgs84`
- 本预览的 `routeName` 必须为 `null`,且不得生成 `trafficSimulation` 描述符。
真正的不变量是**不得生成路线或交通仿真**,而不是「没有车模型」——
实时 V2X 车辆需要打包的车模型才能渲染,故 `vehicleModelNames`
`writeVehicleModel(area)` 正常写出(`build-area.js:writeCesiumPreview`
`vehicleModelName` 取其首项。早期版本靠清空模型列表来阻止仿真车辆,
该机制已不适用,不要再用它作为约束手段。
- 每辆实时车保留最多 24 个已转换的位置作为实际轨迹;轨迹不是推测路径。
- **灯色码字典只有一份**,取自源看板 `HologramCross/components/utils.ts`
`11=灭 21=红 22=黄 23=绿 31=其他`,未知码归入「其他」,**不得**落到红色。
overlay 归一化为 `{nodeKeys, color, countDown}` 后单向传给 `cesium-preview.js`
预览层不得自带第二份字典。
- **三条 socket 必须发心跳** `{"heartBeat":"ping"}` / 30000ms并在断线后退避重连
且**重连后重放订阅帧**signal 发 `junctionId`obu 发 boundstarget 发 `deviceId`)。
缺心跳会被服务端断开,画面在一分钟后静默变空。
- **相位到原生灯头靠几何绑定**`traffic-signals.json``phaseNo`,用 link 末段停止线点与
航向匹配原生 `stopLongitude/stopLatitude` 与行车方向;行车方向 =
`faceHeadingDegrees + 180`(生成侧 `scripts/lib/traffic-signals.js` 定义
`mast = travel - 90``face = travel + 180`)。
一相位可点亮多条进口道,一条 link 可点亮同进口道多个灯头,两侧都取并集。
未绑定相位须计数上报,可用 `v2xPreview.phaseSignalMap` 显式覆盖。
- **车辆生命周期**:超过 `interval * 1.5` 未更新即隐藏(不删除),隐藏项作为同模型的可复用槽位;
socket 断开清空车辆。模型名沿用看板语义OBU 为 `car_obu.glb`,目标车为 `${type}${subType}.glb`
- 车辆推送体解析必须容忍非严格 JSON源看板用 `saferEval`),失败须计数并在面板可见,
**不得**静默丢弃;实现不得使用 `eval` / `new Function`
- 周流量比 `FlowTravelRatio/queryListWeek` **不属于**实时路口范围。
### 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| 未登录、令牌失效、REST/WS 不可用 | 静态路口继续显示,车辆层为空,并显示实时数据不可用状态 |
| 消息不是 JSON、心跳、坐标无效 | 忽略该消息,不创建车辆 |
| 消息不是 JSON、心跳、坐标无效 | 忽略该消息,不创建车辆;非心跳的解析失败须计数 |
| 收到有效车辆坐标 | 创建或更新真实车辆与实际轨迹 |
| 车辆超过 `interval * 1.5` 未更新 | 隐藏该车并保留槽位,不得堆积幽灵车 |
| 相机视野变化 | 节流后向 obu socket 发送 GCJ-02 四角 `bounds` |
| 视野矩形不可用 | 发空帧(等于不过滤),不得跳过发送导致空场景 |
| 相位一个都没绑定 | 面板显性提示并建议配置 `phaseSignalMap` |
### 5. Good / Base / Bad Cases
@@ -347,11 +376,15 @@ OBU 消息使用 `carCode|obuCode`、`lon`、`lat`、`angle`、`speed`;目标
### 6. Tests Required
- `npm run test:v2x-cesium-preview`:校验两类消息解析与 GCJ-02 转换。
- `npm run test:preview-assets`:断言两条 WebSocket 存在,且 runtime 不调用
`addVehicleCruises`、不显示 simulation 诊断
- `npm run test:v2x-cesium-preview`:校验灯色字典(含 `2`/`3` 不再是黄/绿的回归断言)、
宽松解析、几何相位绑定(对真实 `traffic-signals.json` 须达成精确一对一)、
同相位多进口道并集、车辆超时与槽位复用、心跳间隔与重连重放订阅帧、bounds 报文
- `npm run test:preview-assets`:断言三条 WebSocket 存在runtime 不调用
`addVehicleCruises`、不显示 simulation 诊断,且预览层**不再自带** `lampColorName`
`Number(status) === 3` 判断。
- 对目标区域运行 `npm run build:area -- --config config/areas/<area>.json --stages preview`
检查 descriptor 中 route/vehicle 字段为空,且不存在 traffic-simulation 文件
检查 descriptor 中 `routeName` 为空、不存在 traffic-simulation 文件
`vehicleModelNames` 已写出(实时车辆渲染需要)。
### 7. Wrong vs Correct