diff --git a/sunnypilot/selfdrive/controls/lib/radar_distance/radar_distance.py b/sunnypilot/selfdrive/controls/lib/radar_distance/radar_distance.py index 519d3b1eb5..e51360b79b 100644 --- a/sunnypilot/selfdrive/controls/lib/radar_distance/radar_distance.py +++ b/sunnypilot/selfdrive/controls/lib/radar_distance/radar_distance.py @@ -44,7 +44,14 @@ LOW_SPEED_PASSTHROUGH_V = 5.0 # m/s: below this, no flicker-hold (holding a st # delay the launch); the churn smoother still runs down to CREEP_PASSTHROUGH_V CREEP_PASSTHROUGH_V = 1.0 # m/s: below this, full byte-stock passthrough (protect the stock stop distance) -SWITCH_DREL = 8.0 # m, dRel jump = a track switch (used by the instability detector + jump-guard) +SWITCH_DREL = 4.0 # m, dRel jump = a track switch (used by the instability detector + jump-guard). +# Route 550a71ee4c7a7fbe/000004b4, t~976.1s: a real fusion-glitch bounce (17.7<->12.3m across ~3 radar +# cycles, ~0.3s) sailed through both mechanisms at the old 8.0 threshold -- physically impossible for one +# real object at the vRel logged alongside it (~1-2 m/s closing would take seconds to cover 5m, not 0.1s). +# Telemetry (all 4 routes in that session, ~230-280k dRel-diff samples/route) shows a clean separation: the +# 99.5th-percentile ORDINARY frame-to-frame jitter is under ~2m on 3 of 4 routes (the 4th runs hotter due to +# genuinely more real target changes, not noise -- see radar_distance tests/telemetry notes), while glitch +# jumps cluster well above 4m. 4.0 sits with margin on both sides of that gap. # Jump-guard: a same-cycle dRel jump this far FARTHER, on a lead that never dropped status, is treated as a # fusion transient (not a real sudden separation) and held at the last-trusted value for a bounded number of diff --git a/sunnypilot/selfdrive/controls/lib/radar_distance/tests/test_radar_distance.py b/sunnypilot/selfdrive/controls/lib/radar_distance/tests/test_radar_distance.py index 1fdf88b88d..7f8a8d535f 100644 --- a/sunnypilot/selfdrive/controls/lib/radar_distance/tests/test_radar_distance.py +++ b/sunnypilot/selfdrive/controls/lib/radar_distance/tests/test_radar_distance.py @@ -278,6 +278,30 @@ def test_jump_guard_off_when_disabled(): assert c.smooth_radarstate(r) is r # disabled -> raw passthrough, no guard +def test_jump_guard_replays_real_route_sub_threshold_bounce(): + # route 550a71ee4c7a7fbe/000004b4--2bd66184db, t~976.08-976.48: dRel bounced 17.70 -> 12.32 -> ... -> 17.15 + # -> 12.04m across ~0.4s while vRel stayed -0.8 to -2.4 m/s -- physically impossible for one real object + # at that closing speed (5m in ~0.1s would need ~50 m/s, not ~1-2). This is the case that motivated + # lowering SWITCH_DREL from 8.0 to 4.0: the farther excursion (12.24 -> 17.15, a 4.91m jump) sailed through + # unguarded at the old threshold, producing a false-relief-then-correction whipsaw. A closer jump (e.g. + # 17.70 -> 12.32) always passes immediately regardless of threshold -- that invariant is untouched here. + c = ctrl() + raw = [ + (19.12, -2.32, 9.22, -0.67, -1), (17.95, -2.06, 9.39, -0.58, -1), (18.06, -1.90, 9.49, -0.60, -1), + (17.70, -1.84, 9.44, -0.52, -1), (12.32, -1.20, 10.01, -0.02, 2449), (12.12, -1.40, 9.75, -1.60, 2427), + (12.56, -1.20, 9.87, -1.45, 2427), (12.24, -1.05, 9.92, -1.29, 2427), (17.15, -2.39, 8.53, -0.85, -1), + (12.04, -0.82, 10.02, -0.97, 2427), (12.04, -0.82, 9.94, -0.85, 2427), (11.80, -0.85, 9.81, -0.78, 2427), + ] + out = None + seen = [] + for dRel, vRel, vLead, aLeadK, tid in raw: + out = c.smooth_radarstate(rs(lead(dRel=dRel, vRel=vRel, vLead=vLead, aLeadK=aLeadK, radarTrackId=tid))) + seen.append(out.leadOne.dRel) + assert seen[4] == pytest.approx(12.32) # the initial closer jump (17.70->12.32) passes immediately + assert seen[8] < 14.0 # the 12.24->17.15 farther excursion is held, not passed + assert out.leadOne.dRel == pytest.approx(11.80) # recovers exactly once raw resumes reporting close values + + def test_jump_guard_boundary_not_triggered(): c = ctrl() c.smooth_radarstate(rs(lead(dRel=30.0, vRel=-2.0, vLead=18.0)))