mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 17:21:13 +00:00
[AZ-598] Batch 78: sitl_observer.wait_for_outbound + FT-P-01 fixture builder
Phase 1: extend sitl_observer with cursor-based `wait_for_outbound` returning `OutboundMessage` from `outbound_messages_<fc_kind>_<host>.json` fixtures. Three outcomes: message, TimeoutError (null entries), or RuntimeError (missing/malformed). Fix FT-P-01 + FT-P-05 scenarios to use `fc_kind=` kwarg. Phase 2: FT-P-01 vertical-slice fixture builder under `e2e/fixtures/sitl_replay_builder/`. Reuses the production `gps-denied-replay` CLI + `ReplayInputAdapter`: encode 60 stills as 1 fps MP4 + synthetic stationary tlog (pymavlink); run replay; project FDR outbound estimates into the schema. Avoids the 13+ cp of SUT-side frame-ingestion that a live-SITL-capture path would have required. Live execution remains a manual operator step. +35 unit tests (664 total, up from 637). K=3 cumulative review for b76-b78 documents the offline-replay arc convergence. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# FT-P-01 vertical slice: observer wait_for_outbound + SITL capture builder
|
||||
|
||||
**Task**: AZ-598_ft_p_01_vertical_slice
|
||||
**Complexity**: 5 points
|
||||
**Dependencies**: AZ-594, AZ-595, AZ-596, AZ-597
|
||||
**Component**: Blackbox Tests / Test Infrastructure (epic AZ-262)
|
||||
**Tracker**: AZ-598
|
||||
**Epic**: AZ-262 (E-BBT)
|
||||
|
||||
## Problem
|
||||
|
||||
Batch 78 was scoped as "build the SITL replay fixture builder, vertical
|
||||
slice for FT-P-01". The audit during scoping surfaced two gaps that
|
||||
must be fixed before the builder is meaningful:
|
||||
|
||||
1. FT-P-01 + FT-P-05 call `observer.wait_for_outbound(timeout_s=...)`
|
||||
but `wait_for_outbound` does not exist on `_FdrReplayObserver`.
|
||||
Only the *config-read* surface (`read_gps_state`, `read_*_events`)
|
||||
was implemented in b75.
|
||||
2. FT-P-01 + FT-P-05 call `get_observer(fc_adapter=..., host=...)`
|
||||
but the signature is `get_observer(fc_kind, host)`. Calling with
|
||||
the wrong kwarg name raises `TypeError` before any scenario logic
|
||||
runs.
|
||||
|
||||
Building the capture pipeline without first fixing these would either
|
||||
defer the failure (capture writes a file format the consumer can't
|
||||
read) or commit to a fixture format unilaterally.
|
||||
|
||||
## Strategy
|
||||
|
||||
Two phases in one ticket — both ship together so FT-P-01 is end-to-end
|
||||
executable at batch end.
|
||||
|
||||
### Phase 1 — observer extension (offline-safe)
|
||||
|
||||
* Add `OutboundMessage(lat_deg: float, lon_deg: float)` frozen
|
||||
dataclass to `e2e/runner/helpers/sitl_observer.py`.
|
||||
* Extend `_FdrReplayObserver` with `wait_for_outbound(timeout_s: float | None = None) -> OutboundMessage`.
|
||||
* Replay-mode semantics: cursor-based read from
|
||||
`${E2E_SITL_REPLAY_DIR}/outbound_messages_<fc_kind>_<host>.json`.
|
||||
* Each call advances the cursor by one entry.
|
||||
* `null` entries raise `TimeoutError` (encoding "SUT didn't emit
|
||||
anything for this image during capture").
|
||||
* Cursor past list length raises `RuntimeError`.
|
||||
* `timeout_s` accepted for live-mode parity; ignored in replay.
|
||||
* Fix call sites: `get_observer(fc_adapter=...)` → `get_observer(fc_kind=...)`.
|
||||
|
||||
### Phase 2 — SITL capture builder (FT-P-01)
|
||||
|
||||
* New `e2e/fixtures/sitl_replay_builder/build_p01_fixtures.py`:
|
||||
* Stand up the existing `e2e/docker/docker-compose.test.yml` stack.
|
||||
* For each `AD0000NN.jpg`: push through SUT frame source, wait up
|
||||
to 5 s for SUT's outbound `GPS_INPUT` from the mavproxy listener.
|
||||
* Persist `outbound_messages_<fc_kind>_<host>.json` and
|
||||
`observer_<fc_kind>_<host>.json` to `--output` directory.
|
||||
* Optional docker compose override `docker-compose.sitl-builder.yml`.
|
||||
* Unit tests with mocked docker / mavproxy layer.
|
||||
|
||||
## Fixture Format (`outbound_messages_<fc_kind>_<host>.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"messages": [
|
||||
{"image_id": "AD000001.jpg", "lat_deg": 48.275292, "lon_deg": 37.385220},
|
||||
null,
|
||||
{"image_id": "AD000003.jpg", "lat_deg": 48.275001, "lon_deg": 37.382922}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* `image_id` is optional metadata (diagnostics only).
|
||||
* `null` = timeout (no message captured for this image).
|
||||
* Entries map 1:1 to scenario `wait_for_outbound` calls in order.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1**: `wait_for_outbound()` returns `OutboundMessage(lat_deg, lon_deg)`
|
||||
from the cursor entry.
|
||||
|
||||
**AC-2**: `wait_for_outbound()` raises `TimeoutError` when the cursor
|
||||
entry is `null`.
|
||||
|
||||
**AC-3**: `wait_for_outbound()` raises `RuntimeError` when the cursor
|
||||
exceeds the messages list length.
|
||||
|
||||
**AC-4**: `wait_for_outbound()` raises `RuntimeError` when the fixture
|
||||
file is missing OR malformed.
|
||||
|
||||
**AC-5**: FT-P-01 + FT-P-05 use `get_observer(fc_kind=fc_adapter, ...)`.
|
||||
|
||||
**AC-6**: `build_p01_fixtures.py` writes both fixture files in the
|
||||
documented schema; unit tests verify schema via mock docker
|
||||
interactions.
|
||||
|
||||
**AC-7**: Full e2e unit-test suite passes (regression gate).
|
||||
FT-P-01 + FT-P-05 still skip via `sitl_replay_ready` when env unset.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
* Live capture EXECUTION — will ask user approval before running
|
||||
(docker-heavy).
|
||||
* Other scenarios (FT-P-02 through FT-N-04).
|
||||
* iNav adapter for capture — AP first.
|
||||
|
||||
## Files Touched
|
||||
|
||||
* `e2e/runner/helpers/sitl_observer.py` (extend)
|
||||
* `e2e/_unit_tests/helpers/test_sitl_observer.py` (extend; add
|
||||
`wait_for_outbound` tests)
|
||||
* `e2e/tests/positive/test_ft_p_01_still_image_accuracy.py` (kwarg fix)
|
||||
* `e2e/tests/positive/test_ft_p_05_sat_anchor.py` (kwarg fix)
|
||||
* `e2e/fixtures/sitl_replay_builder/__init__.py` (new)
|
||||
* `e2e/fixtures/sitl_replay_builder/build_p01_fixtures.py` (new)
|
||||
* `e2e/fixtures/sitl_replay_builder/README.md` (new)
|
||||
* `e2e/_unit_tests/fixtures/test_sitl_replay_builder.py` (new)
|
||||
* `e2e/_unit_tests/test_directory_layout.py` (register new paths)
|
||||
* `e2e/docker/docker-compose.sitl-builder.yml` (new, if needed)
|
||||
Reference in New Issue
Block a user