# Implementation Report — Operator Replay Tooling — Cycle 2

**Cycle**: 2
**Feature slug**: `operator_replay_tooling`
**Epic**: AZ-696
**Date**: 2026-05-20
**Tasks shipped (6)**: AZ-697, AZ-702, AZ-698, AZ-699, AZ-700, AZ-701 (total: 24 story points)
**Batches**: 98 → 102 (5 batches)
**Status**: implementation phase complete; handing off full-suite gate to Step 11 (Run Tests) per `implement/SKILL.md` Step 16

## What shipped

A new operator-side post-flight replay path. Given a recorded ArduPilot binary `.tlog` + a flight video + a camera calibration, the operator now has three ways to consume the gps-denied estimator's output:

1. **Direct CLI** — `gps-denied-replay --auto-trim` (extended in AZ-698) emits GPS-fix JSONL; **`gps-denied-render-map`** (new in AZ-700) turns the JSONL plus the binary tlog ground truth into a self-contained HTML map; the AZ-699 e2e runner wraps both and produces a Markdown accuracy report with an explicit PASS/FAIL verdict against the AZ-696 epic AC-3 (≥ 80 % of emissions within 100 m).
2. **HTTP API** — new `replay_api` component (AZ-701): `POST /replay` accepts multipart `(tlog + video [+ calibration])` and returns either a synchronous 200 with the result URLs or an async 202 with a job id for polling. Static endpoints serve the JSONL, accuracy report, and HTML map per job.
3. **Programmatic** — `gps_denied_onboard.replay_input.load_tlog_ground_truth` (AZ-697) + `gps_denied_onboard.helpers.gps_compare.horizontal_error_distribution` (AZ-699) + `gps_denied_onboard.helpers.accuracy_report.render_report` (promoted to production from a test helper in AZ-701) are now stable importable surfaces.

