feat: finalize corrected-route planning and Rerun recording review
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user