Files
StarPilot/openpilot/starpilot/flm/tests/test_compact_parent_timing.py
T
2026-10-21 23:28:39 -05:00

110 lines
5.5 KiB
Python

"""Parent availability/attempt policy only; no fit or native proof is invented."""
from dataclasses import replace
from types import SimpleNamespace
import pytest
from openpilot.starpilot.flm import source_plant_runtime as runtime
from openpilot.starpilot.flm.original_source_capture import OriginalSourceCapture, MAX_CAPTURE_SPAN_NS
from openpilot.starpilot.flm.source_plant_fit import FIRST_SLOT_NS, BLOCK_NS, PURGE_NS
from openpilot.starpilot.flm.tests.test_fullrate_pipeline import snapshots
SECOND = 1_000_000_000
START = 200 * SECOND
def parent_fixture(age_ns, *, pose_hz=20, with_goals=True, last_attempt=0):
now = START + age_ns
control, output, _ = snapshots(now - 1_000_000)
# Existing ordinary-car evidence contract: no native ACK is manufactured.
control = replace(control, native_required=False, native_allowed=False, native_identity='',
native_observed_ns=0, native_expires_ns=0)
records = []
for tick in range(age_ns // 10_000_000 + 1):
elapsed = tick * 10_000_000
goal = (0.3 if ((elapsed - 39 * SECOND) // (2 * SECOND)) % 2 == 0 else -0.3) if with_goals and elapsed >= 39 * SECOND else 0.
records.append(SimpleNamespace(control_ns=START + elapsed, frame_sequence=tick + 1,
desired_physical_accel=goal))
capture = SimpleNamespace(epoch=0, epoch_start_ns=START, epoch_cutoff_ns=START + MAX_CAPTURE_SPAN_NS,
rows=tuple(SimpleNamespace(mono_ns=stamp) for stamp in range(START, now + 1, SECOND // pose_hz)))
exported = []
def original_source_packet(collector, windows, observed_ns):
exported.append((windows, observed_ns))
# A parent-selection fixture must never fabricate a qualified child packet.
raise ValueError('parent-only export boundary reached')
adapter = SimpleNamespace(envelope=object(), source_plant_submitted_epoch=-1, identity=control.identity,
baseline_revision=control.baseline_revision, snapshot_readiness=None,
replay=SimpleNamespace(records=tuple(records), checkpoints=((START, object()),), _frame_context=lambda frame: True),
original_source_packet=original_source_packet)
collector = SimpleNamespace(adapter=adapter, original_source_capture=capture, ready=[(control, output)],
last_fit=last_attempt, snapshot_attempts=0)
assert runtime._current_fit_pair_reason(collector, now) is None
return collector, now, exported
def test_predeclared_constants_and_plant_design_remain_separate():
assert runtime.MIN_NORMAL_JOB_AGE_NS == 60 * SECOND
assert runtime.NORMAL_JOB_ATTEMPT_SPACING_NS == 80 * SECOND
assert MAX_CAPTURE_SPAN_NS == 100 * SECOND
assert FIRST_SLOT_NS + 7 * (BLOCK_NS + PURGE_NS) + BLOCK_NS == 38_800_000_000
@pytest.mark.parametrize('age_ns,attempt', [(60 * SECOND - 1, False), (60 * SECOND, True)])
def test_complete_parent_geometry_first_attempt_boundary(age_ns, attempt):
collector, now, exported = parent_fixture(age_ns)
assert runtime.snapshot(collector, now) is None
assert bool(exported) is attempt
assert collector.snapshot_attempts == int(attempt)
assert collector.last_fit == (now if attempt else 0)
if attempt:
assert exported == [(((START, now - age_ns % 10_000_000),), now)]
assert collector.adapter.snapshot_readiness['reason'] == 'plant_snapshot_unavailable'
else:
assert collector.adapter.snapshot_readiness['reason'] == 'plant_fixed_cohort_still_collecting'
@pytest.mark.parametrize('pose_hz,goals', [(20, False), (10, True)])
def test_incomplete_geometry_never_consumes_attempt_or_cooldown(pose_hz, goals):
collector, now, exported = parent_fixture(60 * SECOND, pose_hz=pose_hz, with_goals=goals)
assert runtime.snapshot(collector, now) is None
assert exported == []
assert collector.snapshot_attempts == collector.last_fit == 0
assert collector.adapter.snapshot_readiness['reason'] == 'plant_later_original_goal_geometry'
assert collector.adapter.snapshot_readiness['candidateBundles'] < 8
@pytest.mark.parametrize('elapsed,attempt', [(80 * SECOND - 1, False), (80 * SECOND, True)])
def test_actual_attempt_spacing_boundary(elapsed, attempt):
collector, now, exported = parent_fixture(90 * SECOND)
collector.last_fit = now - elapsed
previous = collector.last_fit
assert runtime.snapshot(collector, now) is None
assert bool(exported) is attempt
assert collector.snapshot_attempts == int(attempt)
assert collector.last_fit == (now if attempt else previous)
def test_submitted_epoch_still_forbids_another_attempt():
collector, now, exported = parent_fixture(90 * SECOND)
collector.adapter.source_plant_submitted_epoch = collector.original_source_capture.epoch
assert runtime.snapshot(collector, now) is None
assert not exported and collector.snapshot_attempts == 0
def test_real_capture_keeps_fixed_hundred_second_epoch_and_no_reselection():
capture = OriginalSourceCapture()
receipt = {'epoch_id': 'first', 'start_ns': START, 'cutoff_ns': START + 100 * SECOND,
'identity': '1' * 64, 'source_epoch': '2' * 64, 'baseline_revision': 'baseline'}
capture.begin_epoch(receipt)
ordinal = capture.epoch
with pytest.raises(ValueError, match='cannot reselect'):
capture.begin_epoch({**receipt, 'epoch_id': 'overlap', 'start_ns': START + 60 * SECOND,
'cutoff_ns': START + 160 * SECOND})
assert capture.epoch == ordinal and capture.epoch_cutoff_ns == START + 100 * SECOND
capture.begin_epoch({**receipt, 'epoch_id': 'second', 'start_ns': START + 100 * SECOND,
'cutoff_ns': START + 200 * SECOND})
assert capture.epoch == ordinal + 1
assert capture.epoch_cutoff_ns - capture.epoch_start_ns == 100 * SECOND