mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 21:11:12 +00:00
[AZ-698] Multi-flight tlog handling: segment first, pick last flight
Real derkachi.tlog covers 3 takeoffs at the same field but the uploaded video covers only the last. Original NCC argmax + AZ-405 head-takeoff fallback both biased toward flight 1, violating the spec's "the last chunk in tlog is relevant" framing. Patch: pre-NCC flight segmenter partitions the IMU energy stream into distinct flights (threshold + gap walk); find_aligned_window restricts NCC search to the last segment; low-confidence fallback uses that segment's start instead of head-takeoff detection. AlignedWindow gains flight_count_detected + selected_flight_index for FDR-visible audit. 7 new unit tests (segmenter shapes + end-to-end multi-flight pipeline + segmented fallback path). 19 AZ-698 tests pass, 113 in the regression slice. Zero new mypy --strict errors. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -164,3 +164,49 @@ Tests:
|
||||
### Algorithm note
|
||||
|
||||
Implementation uses **normalised cross-correlation with per-window unit-norm** (each `len(flow_arr)`-sized slice of the tlog energy stream is zero-meaned + unit-normed before the dot product with the unit-normed flow stream). This makes the peak confidence scale-invariant — a 10 s motion burst inside a 300 s tlog produces a peak ≥ 0.95, where the original FFT-style correlation with full-length normalisation produced ≤ 0.3 and tripped the low-confidence fallback. Cost is O(N·M); with the 10 Hz subsample and a typical 300 s tlog × 10 s flow window, that's ~3 000 inner products — well below the NFR perf budget.
|
||||
|
||||
### Follow-up: multi-flight tlog handling (post-batch-99 review)
|
||||
|
||||
User reported that real `derkachi.tlog` contains **three takeoffs at the same field**, but the uploaded video covers only the **last** one. The original AZ-698 implementation was vulnerable in two places:
|
||||
|
||||
1. NCC `argmax` returns the **first** index of the maximum — if all three flights produce comparable correlation peaks, the result would lock onto flight 1.
|
||||
2. The low-confidence fallback called `detect_tlog_takeoff` on the whole tlog, which is the AZ-405 head-takeoff detector — also locks onto flight 1.
|
||||
|
||||
Both contradicted the spec line 22: *"the last chunk in tlog is relevant"*.
|
||||
|
||||
Resolution: added a pre-NCC flight segmenter and made the aligner explicitly select the last flight before running NCC. The fallback also now uses the last segment's start instead of head-takeoff detection.
|
||||
|
||||
#### New module surface
|
||||
|
||||
- `_segment_flights_from_imu_energy(samples, *, motion_threshold, min_flight_duration_ns, max_internal_gap_ns) -> list[(start_ns, end_ns)]` — partitions the IMU energy stream into distinct flights. A flight is a contiguous span where energy stayed above threshold, with sub-threshold runs (cruise lulls) shorter than `max_internal_gap_ns`. Reused logic note: `_find_sustained_event` (AZ-405) returns only the FIRST qualifying window by design; partitioning all flights needs the fresh one-pass walk.
|
||||
|
||||
#### New config knobs (`AutoSyncConfig` + `ReplayAutoSyncConfig`)
|
||||
|
||||
| Knob | Default | Meaning |
|
||||
|------|---------|---------|
|
||||
| `alignment_segment_motion_threshold_g` | `0.10` | Min IMU energy (`|a| − 1g`, g-units) for a sample to count as in-flight. 0.10 captures cruise oscillation while ignoring stationary sensor noise (~ 0.02). |
|
||||
| `alignment_segment_min_flight_duration_seconds` | `30.0` | Discards short ground-startup blips. |
|
||||
| `alignment_segment_max_internal_gap_seconds` | `30.0` | Sub-threshold gaps shorter than this stay inside a flight. |
|
||||
|
||||
#### New observability fields (`AlignedWindow`)
|
||||
|
||||
- `flight_count_detected: int` — how many distinct flights the segmenter found. `1` for a clean single-flight tlog. `> 1` means multi-flight; the aligner always selected the last one.
|
||||
- `selected_flight_index: int` — zero-based; always `flight_count_detected - 1` when segmentation fired, else `-1` (segmenter found nothing — fall through to whole-tlog NCC, preserving pre-segmentation behaviour for degenerate inputs).
|
||||
|
||||
Surfaced via the `replay.auto_trim.resolved` / `replay.auto_trim.fallback_to_takeoff` log records' `kv` dict so the operator can audit segment selection from the FDR.
|
||||
|
||||
#### New tests
|
||||
|
||||
| Test | Asserts |
|
||||
|------|---------|
|
||||
| `test_segmenter_one_flight_returns_single_span` | Single-flight tlog → 1 segment, correct bounds |
|
||||
| `test_segmenter_three_flights_returns_three_spans_in_order` | 3-flight tlog → 3 segments in chronological order |
|
||||
| `test_segmenter_drops_ground_blip_below_min_duration` | < 30 s motion is filtered out |
|
||||
| `test_segmenter_keeps_brief_cruise_lull_inside_flight` | 3 s mid-flight lull does NOT split a flight |
|
||||
| `test_find_aligned_window_picks_last_flight_for_multi_flight_tlog` | Full `find_aligned_window` pipeline on a 3-flight tlog → resulting window is inside flight 3, not flight 1 or 2 |
|
||||
| `test_align_via_cross_correlation_locks_onto_burst_inside_last_segment` | NCC path locks correctly on a pre-restricted-to-last-flight energy stream |
|
||||
| `test_find_aligned_window_uses_only_segment_for_segmented_tlog_fallback` | Low-confidence fallback uses segment start (last flight), NOT head-takeoff (flight 1) |
|
||||
|
||||
All 19 AZ-698 tests pass, 1 expected skip (AC-5 real-video smoke). 113 tests pass in the broader regression slice — no regressions.
|
||||
|
||||
Backward-compat verified: AC-1 / AC-2 / AC-3 / AC-4 tests exercise `_align_via_cross_correlation` directly and continue to pass; the segmentation gate only fires through `find_aligned_window`'s public entry point.
|
||||
|
||||
@@ -98,6 +98,33 @@ 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.
|
||||
|
||||
## Follow-up commit: multi-flight handling
|
||||
|
||||
User-reported gap during the AZ-698 "In Testing" phase: real
|
||||
`derkachi.tlog` contains **three takeoffs**; the video covers only
|
||||
the last. The original AZ-698 happy path (`np.argmax`) and fallback
|
||||
(`detect_tlog_takeoff` on head) were both biased toward flight 1.
|
||||
|
||||
Patched in a follow-up commit on top of batch 99:
|
||||
|
||||
- New `_segment_flights_from_imu_energy` helper partitions the IMU
|
||||
energy stream into distinct flights using a motion-threshold +
|
||||
gap-tolerance walk.
|
||||
- `find_aligned_window` now restricts NCC search to the **last**
|
||||
detected segment.
|
||||
- Low-confidence fallback uses the last segment's start instead of
|
||||
re-running head-takeoff detection on the whole tlog.
|
||||
- `AlignedWindow` gains `flight_count_detected` + `selected_flight_index`
|
||||
for observability; both are surfaced in the `replay.auto_trim.resolved` /
|
||||
`…fallback_to_takeoff` log records.
|
||||
- New unit tests: segmenter happy paths (1-flight, 3-flight),
|
||||
ground-blip rejection, cruise-lull preservation; integration test
|
||||
proving `find_aligned_window` on a 3-flight tlog picks flight 3.
|
||||
|
||||
Test totals after follow-up: **113 passed, 2 skipped, 0 failed.**
|
||||
Zero new mypy --strict errors (12 errors in scope, all pre-existing
|
||||
and unchanged).
|
||||
|
||||
## Next batch
|
||||
|
||||
Batch 100 — **AZ-699** (real-flight validation runner). Depends on
|
||||
|
||||
Reference in New Issue
Block a user