Why the 'h' look-through camera cards appear ROLLED (Santa Fe vs the Palisades world origin) (2026 St Catherines Fire Santa Fe)

**Note** from Bead: 2026 St Catherines Fire Santa Fe · [canonical source](https://redfish.acequia.io/guerin/.agents/bee1e0b1-9785-41d3-a4fe-ea4174754a55/2026-07-02/notes/03-camera-card-roll-diagnosis.md) · session 2026-07-02 · discussion: Talk: 2026 St Catherines Fire Santa Fe

Stephen loaded [?stac=…/stac/catalog.json](https://redfish.acequia.io/guerin/apps/incident-viewer/?stac=https://simtable.acequia.io/incidents/2026-NM-StCatherinesFire/stac/catalog.json) (works: incident + 2 camera cards render, look-through works) and asked why the camera image planes have a visible roll.

## Diagnosis (all refs [src/incident-viewer/index.ts](c:/Users/steph/Documents/src/incident-viewer/index.ts)) - **The card mesh is level — locally.** `buildCamCard`/`camBasis` (~L2771–2831) build the billboard in ECEF with `up` = the local ellipsoid radial **at the camera's anchor** and `right = dir × up`. Correct. - **The look-through view camera is level — in the WRONG frame.** `camLock` riding (~L1863–1876) drives the view via `controller.yaw/pitch` only — the controller has **no roll DOF**, and its notion of "level" is the engine world frame's **Y axis**. - **The world frame is anchored at the 2025 Palisades sample** (~L53–62, 355–358): `GeoFrame.atLonLat(SAMPLE.sensor_longitude=-118.504, SAMPLE.sensor_latitude=34.056)` — Los Angeles. World-Y = local up **at LA**. - Santa Fe is ~**10.5° of great-circle arc** from LA, so LA-up and Santa-Fe-up differ by ~10.5°. A yaw/pitch camera held level in LA-up, looking at a card held level in Santa-Fe-up, sees the card **banked** — the component of the up-mismatch perpendicular to the look axis reads as roll (0–10.5° depending on look azimuth). That's the tilt in both screenshots. - Why it never showed on Sandy: those cameras are in the LA/Ventura area, ≲1° from the frame origin — sub-degree roll, invisible. St. Catherine's is the first incident loaded ~1,300 km from the hardcoded origin, so the latent bug surfaced. - Same class of problem as the MISB projected-image rotation fix (note-12 comment at ~L1749: "lookRotation constrains only the boresight; roll about it was arbitrary") — epic-5 `84395793` already lists **roll-fix** in its NEXT queue.

## Candidate fixes (viewer code — belongs to epic-5, whose branches are flagged DIVERGED; don't hot-patch without reconciling) 1. **Re-anchor `geo.frame` per loaded incident** (origin ← incident bbox center or first camera): world-Y ≈ local up wherever the incident is; the yaw/pitch controller becomes level-true. Cleanest; ECEF-delta rendering should be indifferent to the origin. 2. **Give the look-through a roll correction** (mirror `imageOrientation`'s technique): project the card's local up ⊥ look, `atan2` against controller-up, bank the view camera by that angle. Scoped to camLock; leaves the controller yaw/pitch elsewhere. 3. Both: (1) fixes the whole scene (terrain-relative horizon too), (2) is the surgical stopgap.

## Also - Frames observed correct: pose az 240.48 (Tesuque) drives the card heading; ±10 min playhead gating works with the 835-item window.