mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-23 01:21:13 +00:00
[AZ-611] Add --skip-auto-sync flag to bypass AC-9 validator
Mid-flight fixtures (Derkachi) and stationary-still scenarios (FT-P-01) have no take-off spike for the IMU detector and produce false-positive video motion onsets, so the AC-9 frame-window validator rejects every plausible offset. Add an operator-acknowledged opt-out: a new ReplayConfig.skip_auto_sync_validation flag that suppresses validation, paired with a hard requirement that time_offset_ms also be set (silent-zero guard at both schema and adapter layers). Wired through schema -> CLI (--skip-auto-sync) -> composition root -> ReplayInputAdapter; Derkachi e2e fixture now passes time_offset_ms=0 + skip_auto_sync=True by default since the synth tlog and the video share the same t=0 anchor by construction. 5 new unit tests: * schema gate rejects skip=True without manual offset * schema gate accepts the legal pair * default field value is False (default-construction safety) * adapter constructor mirrors the schema gate * adapter open() bypasses validate_offset_or_fail when flag is set All 38 unit tests in test_az401 + test_az405 pass on Mac. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -105,7 +105,16 @@ class ReplayInputAdapter:
|
||||
the coordinator's own structured-event mirror.
|
||||
- ``pace`` — :class:`ReplayPace` (``ASAP`` or ``REALTIME``).
|
||||
- ``manual_time_offset_ms`` — ``None`` triggers auto-sync; an
|
||||
integer bypasses auto-sync entirely (AC-8).
|
||||
integer bypasses auto-sync DETECTION but the AC-9 frame-window
|
||||
validator still runs on the resolved offset (AC-8).
|
||||
- ``skip_auto_sync_validation`` — when ``True``, ALSO skip the
|
||||
AC-9 validator. Only legal in combination with a non-``None``
|
||||
``manual_time_offset_ms`` (the coordinator refuses both-None
|
||||
to avoid silent-zero offset bugs). Intended for fixtures where
|
||||
neither the IMU take-off detector nor the video motion-onset
|
||||
detector can produce a reliable signal (mid-flight clips,
|
||||
stationary still-image scenarios — see AZ-611). Default
|
||||
``False``.
|
||||
- ``auto_sync_config`` — :class:`AutoSyncConfig` thresholds.
|
||||
|
||||
Behaviour:
|
||||
@@ -127,6 +136,7 @@ class ReplayInputAdapter:
|
||||
"_fdr_client",
|
||||
"_pace",
|
||||
"_manual_time_offset_ms",
|
||||
"_skip_auto_sync_validation",
|
||||
"_auto_sync_config",
|
||||
"_tlog_source_factory",
|
||||
"_video_frames_factory",
|
||||
@@ -150,6 +160,7 @@ class ReplayInputAdapter:
|
||||
pace: ReplayPace,
|
||||
manual_time_offset_ms: int | None,
|
||||
auto_sync_config: AutoSyncConfig,
|
||||
skip_auto_sync_validation: bool = False,
|
||||
tlog_source_factory: Any | None = None,
|
||||
video_frames_factory: Any | None = None,
|
||||
video_timestamps_factory: Any | None = None,
|
||||
@@ -172,6 +183,22 @@ class ReplayInputAdapter:
|
||||
raise ReplayInputAdapterError(
|
||||
f"pace must be a ReplayPace enum; got {type(pace).__name__}"
|
||||
)
|
||||
if not isinstance(skip_auto_sync_validation, bool):
|
||||
raise ReplayInputAdapterError(
|
||||
"skip_auto_sync_validation must be a bool; got "
|
||||
f"{type(skip_auto_sync_validation).__name__}"
|
||||
)
|
||||
if skip_auto_sync_validation and manual_time_offset_ms is None:
|
||||
# Mirror the ReplayConfig.__post_init__ gate. Without a
|
||||
# manual offset there is no operator-acknowledged value
|
||||
# to skip validation against — auto-sync would compute
|
||||
# an offset of unknown quality and the validator that
|
||||
# would catch a bad detection is disabled. Refuse so
|
||||
# this can't silently mask a wrong offset.
|
||||
raise ReplayInputAdapterError(
|
||||
"skip_auto_sync_validation=True requires "
|
||||
"manual_time_offset_ms to be set"
|
||||
)
|
||||
self._video_path = video_path
|
||||
self._tlog_path = tlog_path
|
||||
self._camera_calibration = camera_calibration
|
||||
@@ -180,6 +207,7 @@ class ReplayInputAdapter:
|
||||
self._fdr_client = fdr_client
|
||||
self._pace = pace
|
||||
self._manual_time_offset_ms = manual_time_offset_ms
|
||||
self._skip_auto_sync_validation = skip_auto_sync_validation
|
||||
self._auto_sync_config = auto_sync_config
|
||||
self._tlog_source_factory = tlog_source_factory
|
||||
self._video_frames_factory = video_frames_factory
|
||||
@@ -221,21 +249,39 @@ class ReplayInputAdapter:
|
||||
},
|
||||
)
|
||||
|
||||
# Step 3 — load video frame timestamps and run AC-9 validator.
|
||||
# Step 3 — load video frame timestamps and run AC-9 validator
|
||||
# unless the operator explicitly opted out via
|
||||
# skip_auto_sync_validation (AZ-611). The opt-out is meant for
|
||||
# mid-flight + stationary fixtures where neither detector can
|
||||
# produce a reliable signal; the constructor already enforced
|
||||
# that the opt-out requires a manual offset.
|
||||
video_frame_timestamps_ns = self._load_video_timestamps()
|
||||
result_code = validate_offset_or_fail(
|
||||
resolved_offset_ms,
|
||||
tlog_imu_timestamps_ns,
|
||||
video_frame_timestamps_ns,
|
||||
threshold_pct=self._auto_sync_config.match_threshold_pct,
|
||||
window_ms=self._auto_sync_config.match_window_ms,
|
||||
)
|
||||
if result_code != 0:
|
||||
self._raise_ac8_fail(
|
||||
resolved_offset_ms,
|
||||
len(tlog_imu_timestamps_ns),
|
||||
len(video_frame_timestamps_ns),
|
||||
if self._skip_auto_sync_validation:
|
||||
self._log.info(
|
||||
f"{_LOG_KIND_OPEN_MANUAL}: ac9_validator_skipped "
|
||||
f"(resolved_offset_ms={resolved_offset_ms})",
|
||||
extra={
|
||||
"kind": _LOG_KIND_OPEN_MANUAL,
|
||||
"kv": {
|
||||
"resolved_offset_ms": resolved_offset_ms,
|
||||
"ac9_validator_skipped": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
else:
|
||||
result_code = validate_offset_or_fail(
|
||||
resolved_offset_ms,
|
||||
tlog_imu_timestamps_ns,
|
||||
video_frame_timestamps_ns,
|
||||
threshold_pct=self._auto_sync_config.match_threshold_pct,
|
||||
window_ms=self._auto_sync_config.match_window_ms,
|
||||
)
|
||||
if result_code != 0:
|
||||
self._raise_ac8_fail(
|
||||
resolved_offset_ms,
|
||||
len(tlog_imu_timestamps_ns),
|
||||
len(video_frame_timestamps_ns),
|
||||
)
|
||||
|
||||
# Step 4 — clock strategy (single instance per Invariant 2).
|
||||
clock = self._build_clock()
|
||||
|
||||
Reference in New Issue
Block a user