fix(long): SPORT's jerk_scale

This commit is contained in:
rav4kumar
2026-07-04 18:29:06 -07:00
parent 75416274ab
commit a2fbe20532
2 changed files with 24 additions and 4 deletions
@@ -15,7 +15,6 @@ from cereal import custom
AccelerationPersonality = custom.LongitudinalPlanSP.AccelerationPersonality
ECO = AccelerationPersonality.eco
NORMAL = AccelerationPersonality.normal
SPORT = AccelerationPersonality.sport
PERSONALITY_MIN = min(AccelerationPersonality.schema.enumerants.values())
PERSONALITY_MAX = max(AccelerationPersonality.schema.enumerants.values())
@@ -51,11 +50,18 @@ RISE_RATE_V = {
# upstream get_jerk_factor(personality) -- stock 1.0 for relaxed/standard, 0.5 for aggressive. JERK_SCALE
# multiplies into that same upstream factor (same lever stock's aggressive tier already uses), bounded to
# near a stop and ramped back to 1.0 (stock) by cruise speed.
# The v=0 knot is NOT monotone with personality -- verified via a closed-loop MPC harness (dead stop +
# departing lead, 3 scenarios): 0.60/0.45 both measurably beat stock 1.0 (0.3-0.65s faster to cross the
# should_stop 0.1 m/s^2 gate, 0 solver resets), but pushing lower is NOT "more relaxed = faster" -- 0.30 came
# back SLOWER than stock in all 3 scenarios (the MPC back-loads the ramp instead of front-loading it once
# A_CHANGE_COST/J_EGO_COST get too cheap to bother avoiding), and below ~0.15 the solver itself destabilizes
# (46-68% QP resets). SPORT's knot is pinned to the verified-good value (tied with NORMAL) rather than pushed
# lower for tier-consistency -- lower is not safe or effective here, unlike every other tier table in this file.
JERK_SCALE_BP = [0., 5.] # m/s
JERK_SCALE_V = {
ECO: [0.60, 1.0],
NORMAL: [0.45, 1.0],
SPORT: [0.30, 1.0],
SPORT: [0.45, 1.0],
}
# --- Onset jerk-cost relaxation (MPC INPUT: general accel<->decel-direction change, not just launch) ------
@@ -155,7 +155,7 @@ def test_jerk_scale_disabled_is_stock():
def test_jerk_scale_relaxed_near_stop_flat_at_speed():
for personality, relaxed in ((ECO, 0.60), (NORMAL, 0.45), (SPORT, 0.30)):
for personality, relaxed in ((ECO, 0.60), (NORMAL, 0.45), (SPORT, 0.45)):
ctrl = make_controller(personality=personality)
assert ctrl.get_jerk_scale(0.0) == pytest.approx(relaxed)
assert ctrl.get_jerk_scale(5.0) == pytest.approx(1.0) # back to stock by the v=5 knot
@@ -171,10 +171,15 @@ def test_jerk_scale_never_exceeds_stock():
def test_jerk_scale_tier_ordering_at_stop():
# NOT a strict SPORT<NORMAL<ECO ordering here (unlike every other tier table in this file) -- verified via
# a closed-loop MPC harness that pushing the v=0 knot lower than ~0.45 is counterproductive (the MPC
# back-loads the ramp instead of front-loading it), so SPORT is pinned to NORMAL's value instead of being
# pushed lower for tier-consistency. See the JERK_SCALE_V comment in constants.py.
eco = make_controller(personality=ECO).get_jerk_scale(0.0)
nrm = make_controller(personality=NORMAL).get_jerk_scale(0.0)
spt = make_controller(personality=SPORT).get_jerk_scale(0.0)
assert spt < nrm < eco # SPORT relaxes the most (most responsive launch), ECO the least
assert spt == pytest.approx(nrm)
assert nrm < eco
def test_jerk_scale_table_matches_constants():
@@ -184,6 +189,15 @@ def test_jerk_scale_table_matches_constants():
assert ctrl.get_jerk_scale(v) == pytest.approx(np.interp(v, JERK_SCALE_BP, JERK_SCALE_V[personality]))
def test_jerk_scale_v0_knot_stays_out_of_the_counterproductive_zone():
# Regression guard for the verified finding: pushing the v=0 knot below ~0.45 stops helping and starts
# hurting (measured via a closed-loop MPC harness -- 0.30 came back slower than stock in every scenario
# tested, and below ~0.15 the solver itself destabilizes). No tier's launch floor should regress into that
# zone even if someone re-tunes ECO/NORMAL/SPORT independently later.
for personality in (ECO, NORMAL, SPORT):
assert JERK_SCALE_V[personality][0] >= 0.40
# --- onset relax: fresh accel<->decel direction change, any speed ------------------------------------------
def test_onset_disabled_is_stock():