Files
NODEDC_MISSION_CORE/tests/test_planning_fast_display.py
DCCONSTRUCTIONS e515ab1b8c feat(planning): consolidate recorded-route localization and spatial scene
Preserve the completed teach-and-repeat laboratory stage: reference preparation, cascaded acquisition, local tracking and recovery, recording lifecycle, replay qualification, and persistent Rerun scene controls. Document the open grid-picking regression and Rerun upgrade contract. No autonomous driving or loop-closure optimization is claimed.
2026-09-21 08:47:19 +03:00

313 lines
13 KiB
Python

"""Bounded functional tests of display/registration separation and delta fences."""
from dataclasses import replace
from types import SimpleNamespace
import numpy as np
from fastapi import FastAPI
from fastapi.testclient import TestClient
from test_planning_live import event, fixture_service
from test_planning_stabilization import fit, sample
from k1link.missions.live_buffer import LiveCloudBuffer
from k1link.missions.live_display_buffer import LiveDisplayBuffer
from k1link.missions.live_scene_delta import decode_cursor, scene_delta
from k1link.web.planning_live_api import build_planning_live_router
def ingest(display, numeric, t, sequence, points=None):
pose = event("pose", t=t, sequence=sequence, p=(t / 100, 0, 0))
cloud = event(
"points",
t=t + 0.001,
sequence=sequence + 1,
points=np.array([[t / 100, 1, 0]]) if points is None else points,
)
for e in (pose, cloud):
numeric.ingest(e)
display.ingest(e, numeric)
return cloud
def test_display_preserves_high_points_independently_from_tracking_filter():
numeric, display = LiveCloudBuffer([[0,0,0],[10,0,0]]), LiveDisplayBuffer()
points = np.array([[1,1,1],[1,0,40],[1,0,79.9],[1,0,80.1]])
ingest(display, numeric, 100, 1, points)
np.testing.assert_allclose(display.snapshot()["points"], points[:3])
np.testing.assert_allclose(numeric.snapshot()["points"], points[:1])
def test_native_packets_are_not_numerical_sampling_and_snapshots_are_immutable():
numeric, display = LiveCloudBuffer([[0, 0, 0], [10, 0, 0]]), LiveDisplayBuffer()
ingest(display, numeric, 100, 1)
first = display.snapshot()
for i in range(1, 10):
ingest(display, numeric, 100 + i / 10, i * 2 + 1)
assert display.frames == 10
assert len(numeric.chunks) == 2 # registration's 500 ms policy is unchanged
assert display.snapshot()["sequence"] == 20
assert first["sequence"] == 2 and first["cloud_revision"] == 1
np.testing.assert_equal(first["points"], [[1, 1, 0]])
assert len(display.chunks) == 1 # The current half-second receipt stays live.
np.testing.assert_allclose(display.snapshot()["current_points"], [[1.009, 1, 0]])
def test_budgets_eviction_tombstones_old_receipts_and_segment_reset():
numeric, display = LiveCloudBuffer([[0, 0, 0], [10, 0, 0]]), LiveDisplayBuffer()
points = np.array([[x * 0.3, y * 0.3, 0] for x in range(35) for y in range(35)])
for i in range(60):
ingest(display, numeric, 100 + i * 0.5, 2 * i, points)
assert len(display.chunks) <= 40
assert sum(map(len, display.chunks.values())) <= 40_000
before = display.frames
display.ingest(event("points", t=100, points=points), numeric)
assert display.frames == before
assert any(p is None and revision > 0 for _, revision, p in display.snapshot()["chunks"])
ingest(display, numeric, 133, 200, points)
assert numeric.segment == 1 and len(display.chunks) == 0
assert len(display.snapshot()["current_points"]) == len(points)
def test_delta_sends_only_changed_chunks_pose_and_transform(monkeypatch):
import k1link.missions.live_scene_delta as module
logs = []
class Recording:
def __init__(self, *args, **kwargs):
pass
def binary_stream(self):
return SimpleNamespace(read=lambda: b"RRF2")
def log(self, name, value, **kwargs):
logs.append((name, value, kwargs))
def set_time(self, *args, **kwargs):
logs.append(("time", args, kwargs))
def flush(self):
pass
def disconnect(self):
pass
monkeypatch.setattr(module.rr, "RecordingStream", Recording)
monkeypatch.setattr(module, "log_base", lambda *args: None)
monkeypatch.setattr(module, "log_view", lambda *args: None)
monkeypatch.setattr(module.rr, "Points3D", lambda points, **kwargs: np.asarray(points))
monkeypatch.setattr(module.rr, "Transform3D", lambda **kwargs: kwargs)
numeric, display = LiveCloudBuffer([[0, 0, 0], [10, 0, 0]]), LiveDisplayBuffer()
ingest(display, numeric, 100, 1)
def render(cursor="", result=None, live=True, evidence=None, options=None):
return scene_delta(
"run",
display.epoch,
np.zeros((3, 3)),
np.zeros((2, 3)),
display.snapshot(),
result or fit(),
evidence,
live,
cursor=cursor,
options=options,
)
_, cursor = render()
assert all(
kwargs.get("static")
for name, _, kwargs in logs
if name == "world/query" or "/cloud/" in name
)
np.testing.assert_equal(dict((n, v) for n, v, _ in logs)["world/query/live"], [[1, 1, 0]])
logs.clear()
render(options={"ceiling_m": -0.1})
assert len(dict((n, v) for n, v, _ in logs)["world/query/live"]) == 0
logs.clear()
assert render(cursor)[0] == b""
assert logs == []
ingest(display, numeric, 100.1, 3)
_, cursor = render(cursor)
assert [n for n, _, _ in logs if "/cloud/" in n] == []
assert "world/query/live" in [n for n, _, _ in logs]
assert "world/query" not in [n for n, _, _ in logs]
logs.clear()
adjusted = fit()
adjusted["T_reference_query"][0][3] = 9
_, cursor = render(cursor, result=adjusted)
assert [n for n, _, _ in logs] == ["world/query", "time", "world/query/live"]
assert logs[0][1]["translation"][0] == 9
logs.clear()
_, cursor = render(cursor, result=adjusted, evidence=(sample(), fit()))
assert "world/validated_query" in [n for n, _, _ in logs]
logs.clear()
render(cursor, result=adjusted, live=False)
assert "world/validated_query" in [n for n, _, _ in logs]
assert not any("/cloud/" in n for n, _, _ in logs)
def test_presentation_changes_and_geometry_repairs_never_reset_camera(monkeypatch):
import k1link.missions.live_scene_delta as module
views = []
monkeypatch.setattr(module, "log_view", lambda *args: views.append(args[-1].copy()))
reference = np.array([[0., 0., -1.], [20., 1., 40.]])
cursor = ""
def render(options=None, **kwargs):
nonlocal cursor
payload, cursor = scene_delta(
"camera-test", kwargs.pop("epoch", "first"), reference, reference,
None, None, None, False, cursor=cursor, options=options, **kwargs,
)
return payload
assert render().startswith(b"RRF2")
assert len(views) == 1
for options in ({"ceiling_m": 3}, {"reference": False}, {"query": False},
{"trajectory": False}, {"point_size": 4}, {"grid": False}, {}):
assert render(options).startswith(b"RRF2")
assert len(views) == 1
assert render(base=True).startswith(b"RRF2")
assert render(epoch="reconnected").startswith(b"RRF2")
assert len(views) == 1
render({"mode": "top"})
render({"mode": "top", "reset": 1})
assert len(views) == 3
assert render({"mode": "top", "reset": 1}) == b""
assert len(views) == 3
def test_grid_is_display_geometry_and_never_a_camera_command():
from k1link.missions.live_scene import grid_lines, log_base
reference = np.array([[0., 0., -1.], [20., 1., 40.]])
logs = {}
recording = SimpleNamespace(log=lambda path, value, **kw: logs.update({path: value}))
log_base(recording, reference, reference, {"ceiling_m": 3})
assert "world/grid" in logs
assert len(logs["world/reference"].positions.as_arrow_array()) == 1
assert len(grid_lines(reference)) > 0
log_base(recording, reference, reference, {"grid": False})
assert len(logs["world/grid"].strips.as_arrow_array()) == 0
def test_service_freezes_after_loss_fences_identity_and_expires_without_packets(
tmp_path, monkeypatch
):
import k1link.missions.live_tests as module
service, source, _, _ = fixture_service(tmp_path, monkeypatch)
run_id = "cedf3261-a703-453d-a1c8-aac71c6ce5e3"
service.run = dict(
id=run_id,
state="running",
query_session_id="B",
query_generation=2,
vehicle_control=False,
)
service.source = source
source.state.update(active=True, session_id="B", session_generation=2)
service.reference, service.reference_path = np.zeros((3, 3)), np.zeros((2, 3))
summary = service.get()
assert summary["scene_height_min_m"] == 0.0
assert summary["scene_height_max_m"] == 80.0
clock = [int(100.1e9)]
monkeypatch.setattr(module, "time", SimpleNamespace(monotonic_ns=lambda: clock[0]))
monkeypatch.setattr(service, "persist", lambda: None)
numeric = LiveCloudBuffer(service.reference_path)
for e in (event("pose", t=100), event("points", t=100.01, points=np.ones((2, 3)))):
numeric.ingest(e)
service.observe_display(e, numeric, clock[0])
assert service.presentation.sample is None # no hint fallback
service.commit_result(
fit(),
sample(),
{"accepted": True},
"tracking",
phase="tracking",
message="tracking",
tracking_established=True,
)
app = FastAPI()
app.include_router(build_planning_live_router(service))
with TestClient(app) as client:
url = f"/api/v1/mission-planner/live-tests/{run_id}/scene-delta.rrd"
first = client.get(url)
assert first.status_code == 200 and first.content.startswith(b"RRF2")
cursor = first.headers["X-Planning-Scene-Cursor"]
assert first.headers["X-Planning-Cloud-Revision"] == "1"
assert first.headers["X-Planning-Cloud-Sequence"] == "1"
assert first.headers["X-Planning-Height-Min"] == "0.0"
assert first.headers["X-Planning-Height-Max"] == "80.0"
observation = {
"schema_version": "missioncore.planning-browser-presentation/v1",
"samples": [
{
"cloud_revision": 1,
"cloud_sequence": 1,
"display_epoch": service.display.epoch,
"request_ms": 40.0,
"rerun_admission_ms": 2.0,
"first_animation_frame_ms": 12.0,
"second_animation_frame_ms": 28.0,
"frame_timeout": False,
"source_to_second_animation_frame_upper_bound_ms": 268.0,
}
],
}
report_url = (
f"/api/v1/mission-planner/live-tests/{run_id}/presentation-observations"
)
assert client.post(report_url, json=observation).status_code == 204
presentation = service.get()["browser_presentation"]
assert presentation["reported_sample_count"] == 1
assert presentation["second_animation_frame_ms"]["p50"] == 28.0
assert service.get()["vehicle_control"] is False
observation["samples"][0]["cloud_revision"] = 2
assert client.post(report_url, json=observation).status_code == 204
assert service.get()["browser_presentation"]["rejected_sample_count"] == 1
assert client.get(url, params={"cursor": cursor}).status_code == 204
assert client.get(url, params={"cursor": "corrupt"}).status_code == 200
assert client.get(url, params={"cursor": "a" * 2049}).status_code == 422
clock[0] = int(102.2e9)
expired = client.get(url, params={"cursor": cursor})
assert expired.status_code == 200
assert decode_cursor(expired.headers["X-Planning-Scene-Cursor"])["evidence"] is None
frozen = service.presentation.sample
service.accepted_sample = None
fresh = event("points", t=102.3, points=np.ones((2, 3)))
service.observe_display(replace(fresh, session_id="other"), numeric, int(102.3e9))
assert service.display.frames == 1
numeric.ingest(event("pose", t=102.2))
service.observe_display(event("pose", t=102.2), numeric, int(102.2e9))
service.observe_display(fresh, numeric, int(102.3e9))
assert service.presentation.sample is frozen
source.state["session_generation"] = 3
assert not service._view_live(int(102.3e9))
def test_reinitialize_endpoint_discards_only_a_failed_initialization(tmp_path, monkeypatch):
service, _, _, _ = fixture_service(tmp_path, monkeypatch)
run_id = "cedf3261-a703-453d-a1c8-aac71c6ce5e4"
service.run = dict(
id=run_id,
state="running",
planning_phase="lost",
tracking_established=False,
initialization_attempt=1,
reinitialization_count=0,
)
service.persist = lambda: None
app = FastAPI()
app.include_router(build_planning_live_router(service))
with TestClient(app) as client:
response = client.post(f"/api/v1/mission-planner/live-tests/{run_id}/reinitialize")
assert response.status_code == 200
body = response.json()
assert body["state"] == "running"
assert body["planning_phase"] == "waiting-cloud"
assert body["initialization_attempt"] == 2
assert body["reinitialization_count"] == 1