[AZ-420] Batch 81: FT-P-12 + FT-P-13 GCS scenarios

FT-P-12: parse mavproxy-listener tlog over a 60 s Derkachi replay and
assert SUT->GCS GLOBAL_POSITION_INT cadence lands in [1, 2] Hz (AC-6.1).

FT-P-13: inject `RELOC:<lat>,<lon>,<radius_m>` STATUSTEXT while the SUT
is in dead_reckoned; verify FDR `c8.gcs.operator_command` ack <=2s,
`anchor_search_region` centre shifts toward the hint, and no
BAD_SIGNATURE / UNAUTHORIZED / REJECTED STATUSTEXT lands in the
post-inject window (AC-6.2).

Adds runner.helpers.gcs_telemetry_evaluator (rate, hint-ack correlation,
haversine search-region shift, rejection scan) and
sitl_observer.capture_gcs_tlog (parity surface to capture_ap_tlog).
Pure-logic coverage: 39 new unit tests; full e2e/_unit_tests/ suite
746 passing (was 700). Scenarios skip locally on missing SITL replay
fixture; production hooks (inbound STATUSTEXT parser, anchor_search_region
FDR emitter) tracked outside this task.

See _docs/03_implementation/batch_81_report.md +
reviews/batch_81_review.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-17 14:46:08 +03:00
parent 7fb3cb3f34
commit bb744d9078
10 changed files with 1777 additions and 3 deletions
+31
View File
@@ -29,6 +29,8 @@ Fixture file naming (under `${E2E_SITL_REPLAY_DIR}/`):
{messages: [{image_id?, lat_deg, lon_deg} | null, ...]}
* `ap_parameters_<host>.json` — {<param_name>: <value>, ...}
* `ap_tlog_<host>.tlog` — raw mavproxy tlog (any binary content)
* `gcs_tlog_<host>.tlog` — raw mavproxy-listener tlog from the GCS link
(SUT→GCS summary stream + GCS→SUT operator commands; FT-P-12, FT-P-13)
* `inav_handshake_<host>.json` — {established_within_s: float | None}
* `inav_msp_frames_<host>.json` — {frames: [...], expected_num_sat: int}
* `inav_gps_state_<host>.json` — {fix_type, num_sat, provider}
@@ -418,6 +420,35 @@ def capture_ap_tlog(host: str, duration_s: float) -> Path:
return path
def capture_gcs_tlog(host: str, duration_s: float) -> Path:
"""Return the path to the GCS-side mavproxy-listener tlog for ``host``.
Fixture: ``${E2E_SITL_REPLAY_DIR}/gcs_tlog_<host>.tlog``. The tlog
captures both directions over the QGC GCS link — SUT→GCS summary
bursts (``GLOBAL_POSITION_INT`` + ``NAMED_VALUE_FLOAT``) and
GCS→SUT operator commands (``STATUSTEXT`` reloc-hints,
``COMMAND_LONG`` parameter reads, etc.).
``duration_s`` is recorded for future live-mode use but ignored here
— under FDR-replay the fixture file IS the captured stream.
Raises ``RuntimeError`` if env var unset or fixture missing.
"""
if duration_s <= 0:
raise RuntimeError(f"capture_gcs_tlog: duration_s must be positive; got {duration_s}")
root = replay_dir()
if root is None:
raise RuntimeError(
f"capture_gcs_tlog: {_ENV_VAR} env var not set"
)
path = root / f"gcs_tlog_{host}.tlog"
if not path.exists():
raise RuntimeError(
f"capture_gcs_tlog: fixture not found at {path}"
)
return path
# read_ap_parameter — reads from param-dump JSON