From c7f3de24162fcd0f36886f26ddeeea5fff5aeff8 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:28:07 -0500 Subject: [PATCH] Clarify speed-limit type validation and settings test contracts --- .../starpilot/speed_limits/MAP_SOURCE.md | 52 ++----------------- openpilot/starpilot/speed_limits/advance.py | 2 +- .../speed_limits/tests/test_advance.py | 6 ++- prebuilt.json | 2 +- 4 files changed, 12 insertions(+), 50 deletions(-) diff --git a/openpilot/starpilot/speed_limits/MAP_SOURCE.md b/openpilot/starpilot/speed_limits/MAP_SOURCE.md index f40ef5756e..95b93ec60e 100644 --- a/openpilot/starpilot/speed_limits/MAP_SOURCE.md +++ b/openpilot/starpilot/speed_limits/MAP_SOURCE.md @@ -1,51 +1,9 @@ -# Live Maps source boundary +# Map source boundary (disabled) -`plannerd` subscribes to `mapdOut` when SLC transport is available. -`runtime.py` samples the Maps adapter when Maps is configured or a highest/lowest -selection policy considers it. The [controller contract](README.md) owns adoption, -confirmation and car-control behavior. +`map_source.py` decodes v2 `mapdOut` events and checks source ordering, boot-time freshness, and structural consistency. V1 remains decodable but cannot produce a candidate because it treated Python GPS Event MONOTONIC timestamps as BOOTTIME. It is not subscribed from plannerd and does not create a valid or absent SLC observation. A matched road can therefore be inspected in isolated tests without becoming a cruise ceiling, accepted-history fallback, or geographic identity claim. -`map_source.MapTracker` validates v2 wire structure, source ordering and freshness. -It exposes diagnostics, not a valid SLC candidate. `map_observation.MapSource` -projects a fresh `matchedLimit` diagnostic into a valid observation only when its -way-selection type is `current`. Possible, predicted and extended ways, missing -speed metadata, GPS/coverage loss and `matchedNoLimit` remain unknown or stale; -they do not mean regulatory absence. The map provider rejects multiple plausible -loaded-tile road IDs before selecting a current way and applies direction and -one-way checks. Provider and offline-package admission remain with the Maps -lifecycle and storage owners. +The boundary keeps the GPS observation time separate from computation and output times. V2 converts a Python GPS Event's MONOTONIC timestamp to BOOTTIME only after a post-start/resume fence and clock-pair check; an offset step invalidates queued fixes and advances source generation. External fixes expire after 500 ms, internal fixes after 2 s; computation and output expire after 250 ms. These are transport/input deadlines, not proof that the selected road is correct. Linux callers must provide `CLOCK_BOOTTIME` nanoseconds, matching cereal and mapd output; Apple uses monotonic as the host fallback. The current plannerd live `time.monotonic_ns()` is not interchangeable with boot time after Linux suspend, so no runtime subscription is wired here. -Map output uses BOOTTIME nanoseconds on Linux. The runtime samples a paired -MONOTONIC/BOOTTIME clock and passes BOOTTIME to the tracker; the clocks must not -be interchanged after suspend. Apple uses monotonic as the host fallback. -GPS observation, computation and output timestamps stay separate. External GPS -fixes expire after 500 ms, internal fixes after 2 s and car fixes after 2.5 s; -computation and output expire after 250 ms. V1 records remain decodable but cannot -create a live candidate because their GPS clock domain was ambiguous. +`matchedNoLimit` means a geographically matched sample with no posted numeric limit. It is not malformed and is never turned into SLC `ABSENT` by this disabled boundary. Old pre-v1 records, unknown versions, conflicting status/limit fields, stale evidence, and reordering cannot authorize a map candidate. Source-generation changes clear held evidence. A source change without a generation change, generation regression, boot-clock regression, or excessive restarts fails closed until an explicit tracker reset; late packets from retired producer sessions cannot restore a limit. No state is persisted. -Unknown versions, conflicting status/limit fields, stale evidence and reordering -cannot authorize a candidate. Source-generation changes clear held evidence. -A source change without a generation change, generation regression, clock -regression or excessive restarts fails closed until an explicit tracker reset; -late packets from retired producers cannot restore a limit. - -The observation identity binds the current drive, vehicle, producer, source -generation, GPS source and OSM way ID. It is not a durable identity for restoring -a limit in a later drive. Freshness and a unique loaded-tile match do not establish -real-road accuracy or current map metadata. Synthetic host tests verify these -software gates; they do not qualify Maps on the vehicle. - -The pinned data-only provider also appends `previewSpeedLimit`, `previewDistance`, -`previewBoundaryId` and `previewWayId`. They share the current match's original -GPS epoch, ordering, source TTL and verified snapshot. Older messages have no -qualified preview. Invalid optional preview fields cannot invalidate an otherwise -qualified current limit or enable early adoption. - -Lookahead walks at most 500 metres and 64 ways through a unique legal -continuation. It stops at unknown intermediate limits, missing coverage, -branches or cycles. It uses raw road geometry with no controller propagation, -retained minimum distance, curve-speed calculation or new vehicle subscriptions. -The boundary hash binds the snapshot, adjacent boundary ways, crossing position -and travel direction; it survives approach through several same-limit ways. -The SLC presentation identity additionally binds the current source evidence so -retired producers and stale actions cannot authorize a returned proposal. +`wayId`, way-selection type, and distance from center do not establish correct-road confidence. The provider can choose a parallel road, ramp, junction branch or wrong direction despite a fresh GPS fix and loaded tile. Real map provenance, tile integrity/freshness, matched-road alternatives, direction and segment applicability, route faults, and restart recovery need separate evidence before this boundary can ever yield a `VALID` or `ABSENT` map observation. Bare `wayId` is not a durable geographic identity. diff --git a/openpilot/starpilot/speed_limits/advance.py b/openpilot/starpilot/speed_limits/advance.py index 97781b7002..1549f3d148 100644 --- a/openpilot/starpilot/speed_limits/advance.py +++ b/openpilot/starpilot/speed_limits/advance.py @@ -58,7 +58,7 @@ class Advance: # shortcut: use fresh GPS distance without propagation; revisit only if replay shows material timing error. eligible = (can_offer and type(seconds) is int and 0 < seconds <= 10 and not standstill and - type(speed_mps) in (int, float) and math.isfinite(speed_mps) and speed_mps > 0 and + speed_mps is not None and type(speed_mps) in (int, float) and math.isfinite(speed_mps) and speed_mps > 0 and upcoming is not None and upcoming.distance_m <= speed_mps * seconds) if eligible and (encounter is None or upcoming.boundary_id != encounter.boundary_id): proposed = dict(observations) diff --git a/openpilot/starpilot/speed_limits/tests/test_advance.py b/openpilot/starpilot/speed_limits/tests/test_advance.py index e267228d7f..287f6b8ffe 100644 --- a/openpilot/starpilot/speed_limits/tests/test_advance.py +++ b/openpilot/starpilot/speed_limits/tests/test_advance.py @@ -380,13 +380,17 @@ def test_existing_settings_owner_saves_guards_and_repairs_one_timing_option(tmp_ assert keys.index('SLCAdvanceSeconds') == keys.index('SLCConfirmation')+1 assert row().available and row().value == 'Off' request = row_change(row()) + assert request is not None assert owner.apply(request) assert read_params(params).advance_seconds == 1 assert not owner.apply(request) request = row_change(row()) + assert request is not None allowed[0] = False assert not owner.apply(request) allowed[0] = True Path(params.get_param_path('SLCAdvanceSeconds')).write_bytes(b'broken') - assert row().choices == ('Off',) and owner.apply(row_default(row())) + assert row().choices == ('Off',) + request = row_default(row()) + assert request is not None and owner.apply(request) assert read_params(params).advance_seconds == 0 and read_params(params).enabled diff --git a/prebuilt.json b/prebuilt.json index cd55eb4fe2..943149c54b 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "175c353e6829ca067504e2c65ec1e7b7d369b7ec59b8d86d1ad349fc9e4ff023", "schema_version": 1} +{"protected_inventory_sha256": "45aa2de8b9c8d86f22f382b25ba847afff179057d13e9414e4900513a97c6705", "schema_version": 1}