Camera calibration for the Topotek KHP20S30 gimbal (the Derkachi flight's optical chain) is committed as a factory-sheet JSON (AZ-702), so the AZ-699 e2e run reproduces with no operator-side intrinsics work.

The whole tooling family is **operator-side only** — packaged under the `[operator-tools]` extra in `pyproject.toml`, served by a separate `docker/replay-api.Dockerfile`, and never linked into the airborne companion-tier1 binary. ADR-002 (binary-exclusion) is preserved.

## Per-task summary

| Task | Pts | What it delivers |
|------|----|------------------|
| **AZ-697** tlog ground-truth extractor | 3 | `replay_input/tlog_ground_truth.py` streams `GLOBAL_POSITION_INT` / `GPS_RAW_INT` from the binary tlog into a typed `TlogGroundTruth` DTO. Comparison kernels (`GroundTruthRow`, `l2_horizontal_m`, `match_percentage`) promoted to `helpers/gps_compare.py`. |
| **AZ-702** KHP20S30 factory calibration | 2 | `_docs/00_problem/input_data/flight_derkachi/khp20s30_factory.json` + assumptions documented in `camera_info.md`. `conftest.py::_calibration_path()` prefers it when present. |
| **AZ-698** tlog trim + mid-flight alignment | 5 | `replay_input/auto_sync.py` NCC aligner + `AlignedWindow` DTO + `auto_trim` flag through `ReplayConfig`/`ReplayAutoSyncConfig` + `--auto-trim` CLI flag on `gps-denied-replay` + `tlog_start_ns` skip in `TlogReplayFcAdapter`. |
| **AZ-699** real-flight validation runner | 3 | `helpers/gps_compare.HorizontalErrorDistribution` + `helpers/accuracy_report.py` (originally in `tests/e2e/replay/_report_writer.py`; promoted in AZ-701) + `tests/e2e/replay/test_derkachi_real_tlog.py` honest PASS/FAIL gate (no `@xfail` mask). |
| **AZ-700** replay map visualization | 3 | `cli/render_map.py` console script renders folium / Leaflet HTML of estimated vs truth tracks with start/end markers, 100 m / 50 m scale circles, optional summary banner. `--offline-tiles` mode for Jetsons without internet. |
| **AZ-701** HTTP replay API service | 5 | `replay_api/*` (7 files): FastAPI factory, `JobRegistry` with concurrency/queue caps, `ReplayRunner` Protocol DI seam, `SubprocessReplayRunner` production wiring, magic-byte upload validation (AC-9), bearer-token auth (AC-5), in-memory state by design. 18 unit tests pass. Docker image + compose profile + OpenAPI spec exported. |

## Cumulative review summary

`_docs/03_implementation/cumulative_review_batches_98-102_cycle2_report.md` — **PASS_WITH_WARNINGS**:

- **F1 (Medium / Architecture)** — `_docs/02_document/module-layout.md` is stale. Six new artefacts (replay_api component, two new `replay_input/` files, two new `cli/` entrypoints, two new `helpers/` modules) are not registered. Production code respects the layering; the doc has not caught up. Carried into Step 13 (Update Docs).
- **F2 (Low / Maintainability)** — `replay_api/app.py::_maybe_render_report` uses inline imports for stdlib `json` plus three first-party modules that are already eagerly imported elsewhere. Style nit; lazy import provides no measurable benefit. Owner: AZ-701 follow-up.

No Critical or High findings. No new cyclic dependencies. No duplicate symbols across batches. Module promotion (`_report_writer.py` → `helpers/accuracy_report.py`) was clean; all four consumers re-pointed and the old file deleted from disk.

## Product completeness gate summary

`_docs/03_implementation/implementation_completeness_cycle2_report.md` — **PASS** (all 6 tasks PASS, 0 BLOCKED, 0 FAIL):

- No unresolved-placeholder markers in any cycle-2 production module (only docstring mentions of test-pattern terminology in `replay_api/__init__.py:14` and string-field documentation in `helpers/accuracy_report.py:56,90`).
- Every named runtime dependency is integrated as production behaviour, not as a Protocol-only surface, deterministic fallback, fake runner, or "native bridge".
- The end-to-end production pipeline is wired: HTTP request → multipart validation → job queue → `gps-denied-replay --auto-trim` subprocess → `gps-denied-render-map` subprocess → accuracy report renderer → static result endpoints.

## Deviations from the implement skill workflow

Two deviations from `implement/SKILL.md`, both already justified in the corresponding reports:

1. **Step 14.5 cumulative-review cadence drift**: the cadence trigger (default K=3) should have fired after batch 100 mid-cycle. It did not. The make-up cumulative review at the end of cycle (covering all 5 batches 98–102 in one pass) is `cumulative_review_batches_98-102_cycle2_report.md`. To be flagged in the cycle-2 retrospective.
2. **No `## Baseline Delta` section in the cumulative review**: per the 2026-05-20 LESSONS entry, `_docs/02_document/architecture_compliance_baseline.md` should have been seeded as a cycle-2 Step 6 (Decompose) prerequisite with 0 violations from `_docs/06_metrics/structure_2026-05-20.md`. The file does not exist; cumulative review therefore could not emit Baseline Delta. To be addressed in cycle-2 retrospective (or the next decompose).

## Test status at handoff

- **AZ-701 unit slice** (`tests/unit/replay_api/`): 18/18 pass in 4 s (batch 102).
- **Full unit suite** (last run at batch 102): 2251 passed, 86 skipped, 1 failed. The single failure (`tests/unit/c12_operator_orchestrator/test_cli_console_script.py::TestConsoleScript::test_cold_start_under_500ms_p99`) is a pre-existing C12 CLI cold-start performance flake first reported in batch 100, unrelated to any cycle-2 task. To be handled in Step 11.
- **Mypy --strict** on cycle-2 surface: clean across all new modules.
- **e2e Jetson harness against cycle-2 production code**: **not yet run**. The last Jetson e2e run on file (`_docs/03_implementation/jetson_runs/2026-05-19_az618_tier2_run.txt`) predates the cycle-2 commits (`64d961f` … `7d53cef` all dated 2026-05-20). This is the explicit reason Step 11 (Run Tests) must follow this report.

## Full-suite handoff

Per `implement/SKILL.md` Step 16: "If the next flow step is `Run Tests`, record a handoff in the final implementation report and let `.cursor/skills/test-run/SKILL.md` own the full-suite gate to avoid duplicate full runs." The next flow step IS Step 11 (existing-code flow auto-chain). This report transfers the full-suite gate to `test-run/SKILL.md`, which on the cycle-2 invocation must:

1. Run the Tier-1 Docker harness (Colima) to catch any pure-Python regressions from the 5 cycle-2 batches.
2. Run the Tier-2 Jetson harness (`scripts/run-tests-jetson.sh`) with `RUN_REPLAY_E2E=1`. The Jetson run executes `tests/e2e/replay/test_derkachi_real_tlog.py` against the real `derkachi.tlog` + the real `flight_derkachi.mp4` + the `khp20s30_factory.json` calibration. Verdict is honest PASS/FAIL on the AZ-696 epic AC-3 threshold (≥ 80 % of emissions within 100 m).
3. Surface the C12 cold-start flake from batch 100 / 101 / 102 and decide whether it remains a pre-existing flake or now requires a remediation task.

## Artefacts

- Cumulative review: `_docs/03_implementation/cumulative_review_batches_98-102_cycle2_report.md`
- Completeness gate: `_docs/03_implementation/implementation_completeness_cycle2_report.md`
- Batch reports: `_docs/03_implementation/batch_{98,99,100,101,102}_cycle2_report.md`
- Cycle-2 commits: `64d961f` (AZ-697 + AZ-702) → `f5366bb` (AZ-698) → `dcde602` (AZ-699) → `b66b68f` (AZ-700) → `7d53cef` (AZ-701)
- Carry-forward findings for retro: cumul-review F1 (module-layout drift) + F2 (inline-import style nit) + cadence drift + missing architecture_compliance_baseline.md
