feat: finalize corrected-route planning and Rerun recording review

This commit is contained in:
DCCONSTRUCTIONS
2026-09-22 10:10:03 +03:00
parent c804d89b18
commit 2e5d52521f
132 changed files with 14141 additions and 898 deletions
+75 -3
View File
@@ -44,6 +44,77 @@ def initialized():
return boot, result
def long_route_search():
from k1link.missions.route_relocalization import ROUTE_RELOCALIZATION_POLICY
boot, result = initialized()
boot.initialization_policy = ROUTE_RELOCALIZATION_POLICY
result["initialization"].update(policy=ROUTE_RELOCALIZATION_POLICY,
scope="selected-route", expected_attempts=108)
for i in range(20, 200):
boot.ingest(event(i * .5, i * 2 + 1), 0)
boot.ingest(event(i * .5 + .001, i * 2 + 2, kind="points"), 0)
boot.tick(int((i * .5 + 1.01) * 1e9), 0)
assert boot.phase == "searching"
return boot, result
def test_long_complete_search_is_only_a_hypothesis_until_three_new_windows():
boot, result = long_route_search()
ready = 101_000_000_000
decision = boot.offer_prior(result, ready, 0)
assert decision["age_s"] > 90
assert decision["provisional"] and decision["stationary_search_continuity"]
assert not decision["accepted"] and boot.gate.matrix is None
seen = set()
for i, start in enumerate((101., 106., 111.)):
sample, _ = fresh_window(boot, start, 500 + i * 20, segment=0)
ids = {e["sequence"] for e in sample["events"]}
assert not ids & seen
seen |= ids
assert all(e["monotonic_ns"] > ready for e in sample["events"])
decision = boot.accept_fresh(fit(), sample, int((start + 4.2) * 1e9), 0)
assert decision["accepted"]
assert boot.phase == ("tracking" if i == 2 else "validating")
@pytest.mark.parametrize(
"failure", ["motion", "gap", "pose-only", "cloud-only", "silent", "identity"]
)
def test_long_search_cannot_survive_motion_or_invalid_live_receipts(failure):
boot, result = long_route_search()
now = 111_000_000_000
if failure == "motion":
boot.ingest(replace(event(100., 500), position=(.11, 0., 0.)), 0)
elif failure == "gap":
boot.ingest(event(100., 500), 1)
elif failure == "identity":
with pytest.raises(ValueError):
boot.ingest(replace(event(100., 500), generation=2), 0)
elif failure in {"pose-only", "cloud-only"}:
boot.ingest(event(109., 500, kind="pose" if failure == "pose-only" else "points"), 0)
boot.tick(now, boot.segment)
decision = boot.offer_prior(result, now, boot.segment)
assert boot.phase == "lost" and not decision["provisional"]
assert boot.gate.matrix is None
def test_long_search_can_try_next_place_only_with_another_fresh_window():
boot, result = long_route_search()
alternative = np.eye(4)
alternative[0, 3] = 40
result["initialization"]["candidate_queue"] = [
dict(candidate_index=1, T_reference_query=np.eye(4).tolist(), ambiguous=False),
dict(candidate_index=2, T_reference_query=alternative.tolist(), ambiguous=False),
]
assert boot.offer_prior(result, 101_000_000_000, 0)["provisional"]
sample, _ = fresh_window(boot, 101., 500, segment=0)
decision = boot.accept_fresh(dict(status="rejected"), sample, 105_200_000_000, 0)
assert not decision["accepted"] and decision["next_candidate_trial"] == 2
assert boot.gate.matrix is None and boot.floor_ns == 105_200_000_000
assert np.allclose(boot.prior, alternative)
assert boot.validation(106_000_000_000, 0) is None
def fresh_window(boot, start, seq, segment=1):
for i in range(7):
boot.ingest(event(start + i * 0.5, seq + i * 2), segment)
@@ -170,8 +241,9 @@ def test_initial_failure_and_later_loss_have_distinct_operator_messages():
rejected = boot.offer_prior(result, 36_000_000_000, 0)
assert rejected["reason"] == "initialization-incomplete"
assert not boot.tracking_established
assert "Синхронизация маршрута не завершилась" in phase_message(boot)
assert "Остановите устройство и запись" in phase_message(boot)
assert "Поиск не завершён" in phase_message(boot)
assert "Переинициализировать" in phase_message(boot)
assert "Остановите устройство" not in phase_message(boot)
assert "потеряна" not in phase_message(boot)
boot.tracking_established = True
boot.stop("stale")
@@ -266,7 +338,7 @@ def test_candidate_queue_never_bypasses_freshness_identity_or_ambiguity(failure)
ready = 45_000_000_000 if failure == "expired" else 25_000_000_000
if failure == "ambiguous":
result["initialization"]["candidate_queue"][1]["ambiguous"] = True
# Use the route policy's existing 35 s search fence for the late prefix case.
# The legacy local protocol still has its 30 s search / 40 s source fences.
if failure == "expired":
boot.search_started_ns += 5_000_000_000
assert boot.offer_prior(result, ready, 1)["provisional"]