Revert "joplin"

This reverts commit 59d3c4dd66.
This commit is contained in:
firestarsdog
2026-09-11 22:30:29 -04:00
parent c7b9a77782
commit e517a83541
19 changed files with 89 additions and 586 deletions
-1
View File
@@ -632,7 +632,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}},
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"ShowBrakeStatus", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
{"ShowCPU", {PERSISTENT, BOOL, "0", "0", 3}},
+1 -14
View File
@@ -349,20 +349,7 @@ def _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible):
deadband_mph = VOLT_CC_LEAD_REQUEST_DEADBAND_MPH if lead_visible else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
request_deadband = deadband_mph * (CV.MPH_TO_KPH if ms_convert == CV.MS_TO_KPH else 1.0)
target_setpoint = None
v_cruise_kph = float(getattr(CS.out, "vCruise", 0.0))
if 0.0 < v_cruise_kph < 255.0:
is_metric = ms_convert == CV.MS_TO_KPH
target_setpoint = int(round(v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH))
moving_toward_target = target_setpoint is not None and (
(accel > 0.0 and speed_setpoint < target_setpoint) or
(accel < 0.0 and speed_setpoint > target_setpoint)
)
if target_setpoint is not None and accel > 0.0 and speed_setpoint >= target_setpoint:
return CruiseButtons.INIT, float("inf")
if not moving_toward_target and abs(requested_setpoint - speed_setpoint) <= request_deadband:
if abs(requested_setpoint - speed_setpoint) <= request_deadband:
return CruiseButtons.INIT, float("inf")
if accel == 0.0:
+28 -128
View File
@@ -947,7 +947,7 @@ class TestGMCarController:
assert len(msgs) == 1
def test_volt_cc_redneck_does_not_raise_stock_setpoint_above_max(self):
def test_volt_cc_redneck_holds_when_pseudo_speed_request_is_within_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
@@ -960,7 +960,7 @@ class TestGMCarController:
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
@@ -970,133 +970,33 @@ class TestGMCarController:
)
assert msgs == []
assert controller.apply_speed == 100
def test_volt_cc_redneck_tracks_max_inside_request_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=52.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=52.0 * CV.KPH_TO_MS),
vCruise=60.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.1), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 53
def test_volt_cc_redneck_tracks_max_down_inside_request_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=68.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=68.0 * CV.KPH_TO_MS),
vCruise=60.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 67
def test_volt_cc_redneck_holds_small_decel_request_at_max(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 100
def test_volt_cc_redneck_holds_medium_decel_request_at_max_without_lead(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.5), SimpleNamespace(is_metric=True), lead_visible=False,
)
assert msgs == []
assert controller.apply_speed == 100
def test_volt_cc_redneck_brakes_for_lead_inside_free_road_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.5), SimpleNamespace(is_metric=True), lead_visible=True,
)
assert len(msgs) == 1
assert controller.apply_speed == 99
def test_volt_cc_redneck_uses_smaller_request_deadband_with_lead(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True), lead_visible=True,
)
assert len(msgs) == 1
assert controller.apply_speed == 100
def test_volt_cc_redneck_accelerates_when_pseudo_speed_request_exceeds_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.1 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
@@ -1143,7 +1043,7 @@ class TestGMCarController:
out=SimpleNamespace(
vEgo=50.7 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=49.0 * CV.KPH_TO_MS),
vCruise=49.0,
vCruise=50.0,
),
)
+1 -6
View File
@@ -8,7 +8,6 @@ from openpilot.selfdrive.ui.onroad.starpilot.rivian_lateral_mode import rivian_l
from openpilot.selfdrive.ui.mici.onroad.speed_limit_utils import resolve_display_speed_limit_ms
from openpilot.selfdrive.ui.onroad.starpilot.navigation_card import NavigationCardRenderer
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
from openpilot.selfdrive.ui.onroad.exp_button import get_wheel_tint
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.utils import draw_circle_gradient_compat
from openpilot.system.ui.lib.multilang import tr
@@ -195,11 +194,7 @@ class HudRenderer(Widget):
controls_state = sm['controlsState']
car_state = sm['carState']
rivian_lateral_mode.update()
self._wheel_tint = get_wheel_tint(
getattr(car_state, "brakePressed", False),
rivian_lateral_mode.wheel_tint,
ui_state.ui_params.get_bool("ShowBrakeStatus"),
)
self._wheel_tint = rivian_lateral_mode.wheel_tint
v_cruise_cluster = car_state.vCruiseCluster
set_speed = (
+2 -14
View File
@@ -11,13 +11,6 @@ from openpilot.starpilot.common.experimental_state import (
)
BRAKE_WHEEL_COLOR = rl.Color(255, 0, 0, 255)
def get_wheel_tint(brake_pressed: bool, mode_tint: rl.Color | None, brake_status_enabled: bool) -> rl.Color | None:
return BRAKE_WHEEL_COLOR if brake_status_enabled and brake_pressed else mode_tint
class ExpButton(Widget):
def __init__(self, button_size: int, icon_size: int):
super().__init__()
@@ -109,13 +102,8 @@ class ExpButton(Widget):
texture = self._txt_exp if exp_mode else self._txt_wheel
color = self._white_color
tint = None
wheel_tint = get_wheel_tint(
getattr(ui_state.sm["carState"], "brakePressed", False),
self.wheel_tint,
self._params.get_bool("ShowBrakeStatus"),
)
if wheel_tint is not None:
tint = rl.Color(wheel_tint.r, wheel_tint.g, wheel_tint.b, self._white_color.a)
if self.wheel_tint is not None:
tint = rl.Color(self.wheel_tint.r, self.wheel_tint.g, self.wheel_tint.b, self._white_color.a)
rl.draw_circle(center_x, center_y, self._rect.width / 2, self._bg_color)
if tint is not None:
-17
View File
@@ -41,8 +41,6 @@ from openpilot.selfdrive.ui.onroad.starpilot.aethergauge import (
_is_lead,
_is_stop_light,
_lead_data,
_to_display_distance,
_to_display_speed,
)
from openpilot.starpilot.common.experimental_state import CEStatus
@@ -52,7 +50,6 @@ aethergauge.ui_state = mock_ui_state
@pytest.fixture(autouse=True)
def reset_ui_state():
mock_ui_state.sm.reset()
mock_ui_state.is_metric = False
mock_ui_state.conditional_status = CEStatus["OFF"]
mock_ui_state.starpilot_toggles.update({
"conditional_experimental_mode": True,
@@ -138,20 +135,6 @@ def test_is_stop_light():
assert not _is_stop_light()
@pytest.mark.parametrize(
("is_metric", "expected_distance", "expected_speed"),
[
(False, (33, "ft"), (22, "mph")),
(True, (10, "m"), (36, "km/h")),
],
)
def test_aether_gauge_uses_display_units(is_metric, expected_distance, expected_speed):
mock_ui_state.is_metric = is_metric
assert _to_display_distance(10.0) == expected_distance
assert _to_display_speed(10.0) == expected_speed
def test_is_curve_speed_follows_csc_activation_without_mode_gate(monkeypatch):
monkeypatch.setattr(aethergauge, "_csc_state", lambda: {"active": True, "curvature": 0.002})
assert _is_curve_speed()
@@ -293,32 +293,3 @@ def test_non_mici_wheel_icon_uses_rivian_tint(monkeypatch):
assert len(draws["textures"]) == 1
texture_color = draws["textures"][0][-1]
assert (texture_color.r, texture_color.g, texture_color.b, texture_color.a) == (0x4D, 0x9D, 0xFF, 255)
def test_non_mici_wheel_icon_turns_red_when_brakes_are_pressed(monkeypatch):
module, draws = load_exp_button(monkeypatch)
button = module.ExpButton(192, 144)
button.wheel_tint = FakeColor(0x4D, 0x9D, 0xFF, 255)
module.ui_state.ui_params.get_bool = lambda key, *args, **kwargs: key == "ShowBrakeStatus"
module.ui_state.sm["carState"].brakePressed = True
button._update_state()
button._render(FakeRectangle(0, 0, 192, 192))
assert len(draws["textures"]) == 1
texture_color = draws["textures"][0][-1]
assert (texture_color.r, texture_color.g, texture_color.b, texture_color.a) == (255, 0, 0, 255)
def test_non_mici_wheel_icon_brake_tint_is_disabled_by_default(monkeypatch):
module, draws = load_exp_button(monkeypatch)
button = module.ExpButton(192, 144)
button.wheel_tint = FakeColor(0x4D, 0x9D, 0xFF, 255)
module.ui_state.sm["carState"].brakePressed = True
button._update_state()
button._render(FakeRectangle(0, 0, 192, 192))
assert len(draws["textures"]) == 1
texture_color = draws["textures"][0][-1]
assert (texture_color.r, texture_color.g, texture_color.b, texture_color.a) == (0x4D, 0x9D, 0xFF, 255)
+8 -67
View File
@@ -35,19 +35,9 @@ STEER_DT = CarControllerParams.STEER_STEP * DT_CTRL
CURVATURE_LOOKAHEAD_MIN = 0.20
CURVATURE_LOOKAHEAD_MAX = 0.40
MACH_E_TURN_IN_LOOKAHEAD_EXTRA = 0.80
MACH_E_LOW_SPEED_TURN_IN_LOOKAHEAD_EXTRA = 1.60
MACH_E_LOW_SPEED_TURN_IN_START_SPEED = 2.0
MACH_E_LOW_SPEED_TURN_IN_FULL_SPEED = 3.0
MACH_E_LOW_SPEED_TURN_IN_MAX_SPEED = 9.0
MACH_E_LOW_SPEED_TURN_IN_FADE_SPEED = 12.0
MACH_E_TURN_IN_MIN_CURVATURE = 0.002
MACH_E_TURN_IN_FULL_CURVATURE = 0.008
MACH_E_TURN_IN_LAG_CURVATURE = 0.006
MACH_E_DIRECTION_CHANGE_MIN_SPEED = 9.0
MACH_E_DIRECTION_CHANGE_MIN_PREVIEW_CURVATURE = 0.0005
MACH_E_DIRECTION_CHANGE_FULL_PREVIEW_CURVATURE = 0.002
MACH_E_DIRECTION_CHANGE_MIN_LAG_CURVATURE = 0.0008
MACH_E_DIRECTION_CHANGE_FULL_LAG_CURVATURE = 0.0015
FORD_CURVATURE_LOOKAHEAD = {
CAR.FORD_EXPLORER_MK6: 0.20,
}
@@ -177,11 +167,10 @@ class FordLateralController:
def _current_curvature(CS) -> float:
return -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
def _blend_and_scale(self, desired: float, predicted: float, v_ego: float, current: float = 0.0,
allow_opposite_preview: bool = False) -> tuple[float, int]:
def _blend_and_scale(self, desired: float, predicted: float, v_ego: float, current: float = 0.0) -> tuple[float, int]:
blend = float(np.interp(abs(desired), [0.0, 0.001], [self.curvature_blend_low, self.curvature_blend_high]))
if self.CP.carFingerprint in FORD_CONSERVATIVE_PREVIEW_CARS:
if desired * predicted <= 0.0 and not allow_opposite_preview:
if desired * predicted <= 0.0:
blend = 0.0
elif current * predicted > 0.0 and abs(current) > abs(desired) and abs(predicted) > abs(desired):
blend *= abs(desired) / abs(predicted)
@@ -216,37 +205,6 @@ class FordLateralController:
))
return curvature_weight * lag_weight
@staticmethod
def _turn_in_lookahead_extra(v_ego: float) -> float:
return float(np.interp(
v_ego,
[MACH_E_LOW_SPEED_TURN_IN_START_SPEED, MACH_E_LOW_SPEED_TURN_IN_FULL_SPEED,
MACH_E_LOW_SPEED_TURN_IN_MAX_SPEED, MACH_E_LOW_SPEED_TURN_IN_FADE_SPEED],
[MACH_E_TURN_IN_LOOKAHEAD_EXTRA, MACH_E_LOW_SPEED_TURN_IN_LOOKAHEAD_EXTRA,
MACH_E_LOW_SPEED_TURN_IN_LOOKAHEAD_EXTRA, MACH_E_TURN_IN_LOOKAHEAD_EXTRA],
))
def _direction_change_preview_weight(self, desired: float, preview: float, current: float) -> float:
if self.CP.carFingerprint not in FORD_CONSERVATIVE_PREVIEW_CARS:
return 0.0
if desired * preview >= 0.0 or desired * self.desired_curvature_last <= 0.0:
return 0.0
if (abs(desired) >= abs(self.desired_curvature_last) or desired * current <= 0.0 or
abs(current) <= abs(desired)):
return 0.0
preview_weight = float(np.interp(
abs(preview),
[MACH_E_DIRECTION_CHANGE_MIN_PREVIEW_CURVATURE, MACH_E_DIRECTION_CHANGE_FULL_PREVIEW_CURVATURE],
[0.0, 1.0],
))
lag_weight = float(np.interp(
abs(current) - abs(desired),
[MACH_E_DIRECTION_CHANGE_MIN_LAG_CURVATURE, MACH_E_DIRECTION_CHANGE_FULL_LAG_CURVATURE],
[0.0, 1.0],
))
return preview_weight * lag_weight
def _manual_turn(self, CC, CS) -> bool:
if not CC.latActive:
self.human_turn.reset()
@@ -310,30 +268,13 @@ class FordLateralController:
lookahead = self._curvature_lookahead()
predicted = self._predicted_curvature(v_ego, lookahead)
desired = float(actuators.curvature)
allow_opposite_preview = False
if self.CP.carFingerprint in FORD_CONSERVATIVE_PREVIEW_CARS:
direction_change_predicted = self._predicted_curvature(v_ego, lookahead + MACH_E_TURN_IN_LOOKAHEAD_EXTRA)
direction_change_weight = 0.0
if v_ego > MACH_E_DIRECTION_CHANGE_MIN_SPEED and not CS.out.steeringPressed and not self._lane_change()[0]:
direction_change_weight = self._direction_change_preview_weight(desired, direction_change_predicted, current)
if direction_change_weight > 0.0:
predicted = float(np.interp(direction_change_weight, [0.0, 1.0], [predicted, direction_change_predicted]))
allow_opposite_preview = True
else:
turn_in_predicted = direction_change_predicted
turn_in_lookahead_extra = self._turn_in_lookahead_extra(v_ego)
if (turn_in_lookahead_extra > MACH_E_TURN_IN_LOOKAHEAD_EXTRA and
desired * self.desired_curvature_last >= 0.0 and
abs(desired) > abs(self.desired_curvature_last)):
low_speed_turn_in_predicted = self._predicted_curvature(v_ego, lookahead + turn_in_lookahead_extra)
if (desired * low_speed_turn_in_predicted > 0.0 and
abs(low_speed_turn_in_predicted) > abs(turn_in_predicted)):
turn_in_predicted = low_speed_turn_in_predicted
turn_in_weight = self._turn_in_preview_weight(desired, turn_in_predicted, current)
if turn_in_weight > 0.0:
turn_in_target = float(np.copysign(max(abs(desired), abs(turn_in_predicted)), desired))
predicted = float(np.interp(turn_in_weight, [0.0, 1.0], [predicted, turn_in_target]))
requested, precision = self._blend_and_scale(desired, predicted, v_ego, current, allow_opposite_preview)
turn_in_predicted = self._predicted_curvature(v_ego, lookahead + MACH_E_TURN_IN_LOOKAHEAD_EXTRA)
turn_in_weight = self._turn_in_preview_weight(desired, turn_in_predicted, current)
if turn_in_weight > 0.0:
turn_in_target = float(np.copysign(max(abs(desired), abs(turn_in_predicted)), desired))
predicted = float(np.interp(turn_in_weight, [0.0, 1.0], [predicted, turn_in_target]))
requested, precision = self._blend_and_scale(desired, predicted, v_ego, current)
self.desired_curvature_last = desired
if v_ego > 9.0:
-144
View File
@@ -170,110 +170,6 @@ def test_mach_e_turn_in_preview_is_not_carried_into_unwind(controller):
desired=0.008, preview=0.009, current=0.004) == 0.0
@pytest.mark.parametrize("speed,expected", (
(1.0, 0.80),
(2.0, 0.80),
(2.5, 1.20),
(3.0, 1.60),
(8.0, 1.60),
(9.0, 1.60),
(10.5, 1.20),
(12.0, 0.80),
(15.0, 0.80),
))
def test_mach_e_turn_in_lookahead_extra_fades_by_speed(controller, speed, expected):
assert controller._turn_in_lookahead_extra(speed) == pytest.approx(expected)
@pytest.mark.parametrize("sign", (1.0, -1.0))
def test_mach_e_direction_change_preview_leads_a_lagging_unwind(controller, sign):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.desired_curvature_last = sign * 0.002
weight = controller._direction_change_preview_weight(
desired=sign * 0.0015, preview=-sign * 0.002, current=sign * 0.003)
assert weight == pytest.approx(1.0)
@pytest.mark.parametrize("desired,preview,current,last", (
(0.0015, 0.002, 0.003, 0.002), # no predicted direction change
(0.002, -0.002, 0.003, 0.0015), # desired curvature is still rising
(0.0015, -0.002, -0.001, 0.002), # vehicle already changed direction
(0.0015, -0.002, 0.0022, 0.002), # measured unwind lag is too small
))
def test_mach_e_direction_change_preview_rejects_unrelated_states(
controller, desired, preview, current, last):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.desired_curvature_last = last
assert controller._direction_change_preview_weight(desired, preview, current) == 0.0
def test_mach_e_direction_change_preview_can_cross_the_current_desired_path(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
requested, _ = controller._blend_and_scale(
0.0015, -0.002, 15.0, current=0.003, allow_opposite_preview=True)
assert requested == pytest.approx(0.0001)
def test_mach_e_direction_change_preview_uses_far_path_when_unwind_lags(controller, monkeypatch):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.sm["liveDelay"].lateralDelay = 0.4
controller.desired_curvature_last = 0.002
blend_inputs = []
monkeypatch.setattr(
controller, "_predicted_curvature",
lambda _v_ego, lookahead: 0.002 if lookahead < 1.0 else -0.002,
)
monkeypatch.setattr(
controller, "_blend_and_scale",
lambda desired, predicted, v_ego, current, allow_opposite_preview=False:
blend_inputs.append((desired, predicted, v_ego, current, allow_opposite_preview)) or (0.0, 1),
)
controller.update(
SimpleNamespace(latActive=True), car_state(speed=15.0, curvature=0.003),
SimpleNamespace(curvature=0.0015),
)
assert blend_inputs == [(pytest.approx(0.0015), pytest.approx(-0.002), pytest.approx(15.0),
pytest.approx(0.003), True)]
@pytest.mark.parametrize("speed,steering_pressed,lane_change", (
(8.0, False, False),
(9.0, False, False),
(15.0, True, False),
(15.0, False, True),
))
def test_mach_e_direction_change_preview_is_bypassed_outside_its_operating_state(
controller, monkeypatch, speed, steering_pressed, lane_change):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.desired_curvature_last = 0.002
monkeypatch.setattr(controller, "_predicted_curvature", lambda _v_ego, lookahead: -0.002)
monkeypatch.setattr(controller, "_lane_change", lambda: (lane_change, 2 if lane_change else 0))
monkeypatch.setattr(
controller, "_direction_change_preview_weight",
lambda *_args: pytest.fail("direction-change preview must be bypassed"),
)
controller.update(
SimpleNamespace(latActive=True),
car_state(speed=speed, curvature=0.003, steering_pressed=steering_pressed),
SimpleNamespace(curvature=0.0015),
)
def test_non_mach_e_direction_change_preview_is_unchanged(controller):
controller.desired_curvature_last = 0.002
assert controller._direction_change_preview_weight(
desired=0.0015, preview=-0.002, current=0.003) == 0.0
def test_mach_e_turn_in_preview_uses_extra_model_horizon(controller, monkeypatch):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.sm["liveDelay"].lateralDelay = 0.4
@@ -287,49 +183,9 @@ def test_mach_e_turn_in_preview_uses_extra_model_horizon(controller, monkeypatch
SimpleNamespace(curvature=0.010),
)
assert lookaheads == [pytest.approx(0.4), pytest.approx(1.2), pytest.approx(2.0)]
def test_mach_e_turn_in_preview_keeps_existing_horizon_above_fade_speed(controller, monkeypatch):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.sm["liveDelay"].lateralDelay = 0.4
controller.desired_curvature_last = 0.007
lookaheads = []
monkeypatch.setattr(controller, "_predicted_curvature",
lambda _v_ego, lookahead: lookaheads.append(lookahead) or 0.012)
controller.update(
SimpleNamespace(latActive=True), car_state(speed=15.0, curvature=0.002),
SimpleNamespace(curvature=0.010),
)
assert lookaheads == [pytest.approx(0.4), pytest.approx(1.2)]
def test_mach_e_low_speed_turn_in_preview_cannot_weaken_existing_preview(controller, monkeypatch):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.sm["liveDelay"].lateralDelay = 0.4
controller.desired_curvature_last = 0.007
blend_inputs = []
def predicted_curvature(_v_ego, lookahead):
return {0.4: 0.006, 1.2: 0.010, 2.0: 0.004}[round(lookahead, 1)]
monkeypatch.setattr(controller, "_predicted_curvature", predicted_curvature)
monkeypatch.setattr(
controller, "_blend_and_scale",
lambda desired, predicted, v_ego, current, allow_opposite_preview=False:
blend_inputs.append((desired, predicted)) or (0.0, 1),
)
controller.update(
SimpleNamespace(latActive=True), car_state(speed=8.0, curvature=0.002),
SimpleNamespace(curvature=0.008),
)
assert blend_inputs == [(pytest.approx(0.008), pytest.approx(0.010))]
def test_non_mach_e_does_not_request_extra_model_horizon(controller, monkeypatch):
controller.sm["liveDelay"].lateralDelay = 0.4
lookaheads = []
@@ -2863,16 +2863,6 @@
"parent_key": "CustomUI",
"settings_tier": "simple"
},
{
"key": "ShowBrakeStatus",
"label": "Show Brake Status",
"description": "Tint the on-screen steering-wheel icon red while the car reports that the brake pedal is pressed.",
"picker_description": "Tints the on-screen steering-wheel icon red while braking.",
"data_type": "bool",
"ui_type": "toggle",
"galaxy_only": true,
"settings_tier": "simple"
},
{
"key": "ModelUI",
"label": "Model UI",
@@ -187,18 +187,12 @@ body {
.content {
color: var(--main-fg);
flex-grow: 1;
margin-left: 0;
margin-left: var(--sidebar-width, 250px);
overflow-y: auto;
padding-bottom: var(--padding-xl);
padding-left: var(--padding-lg);
}
@media only screen and (min-width: 768px) {
html.galaxy-sidebar-pinned .content {
margin-left: var(--sidebar-width, 250px);
}
}
.embedded #sidebar,
.embedded #sidebar_shell,
.embedded #sidebarUnderlay {
@@ -316,7 +310,7 @@ a {
/* ――― Snackbar wrapper ――― */
#snackbar_wrapper {
bottom: var(--snackbar-offset, 30px);
left: 0;
left: var(--sidebar-width);
margin: 0 auto;
position: fixed;
right: 0;
@@ -324,12 +318,6 @@ a {
z-index: var(--z-overlay);
}
@media only screen and (min-width: 768px) {
html.galaxy-sidebar-pinned #snackbar_wrapper {
left: var(--sidebar-width);
}
}
/* ――― Animations ――― */
@keyframes fadein {
from {
@@ -354,7 +342,7 @@ a {
}
/* ――― Breakpoint overrides ――― */
@media only screen and (max-width: 767px) {
@media only screen and (max-width: var(--breakpoint-md)) {
.content {
margin-left: 0;
}
@@ -447,4 +435,4 @@ a {
::-webkit-scrollbar-thumb {
background-color: var(--thumb-color);
border-radius: var(--border-radius-sm);
}
}
@@ -251,16 +251,9 @@
.map-wrapper {
height: 100vh;
left: 0;
left: var(--sidebar-width);
position: absolute;
width: 100vw;
}
@media only screen and (min-width: 768px) {
html.galaxy-sidebar-pinned .map-wrapper {
left: var(--sidebar-width);
width: calc(100vw - var(--sidebar-width));
}
width: calc(100vw - var(--sidebar-width));
}
.mapboxgl-popup-content {
@@ -16,7 +16,7 @@ import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=app-
import { RouteRecordings } from "/assets/components/recordings/dashcam_routes.js"
import { SettingsView } from "/assets/components/settings.js?v=router-cycle-fix-5"
import { ScreenRecordings } from "/assets/components/recordings/screen_recordings.js"
import { Sidebar } from "/assets/components/sidebar.js?v=sidebar-pin-2"
import { Sidebar } from "/assets/components/sidebar.js?v=controllers-nav-1"
import { SentryMode } from "/assets/components/tools/sentry.js"
import { SpeedLimits } from "/assets/components/tools/speed_limits.js"
import { ModelManager } from "/assets/components/tools/model_manager.js?v=20260906a"
@@ -1,14 +1,11 @@
#menu_button {
align-items: center;
background-color: var(--main-fg);
border: 0;
border-radius: 50%;
bottom: var(--border-radius-xl);
cursor: pointer;
display: flex;
height: 50px;
justify-content: center;
padding: 0;
position: fixed;
right: var(--border-radius-xl);
width: 50px;
@@ -73,12 +70,11 @@
font-size: var(--border-radius-xl);
height: 100dvh;
justify-content: space-between;
left: calc(-1 * var(--sidebar-width));
left: 0;
min-width: var(--sidebar-width);
overflow-y: auto;
position: fixed;
top: 0;
transition: var(--transition-fast);
width: var(--sidebar-width);
z-index: var(--z-sidebar);
}
@@ -198,29 +194,10 @@
margin: 0;
}
.sidebar-pin-button {
background: transparent;
border: 0;
color: var(--text-muted);
cursor: pointer;
font-size: var(--font-size-lg);
margin-left: auto;
padding: var(--padding-xs);
}
.sidebar-pin-button:hover,
.sidebar-pin-button:focus-visible {
color: var(--text-color);
}
.sidebar.visible {
left: 0;
}
.sidebar.pinned {
left: 0;
}
.sidebar_header a:last-of-type {
color: var(--text-muted);
font-size: var(--font-size-xs);
@@ -253,13 +230,15 @@
transform: var(--hover-scale-sm);
}
@media only screen and (max-width: 767px) {
@media only screen and (max-width: 768px) and (orientation: portrait) {
#sidebarUnderlay {
display: block;
}
.sidebar {
left: calc(-1 * var(--sidebar-width));
overflow-y: scroll;
transition: var(--transition-fast);
}
.sidebar .menu_section>li>a {
@@ -275,10 +254,34 @@
@media only screen and (min-width: 768px) {
#menu_button {
display: flex;
}
html.galaxy-sidebar-pinned #menu_button {
display: none;
}
}
@media only screen and (max-width: var(--breakpoint-md)) {
#sidebarUnderlay {
display: block;
}
.sidebar {
left: calc(-1 * var(--sidebar-width));
overflow-y: scroll;
transition: var(--transition-fast);
}
.sidebar .menu_section>li>a {
font-size: var(--padding-sm);
}
.sidebar .title {
flex-direction: row;
font-size: var(--border-radius-xl);
margin: 0;
}
}
@media only screen and (min-width: var(--breakpoint-md)) {
#menu_button {
display: none;
}
}
@@ -37,40 +37,6 @@ const MENU_ITEMS = {
};
let galaxyDeveloperMode = false;
const SIDEBAR_PINNED_KEY = "galaxySidebarPinned";
let sidebarPinnedPreference = null;
function defaultSidebarPinned() {
try {
return window.matchMedia?.("(min-width: 768px)")?.matches ?? window.innerWidth >= 768;
} catch {
return false;
}
}
function sidebarPinned() {
if (sidebarPinnedPreference !== null) return sidebarPinnedPreference;
let stored;
try {
stored = window.localStorage?.getItem(SIDEBAR_PINNED_KEY);
} catch {
stored = null;
}
if (stored === "true" || stored === "false") {
sidebarPinnedPreference = stored === "true";
return sidebarPinnedPreference;
}
return defaultSidebarPinned();
}
function setSidebarPinned(pinned) {
sidebarPinnedPreference = Boolean(pinned);
try {
window.localStorage?.setItem(SIDEBAR_PINNED_KEY, String(sidebarPinnedPreference));
} catch {
}
document.documentElement.classList.toggle("galaxy-sidebar-pinned", sidebarPinnedPreference);
}
function matchesPath(currentPath, link) {
if (link === "/") return currentPath === "/";
@@ -126,29 +92,14 @@ function bindSidebarHandlers() {
if (!menuButton || !underlay) return;
const pinButton = document.getElementById("sidebar_pin_button");
pinButton?.addEventListener("click", () => {
const pinned = !sidebarPinned();
setSidebarPinned(pinned);
const sidebar = document.getElementById("sidebar");
hideSidebar();
sidebar?.classList.toggle("pinned", pinned);
pinButton.setAttribute("aria-pressed", String(pinned));
pinButton.setAttribute("title", pinned ? "Unpin navigation" : "Pin navigation");
pinButton.querySelector("i")?.classList.toggle("bi-pin-angle-fill", pinned);
pinButton.querySelector("i")?.classList.toggle("bi-pin-angle", !pinned);
});
if (menuButton.dataset.boundClick !== "1") {
menuButton.dataset.boundClick = "1";
if (!window.__theGalaxySidebarMenuBound) {
window.__theGalaxySidebarMenuBound = true;
menuButton.addEventListener("click", () => {
const sidebar = document.getElementById("sidebar");
const currentUnderlay = document.getElementById("sidebarUnderlay");
if (!sidebar || !currentUnderlay) return;
if (sidebar.classList.contains("pinned")) return;
const open = sidebar.classList.toggle("visible");
currentUnderlay.classList.toggle("hidden", !open);
menuButton.setAttribute("aria-expanded", String(open));
sidebar.classList.toggle("visible");
currentUnderlay.classList.toggle("hidden");
});
}
@@ -181,25 +132,19 @@ function renderSidebarIntoShell(currentPath) {
if (!shell) return;
const activePath = currentPath || window.location.pathname;
const pinned = sidebarPinned();
const sectionsMarkup = Object.entries(MENU_ITEMS)
.map(([section, links]) => buildSectionMarkup(section, links, activePath))
.join("");
shell.innerHTML = `
<div id="sidebarUnderlay" class="hidden"></div>
<div id="sidebar" class="sidebar${pinned ? " pinned" : ""}">
<div id="sidebar" class="sidebar">
<div>
<div class="title">
<img class="logo" src="/assets/images/main_logo.png" alt="Galaxy logo" />
<div class="title_text sidebar_header">
<p>Galaxy</p>
</div>
<button id="sidebar_pin_button" class="sidebar-pin-button" type="button"
aria-label="Toggle pinned navigation" aria-pressed="${pinned}"
title="${pinned ? "Unpin navigation" : "Pin navigation"}">
<i class="bi ${pinned ? "bi-pin-angle-fill" : "bi-pin-angle"}"></i>
</button>
</div>
<hr />
${sectionsMarkup}
@@ -212,7 +157,6 @@ function renderSidebarIntoShell(currentPath) {
export function Sidebar(currentPath) {
setTimeout(() => {
document.documentElement.classList.toggle("galaxy-sidebar-pinned", sidebarPinned());
renderSidebarIntoShell(currentPath);
refreshGalaxyDeveloperMode();
}, 0);
@@ -67,7 +67,6 @@ export function showSidebar() {
const html = document.documentElement
document.getElementById("sidebar")?.classList.add("visible")
document.getElementById("sidebarUnderlay")?.classList.remove("hidden")
document.getElementById("menu_button")?.setAttribute("aria-expanded", "true")
html.classList.add("no_scroll")
}
@@ -78,7 +77,6 @@ export function hideSidebar() {
const html = document.documentElement
document.getElementById("sidebar")?.classList.remove("visible")
document.getElementById("sidebarUnderlay")?.classList.add("hidden")
document.getElementById("menu_button")?.setAttribute("aria-expanded", "false")
html.classList.remove("no_scroll")
}
@@ -17,14 +17,14 @@
<link rel="stylesheet" href="/assets/vendor/fonts/fonts.css">
<link rel="stylesheet" href="/assets/components/home/home.css">
<link rel="stylesheet" href="/assets/components/main.css?v=sidebar-pin-2">
<link rel="stylesheet" href="/assets/components/main.css">
<link rel="stylesheet" href="/assets/components/modal.css">
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=sidebar-pin-2">
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=nav-search-context-2">
<link rel="stylesheet" href="/assets/components/navigation/navigation_keys.css?v=app-keys-session-1">
<link rel="stylesheet" href="/assets/components/recordings/dashcam_routes.css">
<link rel="stylesheet" href="/assets/components/recordings/screen_recordings.css">
<link rel="stylesheet" href="/assets/components/settings.css">
<link rel="stylesheet" href="/assets/components/sidebar.css?v=sidebar-pin-2">
<link rel="stylesheet" href="/assets/components/sidebar.css">
<link rel="stylesheet" href="/assets/components/tailscale/tailscale.css">
<link rel="stylesheet" href="/assets/components/tools/doors.css">
<link rel="stylesheet" href="/assets/components/tools/error_logs.css">
@@ -81,9 +81,9 @@
<body>
<div id="app"></div>
<!-- Menu button for phone -->
<button id="menu_button" type="button" aria-label="Open navigation" aria-controls="sidebar" aria-expanded="false">
<div id="menu_button">
<i class="bi bi-list"></i>
</button>
</div>
<!-- Snackbar for messages -->
<div id="snackbar_wrapper"></div>
</body>
@@ -75,15 +75,6 @@ def test_galaxy_new_ui_is_the_visible_default_choice():
assert "Galaxy (old)" in galaxy_default["description"]
def test_brake_status_toggle_is_galaxy_only():
setting = _params_by_section(_layout())["Visual (Display & UI)"]["ShowBrakeStatus"]
assert _declared_default("ShowBrakeStatus") == "0"
assert setting["galaxy_only"] is True
assert setting["settings_tier"] == "simple"
assert setting["ui_type"] == "toggle"
def test_ford_lateral_controls_are_ford_only_and_galaxy_only():
lateral = _params_by_section(_layout())["Lateral (Steering)"]
ford_keys = {
@@ -25,9 +25,6 @@ def test_router_and_settings_cache_bust_is_consistent():
assert "/assets/components/settings.js?v=router-cycle-fix-5" in router
assert "/assets/components/router.js?v=router-cycle-fix-8" in index
assert "/assets/components/sidebar.js?v=sidebar-pin-2" in router
assert "/assets/components/main.css?v=sidebar-pin-2" in index
assert "/assets/components/sidebar.css?v=sidebar-pin-2" in index
def test_bluetooth_actions_use_reactive_disabled_bindings():
@@ -111,27 +108,6 @@ def test_bluetooth_and_controllers_sidebar_order():
controllers = source.index('{ name: "Controllers"')
assert toggles < bluetooth < sentry < controllers
def test_sidebar_pin_state_and_responsive_drawer_are_wired():
sidebar = SIDEBAR_PATH.read_text(encoding="utf-8")
template = INDEX_PATH.read_text(encoding="utf-8")
sidebar_css = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/sidebar.css").read_text(encoding="utf-8")
main_css = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/main.css").read_text(encoding="utf-8")
navigation_css = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.css").read_text(encoding="utf-8")
assert 'window.localStorage?.getItem(SIDEBAR_PINNED_KEY)' in sidebar
assert 'stored === "true"' in sidebar and 'defaultSidebarPinned' in sidebar
assert "try {" in sidebar
assert 'menuButton.dataset.boundClick !== "1"' in sidebar
assert 'class="sidebar-pin-button"' in sidebar
assert '<button id="menu_button"' in template
assert 'aria-controls="sidebar"' in template
assert "@media only screen and (max-width: 767px)" in sidebar_css
assert "@media only screen and (min-width: 768px)" in sidebar_css
assert "@media only screen and (min-width: var(--breakpoint-md))" not in sidebar_css
assert "html.galaxy-sidebar-pinned .content" in main_css
assert "html.galaxy-sidebar-pinned .map-wrapper" in navigation_css
def test_model_laboratory_is_wired_into_classic_and_mobile_navigation():
router = ROUTER_PATH.read_text(encoding="utf-8")
sidebar = SIDEBAR_PATH.read_text(encoding="utf-8")