Every pack sets headingConfidence on each camera, but nothing consumed it:
a bearing synthesized by fallbackHeadingFromId (a hash of the id string,
no geographic input) rendered exactly like a surveyed one — 2,827 of 3,997
cameras in a default catalog. Fixes#639.
Two consumers, plus a passthrough:
- HUD: the heading token is tagged when the bearing is synthetic —
`HDG 194° (ESTIMATED)` — so a hash never reads as a measured facing.
- Wireframe: a low-confidence camera draws its coverage frustum with dashed
polylines instead of solid ones. Colors, widths, and the active/idle
emphasis are unchanged; the dash is the only signal.
- Public camera state gains `headingConfidence` (the raw pack flag) and
`headingEstimated` (the derived bit) so UI consumers never re-derive it.
A human-vouched pose always wins over the pack flag: a manually saved
calibration (calSource:'manual') or a curated catalog entry
(poseSource:'curated') is never presented as estimated, mirroring the CAL
badge states in calibration.js.
- src/layers/cctv/headingConfidence.js: pure helpers (isHeadingEstimated,
headingHudToken)
- src/layers/cctv/headingConfidence.test.mjs: confidence normalization,
calibration/curation overrides, HUD token formatting
- src/layers/cctv/presentation.js: HUD token + public-state passthrough
- src/layers/cctv/rendering.js: dashed materials for estimated bearings