# Replay: CSV-driven IMU+GPS adapter using single canonical clock

**Task**: AZ-894_csv_driven_replay_adapter
**Name**: Add a CSV-replay adapter that consumes the Derkachi-schema `data_imu.csv` (or any flight that ships with a paired CSV) and exposes IMU + GPS-ground-truth on a single canonical clock derived from the CSV's `Time` column
**Description**: Cycle 3 surfaced AZ-848 (eskf_out_of_order on frame 3) because the current replay pipeline imports two incompatible clocks: `VioOutput.emitted_at_ns` uses Jetson process-monotonic time, while `ImuWindow.ts_end_ns` uses FC-boot-relative time (parsed from MAVLink tlog messages). The single-clock model that production uses (single edge device, single clock at receipt) is not what replay does today. The Derkachi fixture's `data_imu.csv` already contains both IMU (`SCALED_IMU2.*`) and GPS ground truth (`GLOBAL_POSITION_INT.*`) on a single canonical clock (the `Time` column, 0..489.9 s at 10 Hz, aligned 3:1 with the 30 fps video). Using the CSV directly eliminates the clock-mismatch surface entirely for the test/demo path and matches the production single-clock model.

**Complexity**: 3 SP
**Dependencies**: AZ-896 (format docs land in the same cycle but can land in either order)
**Blocks**: AZ-895 (auto-sync deprecation), AZ-897 (replay UI)
**Component**: replay_input (new adapter), c8_fc_adapter (alternate ground-truth source), cli/replay
**Tracker**: AZ-894 (https://denyspopov.atlassian.net/browse/AZ-894)
**Parent Epic**: (none — cycle-4 replay-input redesign)

## Schema

The Derkachi CSV header (19 columns):

```
timestamp(ms), Time,
SCALED_IMU2.xacc, SCALED_IMU2.yacc, SCALED_IMU2.zacc,
SCALED_IMU2.xgyro, SCALED_IMU2.ygyro, SCALED_IMU2.zgyro,
SCALED_IMU2.xmag, SCALED_IMU2.ymag, SCALED_IMU2.zmag,
GLOBAL_POSITION_INT.lat, GLOBAL_POSITION_INT.lon, GLOBAL_POSITION_INT.alt,
GLOBAL_POSITION_INT.relative_alt,
GLOBAL_POSITION_INT.vx, GLOBAL_POSITION_INT.vy, GLOBAL_POSITION_INT.vz,
GLOBAL_POSITION_INT.hdg
```

- `timestamp(ms)`: FC-boot-relative milliseconds (kept for traceability; not used by C5)
- `Time`: flight-relative seconds (canonical clock — what C5 actually uses)
- `SCALED_IMU2.*`: 10 Hz IMU stream (accel mg, gyro mrad/s, mag mGauss per ArduPilot convention)
- `GLOBAL_POSITION_INT.*`: 10 Hz GPS ground truth (lat/lon in 1e-7 deg, alt in mm, vx/vy/vz in cm/s, hdg in cdeg)

## Acceptance Criteria

- **AC-1**: Adapter parses the Derkachi `data_imu.csv` end-to-end and emits 4,899 IMU samples + 4,899 GPS-ground-truth samples on a single monotonic clock anchored at row 0.
- **AC-2**: Wired into `cli/replay.py`; `gps-denied-replay --video flight_derkachi.mp4 --imu data_imu.csv` runs without invoking `tlog_replay_adapter.py`.
- **AC-3**: `test_derkachi_1min.py::test_ac1_exits_0_jsonl_count_match` passes on the Jetson e2e harness using the new path. AZ-848 cascade no longer triggers (no two-clock surface in the new path).
- **AC-4**: `VioOutput.emitted_at_ns` is populated from the CSV's `Time` column (or the frame-derived `t = N/fps`), not `time.monotonic_ns()`, when the new adapter is in use.
- **AC-5**: Schema mismatch (missing required column, NaN in `Time`, non-monotonic `Time`) raises a clear `ReplayInputAdapterError` at startup, not deep in the loop.

## Out of scope

- The structural AZ-848 / AZ-883 fix in the tlog adapter — those stay open as backlog.
- UI for picking the CSV — AZ-897.
- Other CSV schemas (PX4, generic MAVLink dumps) — future enhancement if needed.

## References

- Cycle-3 retro: `_docs/06_metrics/retro_2026-05-26.md`
- Bench-run evidence: `_docs/04_release/release_cycle3_jetson-bench_2026-05-26-1442.md`
- Companion tickets: AZ-895 (deprecate auto-sync), AZ-896 (format docs + example CSV), AZ-897 (replay UI)
- Supersedes (re bench-blocking): AZ-848 (VioOutput contract), AZ-883 (SCALED_IMU2 ts_ns=0)
