mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 17:41:13 +00:00
[AZ-776] Open-loop ESKF composition profile via c4_pose.enabled
ADR-012: add c4_pose.enabled (default True) and enforce the (c4_pose.enabled, c5_state.strategy) 2x2 pairing matrix at compose time. When enabled=false, compose_root removes c4_pose from the selection map and build_pre_constructed omits c5_isam2_graph_handle. Replay protocol Invariant 13 owns the gate. Tier-2 conftest YAML writes the open-loop profile; un-xfails AC-1/2/5 and both AC-6 variants in Derkachi (AC-3 stays xfailed for AZ-777). 319/319 runtime_root + c4_pose + c5_state tests green. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -136,24 +136,28 @@ def derkachi_replay_inputs(tmp_path_factory: pytest.TempPathFactory) -> Derkachi
|
||||
# treats each top-level mapping as a block whose key is a
|
||||
# registry slug; nesting the slugs under a `components:`
|
||||
# wrapper makes the loader silently drop them (the wrapper
|
||||
# is not a registered slug). See `_docs/_repo` notes on the
|
||||
# ESKF compose-time blocker (AZ-776) for why this matters.
|
||||
# is not a registered slug).
|
||||
#
|
||||
# KLT/RANSAC + ESKF is the minimal pair that runs without
|
||||
# native deps (cv2 + numpy only). The CLI currently exits
|
||||
# non-zero at compose time for this configuration: c4_pose
|
||||
# hard-requires an iSAM2 graph handle that ESKF does not
|
||||
# provide (handle=None by design). AZ-776 tracks the fix.
|
||||
# Until AZ-776 lands, every heavy AC test in
|
||||
# `test_derkachi_1min.py` is xfailed with that ticket in
|
||||
# the reason. C2/C3/C4 satellite anchoring additionally
|
||||
# require AZ-777 (Derkachi C6 reference tile cache).
|
||||
# Open-loop ESKF composition profile (AZ-776 / ADR-012):
|
||||
# `c4_pose.enabled = false` strips C4 from the composition
|
||||
# graph so the airborne binary can run the mandatory simple
|
||||
# baseline (KLT/RANSAC VIO + ESKF state estimator) end-to-end
|
||||
# without a C4 anchor. ESKF has no iSAM2 graph for C4 to
|
||||
# anchor against; the `compose_root` validation gate rejects
|
||||
# the off-diagonal pairings (`enabled=False` + `gtsam_isam2`
|
||||
# or `enabled=True` + `eskf`) with a `CompositionError`.
|
||||
# Position drifts open-loop without C2/C3/C4 satellite
|
||||
# re-anchoring — AZ-777 (Derkachi C6 reference tile cache)
|
||||
# is the follow-up that closes the satellite-anchoring half
|
||||
# of the per-frame loop.
|
||||
"mode: replay\n"
|
||||
"replay:\n"
|
||||
" pace: asap\n"
|
||||
" target_fc_dialect: ardupilot_plane\n"
|
||||
"c1_vio:\n"
|
||||
" strategy: klt_ransac\n"
|
||||
"c4_pose:\n"
|
||||
" enabled: false\n"
|
||||
"c5_state:\n"
|
||||
" strategy: eskf\n"
|
||||
)
|
||||
|
||||
@@ -58,22 +58,6 @@ _HEAVY_SKIP = pytest.mark.skipif(
|
||||
|
||||
@pytest.mark.tier2
|
||||
@_HEAVY_SKIP
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"Blocked by AZ-776: the replay compose root cannot wire "
|
||||
"c5_state=eskf because c4_pose hard-requires an iSAM2 graph "
|
||||
"handle that ESKF does not provide (handle=None by design). "
|
||||
"The CLI exits non-zero at compose time before the per-frame "
|
||||
"loop runs, so this test cannot pass against the current "
|
||||
"runtime. Once AZ-776 ships, an open-loop C1+C5(ESKF) "
|
||||
"composition will allow the CLI to exit 0 and this AC-1 "
|
||||
"test (emit one EstimatorOutput per video frame) can pass. "
|
||||
"Full-pipeline accuracy still requires AZ-777 (Derkachi C6 "
|
||||
"reference tile cache) but AC-1 only needs successful exit, "
|
||||
"not anchor-quality, so AZ-776 alone is sufficient."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
def test_ac1_exits_0_jsonl_count_match(replay_runner, derkachi_replay_inputs) -> None:
|
||||
"""Real loop emits one EstimatorOutput per video frame, not per GPS fix.
|
||||
|
||||
@@ -135,17 +119,6 @@ _ESTIMATOR_OUTPUT_KEYS = frozenset(
|
||||
|
||||
@pytest.mark.tier2
|
||||
@_HEAVY_SKIP
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"Blocked by AZ-776 (replay compose root cannot use "
|
||||
"c5_state=eskf). The CLI exits non-zero before any JSONL "
|
||||
"rows are written, so the schema cannot be validated against "
|
||||
"the current runtime. Schema lives in EstimatorOutput and is "
|
||||
"stable; AC-2 can pass as soon as AZ-776 makes the loop "
|
||||
"actually emit rows."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
def test_ac2_jsonl_schema_match(replay_runner) -> None:
|
||||
# Act
|
||||
result = replay_runner(pace="asap")
|
||||
@@ -174,18 +147,13 @@ def test_ac2_jsonl_schema_match(replay_runner) -> None:
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"AC-3 requires the C1+C2+C3+C4+C5 satellite-re-anchoring "
|
||||
"pipeline. Two blockers, both tracked: "
|
||||
"(1) AZ-776 — the replay compose root cannot currently wire "
|
||||
"c5_state=eskf at all (c4_pose hard-requires an iSAM2 "
|
||||
"handle ESKF does not provide); the CLI exits non-zero "
|
||||
"before any tick is emitted. "
|
||||
"(2) AZ-777 — once AZ-776 lands, the open-loop C1+C5(ESKF) "
|
||||
"composition will run end-to-end but with NO satellite "
|
||||
"anchoring (no C2/C3/C4) because the Derkachi fixture has "
|
||||
"no reference C6 tile cache. ESKF integrates open-loop, so "
|
||||
"position drifts unbounded over the 8-min flight and the "
|
||||
"≤100m threshold cannot be met by physics. "
|
||||
"AC-3 stays xfail until BOTH AZ-776 and AZ-777 ship."
|
||||
"pipeline. Blocked by AZ-777: with AZ-776 landed, the "
|
||||
"open-loop C1+C5(ESKF) composition now runs end-to-end but "
|
||||
"with NO satellite anchoring (no C2/C3/C4) because the "
|
||||
"Derkachi fixture has no reference C6 tile cache. ESKF "
|
||||
"integrates open-loop, so position drifts unbounded over "
|
||||
"the 8-min flight and the ≤100 m threshold cannot be met "
|
||||
"by physics until the reference tile cache (AZ-777) lands."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
@@ -410,17 +378,6 @@ def test_ac4_encoder_byte_equality_via_transport_seam() -> None:
|
||||
|
||||
@pytest.mark.tier2
|
||||
@_HEAVY_SKIP
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"Blocked by AZ-776: with the compose root failing for "
|
||||
"c5_state=eskf the CLI exits non-zero on both runs, so "
|
||||
"determinism cannot be observed. Once AZ-776 ships, the "
|
||||
"open-loop C1+C5 path is deterministic by construction "
|
||||
"(KLT/RANSAC uses fixed seeds, ESKF is closed-form) and "
|
||||
"AC-5 should pass."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
def test_ac5_determinism_two_runs_diff(replay_runner) -> None:
|
||||
# Act
|
||||
r1 = replay_runner(pace="asap")
|
||||
@@ -450,14 +407,6 @@ def test_ac5_determinism_two_runs_diff(replay_runner) -> None:
|
||||
|
||||
@pytest.mark.tier2
|
||||
@_HEAVY_SKIP
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"Blocked by AZ-776: the CLI exits non-zero at compose time, "
|
||||
"so the realtime pacing loop is never reached. Once AZ-776 "
|
||||
"ships, AC-6 realtime can pace the open-loop C1+C5 path."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
def test_ac6_pace_realtime_60s_within_5pct(replay_runner) -> None:
|
||||
# Act — cap to 60 s so a full 490-second flight doesn't pin the test
|
||||
# to an 8-minute realtime run; the pacing correctness is validated
|
||||
@@ -476,15 +425,6 @@ def test_ac6_pace_realtime_60s_within_5pct(replay_runner) -> None:
|
||||
|
||||
@pytest.mark.tier2
|
||||
@_HEAVY_SKIP
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"Blocked by AZ-776: the CLI exits non-zero at compose time, "
|
||||
"so the ASAP pacing loop is never reached. Once AZ-776 "
|
||||
"ships, AC-6 ASAP can run the open-loop C1+C5 path "
|
||||
"to completion."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
def test_ac6_pace_asap_under_30s(replay_runner) -> None:
|
||||
# Act
|
||||
result = replay_runner(pace="asap")
|
||||
|
||||
Reference in New Issue
Block a user