feat(perception): resume full graph on fresh input without restarting models

This commit is contained in:
DCCONSTRUCTIONS
2026-09-02 15:54:13 +03:00
parent 03a7e730ae
commit 70927ea585
10 changed files with 541 additions and 146 deletions
+61
View File
@@ -21,6 +21,67 @@ def pilot(monkeypatch):
return lambda name: importlib.import_module(name)
def test_full_graph_reset_replaces_temporal_state_not_models(pilot):
module = pilot("pilot_graph")
root = Path(__file__).resolve().parents[1] / "config/perception"
graph = module.JointGraph.__new__(module.JointGraph)
graph.surface = module.K1LocalSurfaceShadowEstimator()
graph.store = module.CurrentStore(
module.load_geometry_profile(root / "m4-geometry-association-v1.json")
)
profile = module.load_temporal_motion_profile(root / "m4-temporal-motion-v1.json")
graph.temporal = module.BoundedSpatialTemporalProvider(
point_resolver=graph.store, profile=profile
)
graph.motion = module.ClassIndependentMotionEstimator(profile=profile)
graph.rolling = module.RollingLocalObstacleMapProvider(
pose_resolver=graph.store,
profile=module.load_rolling_map_profile(root / "m4-rolling-local-map-v1.json"),
)
graph.threat = module.DualEvidenceReplayThreatProvider(
body_frame_resolver=graph.store,
profile=module.load_replay_threat_profile(root / "m4-replay-threat-v3.json"),
)
graph.temporal_resets = 0
graph.backend = graph.detector = graph.tgs = model = object()
for cache in (
graph.surface._cache,
graph.store.body_history,
graph.temporal._components,
graph.rolling._cells,
):
cache["old"] = object()
graph.surface._previous_surface = (1, 2, 3, 4)
graph.temporal._previous_sequence = graph.rolling._previous_sequence = 32
old_store = graph.store
state = graph.reset_temporal()
assert set(state["previous"].values()) == {1}
assert set(state["current"].values()) == {0}
assert graph.temporal._previous_sequence is graph.rolling._previous_sequence is None
assert graph.surface._previous_surface is None and graph.temporal_resets == 1
assert graph.store is not old_store
assert (
graph.temporal.point_resolver
is graph.rolling.pose_resolver
is graph.threat.body_frame_resolver
is graph.store
)
assert graph.backend is graph.detector is graph.tgs is model
assert graph.temporal.profile is graph.motion.profile is profile
@pytest.mark.parametrize(
"values", [["0:1"], ["10:0"], ["10:5001"], ["a:1"], ["10:1", "9:1"], ["1:1"] * 5]
)
def test_source_gap_plan_is_bounded(pilot, values):
with pytest.raises(ValueError):
pilot("pilot_binary_source").input_gaps(values)
def test_source_gap_plan_preserves_explicit_sequence_and_duration(pilot):
assert pilot("pilot_binary_source").input_gaps(["16:150", "72:2200"]) == [(16, 150), (72, 2200)]
def test_pending_overflow_is_explicit_and_does_not_evict_active(pilot):
queue = pilot("pilot_queue").Mailbox(capacity=2, byte_limit=100)
@@ -137,7 +137,7 @@ def test_active_old_callback_cannot_publish_or_be_freed_early(harness):
run.check_current(run.start) # In-flight IPC can drain without poisoning the child.
with pytest.raises(StreamSuspended):
run.begin_input(run.start)
with pytest.raises(ValueError, match="still owns"):
with pytest.raises(StreamSuspended, match="still active"):
run.begin_input(run.start)
run.mailbox.release(packet, discard_reason="input-gap")
epoch = run.begin_input(run.start)