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
+42 -15
View File
@@ -1,4 +1,4 @@
"""Selected route length drives live admission, reference coverage and termination."""
"""Reference coverage and admission never impose a live travel budget."""
import threading
from types import SimpleNamespace
@@ -14,11 +14,12 @@ from k1link.missions.reference_window import reference_window
@pytest.mark.parametrize("length", [3, 30, 50, 100, 200, 300, 10_000])
def test_limits_come_from_selected_length(length):
def test_reference_length_does_not_limit_live_distance_or_time(length):
limits = live_route_limits(length)
assert limits["maximum_distance_m"] == length
assert limits["maximum_distance_m"] is None
assert limits["maximum_seconds"] is None
assert limits["route_policy"]["maximum_m"] is None
assert limits["route_policy"]["version"] == "selected-live-route/v2"
@pytest.mark.parametrize("length", [0, 2.9, float("nan"), float("inf")])
@@ -67,8 +68,11 @@ def test_long_path_does_not_freeze_tail_or_multiply_distance():
np.testing.assert_allclose(buffer.path[-1], [2099 * 0.06, 0, 0])
@pytest.mark.parametrize("length", [20, 50, 100, 200, 300])
def test_actual_live_loop_stops_at_selected_distance_not_forty(tmp_path, monkeypatch, length):
@pytest.mark.parametrize("length", [20, 100, 300])
@pytest.mark.parametrize("ending", ["operator", "session-end", "spatial-stop-requested"])
def test_live_travel_beyond_multiple_reference_lengths_requires_explicit_end(
tmp_path, monkeypatch, length, ending
):
import k1link.missions.stationary_live as module
# Isolate termination from the separately tested geometric state machine.
@@ -111,7 +115,9 @@ def test_actual_live_loop_stops_at_selected_distance_not_forty(tmp_path, monkeyp
run = service.start("draft", 1)
try:
until(lambda: service.get()["state"] == "waiting")
assert run["maximum_distance_m"] == length
assert run["maximum_distance_m"] is None
# An old field cannot silently resurrect distance-based termination.
service.update(maximum_distance_m=length)
clock[0] = 4000.0 # Neither waiting nor an active run has an implicit time cap.
source.state.update(active=True, session_id="B", session_generation=2)
source.queue.put(event("pose", t=1))
@@ -120,15 +126,36 @@ def test_actual_live_loop_stops_at_selected_distance_not_forty(tmp_path, monkeyp
)
until(lambda: service.get().get("planning_phase") == "collecting")
clock[0] = 8000.0
# Queue up to just before the chosen boundary, then prove it remains live.
for i in range(1, length):
source.queue.put(event("pose", t=1 + i, p=(i, 0, 0), sequence=i + 2))
until(source.queue.empty)
assert service.get()["state"] == "running"
source.queue.put(event("pose", t=1 + length, p=(length, 0, 0), sequence=length + 2))
until(lambda: service.get()["state"] == "completed")
assert service.get()["termination_reason"] == "distance-limit"
assert service.get()["distance_m"] == length
# Three legs on the same geometry: travel exceeds the route while the
# current location stays on it. Synchronize via a consumed cloud, not
# queue.empty(), which can race the consumer's final update.
sequence = 3
for leg in range(3):
for offset in range(1, length + 1):
traveled = leg * length + offset
position = offset if leg % 2 == 0 else length - offset
source.queue.put(
event("pose", t=1 + traveled, p=(position, 0, 0), sequence=sequence)
)
sequence += 1
clock[0] += 1
source.queue.put(event("points", t=1.001 + traveled, sequence=sequence,
points=np.array([[position, 0, 1.]])))
sequence += 1
until(lambda expected=traveled: service.get()["distance_m"] == expected)
assert service.get()["state"] == "running"
assert service.get().get("termination_reason") is None
assert lock.locked() and source.owner is not None
if ending == "operator":
service.stop(run["id"])
expected_state, expected_reason = "cancelled", "cancelled"
else:
source.queue.put(event(ending, t=2 + traveled, sequence=sequence))
expected_state = "completed"
expected_reason = "input-ended" if ending == "session-end" else ending
until(lambda: service.get()["state"] == expected_state)
assert service.get()["termination_reason"] == expected_reason
assert service.get()["distance_m"] == 3 * length
assert service.accepted_sample is None
assert source.state["active"] is True # Calculation never stops capture.
finally: