[AZ-698] Tlog trim + mid-flight alignment for replay

Adds find_aligned_window cross-correlation (NCC, per-window unit norm)
between IMU energy and video optical-flow magnitude. Returns
AlignedWindow{tlog_start_ns, tlog_end_ns, offset_ms, confidence,
used_fallback}, with fallback to head-takeoff on low confidence to
preserve AZ-405 behavior. TlogReplayFcAdapter honors tlog_start_ns and
skips pre-window messages. New --auto-trim CLI flag, mutex with
--time-offset-ms. AC-1..AC-4 covered by unit tests; AC-5 skipped (no
real flight_derkachi.mp4 in repo). 106 tests pass in regression slice.
Zero new mypy --strict errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-20 16:29:59 +03:00
parent 64d961f60c
commit 87fe98858f
13 changed files with 1360 additions and 7 deletions
@@ -0,0 +1,104 @@
# Batch 99 — Cycle 2 — AZ-698
**Date**: 2026-05-20
**Tasks**: AZ-698 (Tlog trim + mid-flight alignment for replay).
**Story points**: 5.
**Jira status**: AZ-698 → `In Testing`.
## What shipped
A normalised-cross-correlation aligner that finds the video's playback window
inside a longer tlog, plus the plumbing to honor that window across the
replay-mode composition root, replay coordinator, replay-side FC adapter,
config schema, and CLI.
- `find_aligned_window(tlog_path, video_path, config, ...) -> AlignedWindow`
in `src/gps_denied_onboard/replay_input/auto_sync.py`. Returns
`(tlog_start_ns, tlog_end_ns, offset_ms, confidence, used_fallback)`.
- `AlignedWindow` DTO + `auto_trim` flag + `alignment_*` knobs on
`ReplayConfig` / `ReplayAutoSyncConfig`.
- `TlogReplayFcAdapter` skips messages with `_timestamp < tlog_start_ns`
when seeded (`AC-3`).
- `--auto-trim` CLI flag on `gps-denied-replay`, mutually exclusive with
`--time-offset-ms`.
## Files changed
Production (8):
- `src/gps_denied_onboard/replay_input/interface.py`
- `src/gps_denied_onboard/replay_input/auto_sync.py`
- `src/gps_denied_onboard/replay_input/tlog_video_adapter.py`
- `src/gps_denied_onboard/replay_input/__init__.py` (re-export `AlignedWindow`)
- `src/gps_denied_onboard/components/c8_fc_adapter/tlog_replay_adapter.py`
- `src/gps_denied_onboard/config/schema.py`
- `src/gps_denied_onboard/config/loader.py`
- `src/gps_denied_onboard/runtime_root/_replay_branch.py`
- `src/gps_denied_onboard/cli/replay.py`
Tests (1 new):
- `tests/unit/replay_input/test_az698_window_alignment.py`
Specs:
- `_docs/02_tasks/done/AZ-698_tlog_trim_midflight_alignment.md` (moved from
`todo/`, completion notes appended).
## AC coverage
| AC | Test | Result |
| ---- | -------------------------------------------------------------------------- | ------- |
| AC-1 | `test_ac1_takeoff_aligned_offset_matches_az405_within_50ms` | PASS |
| AC-2 | `test_ac2_mid_flight_alignment_locates_correct_window` | PASS |
| AC-3 | `test_ac3_adapter_seek_skips_pre_window_messages`, `_default_no_seek_*` | PASS |
| AC-4 | `test_ac4_validator_passes_for_takeoff_aligned_offset`, `_mid_flight_*` | PASS |
| AC-5 | `test_ac5_cli_auto_trim_smoke_uses_find_aligned_window` | SKIPPED |
AC-5 skip reason: the repo's `flight_derkachi.mp4` is a 134-byte placeholder,
not a real recording. Live CLI smoke is covered by AZ-699 (validation
runner) once the real video is sourced.
## Test run
```
tests/unit/replay_input/test_az698_window_alignment.py 12 PASS 1 SKIP
tests/unit/replay_input/test_az405_auto_sync.py 14 PASS
tests/unit/replay_input/test_az405_replay_input_adapter.py 13 PASS
tests/unit/c8_fc_adapter/test_az399_tlog_replay_adapter.py 24 PASS 1 SKIP
tests/unit/replay_input/test_tlog_ground_truth.py 12 PASS
tests/unit/test_az697_gps_compare.py 10 PASS
tests/unit/calibration/test_khp20s30_factory.py 9 PASS
tests/unit/runtime_root/test_az687_pre_constructed_replay_mode.py 3 PASS
tests/unit/test_az269_config_loader.py 9 PASS
```
Totals: **106 passed, 2 skipped, 0 failed.** No regressions in adjacent
suites.
## Strict typing
Baseline (pre-batch, by stash-and-rerun): 17 `mypy --strict` errors across
6 of the 8 touched `src/` files. After batch: 17 errors — same count,
same kinds, with line numbers shifted only by added code. **Zero new
strict-typing errors introduced by AZ-698.** Pre-existing errors are
out-of-scope per `coderule.mdc` ("Pre-existing lint errors should only be
fixed if they're in the modified area" — they were not in the bytes
modified for AZ-698 ACs).
The new public symbols (`find_aligned_window`, `AlignedWindow`,
`_zero_mean_normalise`, `_resample_uniform`) carry explicit
`npt.NDArray[np.float64]` annotations so they don't add to the debt.
## Code review verdict
Inline self-review: code paths cover the spec's scope, fallback to
head-takeoff on low confidence preserves AZ-405 behavior, adapter seek is
opt-in via constructor kwarg so the `--skip-auto-sync` path is untouched.
The normalised-cross-correlation switch is documented in the spec's
"Implementation Notes" appendix as the algorithmic decision of record.
## Next batch
Batch 100 — **AZ-699** (real-flight validation runner). Depends on
AZ-697 (ground truth) and AZ-698 (alignment) — both now in testing.