fix(perception): stabilize replay body frame

This commit is contained in:
DCCONSTRUCTIONS
2026-08-05 20:04:08 +03:00
parent de19229895
commit c70ad345ea
12 changed files with 783 additions and 256 deletions
+17 -21
View File
@@ -8,10 +8,7 @@ from k1link.perception.threat_replay import read_threat_replay_result
from k1link.web.m4_threat_replay_api import build_m4_threat_replay_router
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
RESULT_ID = (
"m4-threat-replay-"
"7e1613a3ea35638b5ea7a3f7c1c78fe9eba1a3adae540b652dec167f815d45b2"
)
RESULT_ID = "m4-threat-replay-78a06d96c4db5263dc63fc4e6e067c07fc81370d3f5085ff43361af89cec1e9e"
RESULTS_ROOT = REPOSITORY_ROOT / ".runtime/compute-experiments/m4/replay-threat"
@@ -35,9 +32,9 @@ def test_full_source_threat_result_closes_m4_6_contract() -> None:
"stale-or-held": 37995,
}
assert result.metrics["decisions"] == {
"not-threat": 6610,
"threat": 8010,
"unknown": 60832,
"not-threat": 10700,
"threat": 2716,
"unknown": 62036,
}
assert result.metrics["fixtures"] == {
"critical": 4,
@@ -53,10 +50,10 @@ def test_threat_result_is_content_bound_and_visual_evidence_is_complete() -> Non
assert isinstance(identity, dict)
assert identity["frames_sha256"] == (
"bf690358efb45c323db7172251074b33c3ef7ede6ae99bd8d3da53cfba86b142"
"d55e7651f0b16a62c6b61c5cb2358dd8dff87dbfa57a59e9ec350bc38b156bc1"
)
assert identity["visuals_sha256"] == (
"fb022c6efd84f27c0916a6c87887443c9b43993ac4b1f9910332433152533dea"
"957c35d46ae30143beb6b2f26f8f722853ef2a1e91a41d5dc1a03fbf723a54e0"
)
visual = result.metrics["visual_evidence"]
assert isinstance(visual, dict)
@@ -69,24 +66,27 @@ def test_threat_result_is_content_bound_and_visual_evidence_is_complete() -> Non
"point_cloud_available",
"metric_distance_available",
"virtual_corridor_available",
"qualified_base_footprint_available",
)
)
assert visual["geometry_regression_sequences"] == [138, 274]
body_frame = result.metrics["body_frame"]
assert body_frame["qualified"] == 3861
assert body_frame["rejected"] == 67
assert body_frame["camera_forward_alignment_deg"]["p95"] < 9.0
def test_m4_6_lab_api_projects_report_and_exact_visual_frame() -> None:
list_results = _endpoint("/api/v1/laboratory/m4-threat/results")
list_visuals = _endpoint(
"/api/v1/laboratory/m4-threat/results/{result_id}/visuals"
)
get_visual = _endpoint(
"/api/v1/laboratory/m4-threat/results/{result_id}/visuals/{ordinal}"
)
list_visuals = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/visuals")
get_visual = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/visuals/{ordinal}")
catalog = list_results(limit=1)
assert catalog["items"][0]["result_id"] == RESULT_ID
assert catalog["items"][0]["authority"] == "replay-simulated"
visuals = list_visuals(RESULT_ID)
assert len(visuals["items"]) == 32
assert [item["sequence"] for item in visuals["items"][:3]] == [62, 138, 274]
frame = get_visual(RESULT_ID, 1)
assert frame["schema_version"] == "missioncore.perception-threat-visual-frame/v1"
assert frame["point_cloud_sample_count"] > 0
@@ -98,16 +98,12 @@ def test_m4_6_lab_api_projects_report_and_exact_visual_frame() -> None:
def test_m4_6_video_overlay_covers_the_exact_recorded_camera_timeline() -> None:
get_overlay = _endpoint(
"/api/v1/laboratory/m4-threat/results/{result_id}/video-overlay"
)
get_overlay = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/video-overlay")
overlay = get_overlay(RESULT_ID)
assert overlay["frame_count"] == 4489
assert overlay["recorded_source"]["session_id"] == (
"20260720T065719Z_viewer_live"
)
assert overlay["recorded_source"]["session_id"] == ("20260720T065719Z_viewer_live")
assert overlay["frames"][0]["frame_index"] == 0
assert overlay["frames"][-1]["frame_index"] == 4488
assert overlay["authority"] == "replay-simulated"
+50 -18
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
from pathlib import Path
import pytest
from k1link.perception.contracts import (
BoundingRegion2D,
CorridorIntersection,
@@ -15,23 +17,29 @@ from k1link.perception.contracts import (
TemporalState,
ThreatDecision,
)
from k1link.perception.geometry import RecordedGeometryStore
from k1link.perception.graph_validation import validate_threats
from k1link.perception.threat import (
DualEvidenceReplayThreatProvider,
ReplayPose,
RecordedReplayBodyFrameResolver,
ReplayBodyFrame,
load_replay_threat_profile,
)
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
PROFILE_PATH = REPOSITORY_ROOT / "config/perception/m4-replay-threat-v1.json"
PROFILE_PATH = REPOSITORY_ROOT / "config/perception/m4-replay-threat-v2.json"
class _Poses:
def pose_for_frame(self, frame_id: str) -> ReplayPose:
return ReplayPose(
class _BodyFrames:
def body_frame_for_frame(self, frame_id: str) -> ReplayBodyFrame:
return ReplayBodyFrame(
frame_id=frame_id,
position_map_xyz_m=(0.0, 0.0, 0.0),
orientation_map_from_lidar_xyzw=(0.0, 0.0, 0.0, 1.0),
origin_map_xyz_m=(0.0, 0.0, 0.0),
basis_map_from_body=((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)),
sensor_height_m=1.25,
surface_slope_deg=0.0,
forward_source="fixture",
camera_forward_alignment_deg=0.0,
)
@@ -63,9 +71,7 @@ def _obstacle(
last_centroid_xyz_m=None if state is TemporalState.EXPIRED else current.centroid_xyz_m,
motion=MotionState.UNKNOWN if state is not TemporalState.CURRENT else motion,
motion_confidence=(
0.0
if state is not TemporalState.CURRENT or motion is MotionState.UNKNOWN
else 1.0
0.0 if state is not TemporalState.CURRENT or motion is MotionState.UNKNOWN else 1.0
),
motion_reason=(
"stale-support"
@@ -127,9 +133,35 @@ def test_replay_threat_profile_freezes_virtual_authority_and_dual_evidence_polic
)
def test_recorded_body_frame_is_grounded_and_does_not_inherit_handheld_roll_pitch() -> None:
profile = load_replay_threat_profile(PROFILE_PATH)
resolver = RecordedReplayBodyFrameResolver(
RecordedGeometryStore.from_repository(REPOSITORY_ROOT),
profile=profile.body_frame,
)
start = resolver.body_frame_for_frame("frame-000000")
middle = resolver.body_frame_for_frame("frame-000138")
later = resolver.body_frame_for_frame("frame-000274")
assert start is None # the opening surface height is not qualified evidence
assert middle is not None and later is not None
assert tuple(row[2] for row in middle.basis_map_from_body) == (0.0, 0.0, 1.0)
assert tuple(row[2] for row in later.basis_map_from_body) == (0.0, 0.0, 1.0)
assert middle.sensor_height_m == pytest.approx(1.2509065924)
assert later.sensor_height_m == pytest.approx(1.2838213430)
assert middle.camera_forward_alignment_deg < 7.0
assert later.camera_forward_alignment_deg < 2.0
summary = resolver.qualification_summary()
assert summary["available"] == 3928
assert summary["qualified"] == 3861
assert summary["rejected"] == 67
def test_static_crossing_approaching_and_geometry_only_critical_cases_are_never_safe() -> None:
provider = DualEvidenceReplayThreatProvider(
pose_resolver=_Poses(),
body_frame_resolver=_BodyFrames(),
profile=load_replay_threat_profile(PROFILE_PATH),
)
current_frame = "frame-000002"
@@ -179,17 +211,17 @@ def test_static_crossing_approaching_and_geometry_only_critical_cases_are_never_
assert {item.decision for item in result} == {ThreatDecision.THREAT}
assert all(item.corridor_intersection is CorridorIntersection.INTERSECTS for item in result)
assert (
next(item for item in result if item.component_id == "approaching").ttc_seconds
is not None
next(item for item in result if item.component_id == "approaching").ttc_seconds is not None
)
assert (
"geometry-only-evidence"
in next(item for item in result if item.component_id == "geometry-only").reason_codes
)
assert "geometry-only-evidence" in next(
item for item in result if item.component_id == "geometry-only"
).reason_codes
def test_receding_and_static_outside_are_clear_but_incomplete_evidence_is_unknown() -> None:
provider = DualEvidenceReplayThreatProvider(
pose_resolver=_Poses(),
body_frame_resolver=_BodyFrames(),
profile=load_replay_threat_profile(PROFILE_PATH),
)
current_frame = "frame-000002"
@@ -246,7 +278,7 @@ def test_receding_and_static_outside_are_clear_but_incomplete_evidence_is_unknow
def test_semantic_hint_and_ephemeral_component_name_do_not_change_threat_geometry() -> None:
provider = DualEvidenceReplayThreatProvider(
pose_resolver=_Poses(),
body_frame_resolver=_BodyFrames(),
profile=load_replay_threat_profile(PROFILE_PATH),
)
history = (