diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering.py b/selfdrive/ui/sunnypilot/layouts/settings/steering.py
index 14d840138e..a4c1608a06 100644
--- a/selfdrive/ui/sunnypilot/layouts/settings/steering.py
+++ b/selfdrive/ui/sunnypilot/layouts/settings/steering.py
@@ -120,20 +120,13 @@ class SteeringLayout(Widget):
def _update_state(self):
super()._update_state()
- torque_allowed = True
+ # Torque enforcement now handled in UIState.update_params() (shared by C3X + MICI)
+ torque_allowed = ui_state.CP is not None and ui_state.CP.steerControlType != car.CarParams.SteerControlType.angle
if ui_state.CP is not None:
mads_main_desc = self._mads_limited_desc if self._mads_settings_layout._mads_limited_settings() else self._mads_full_desc
self._mads_toggle.set_description(f"{mads_main_desc}
{self._mads_base_desc}")
-
- if ui_state.CP.steerControlType == car.CarParams.SteerControlType.angle:
- ui_state.params.remove("EnforceTorqueControl")
- ui_state.params.remove("NeuralNetworkLateralControl")
- torque_allowed = False
else:
self._mads_toggle.set_description(f"{self._mads_check_compat_desc}
{self._mads_base_desc}")
- ui_state.params.remove("EnforceTorqueControl")
- ui_state.params.remove("NeuralNetworkLateralControl")
- torque_allowed = False
self._mads_toggle.action_item.set_enabled(ui_state.is_offroad())
self._mads_settings_button.action_item.set_enabled(ui_state.is_offroad() and self._mads_toggle.action_item.get_state())
diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py
index 80c05be8bb..d49bbb2486 100644
--- a/selfdrive/ui/sunnypilot/ui_state.py
+++ b/selfdrive/ui/sunnypilot/ui_state.py
@@ -6,7 +6,7 @@ See the LICENSE.md file in the root directory for more details.
"""
from enum import Enum
-from cereal import messaging, log, custom
+from cereal import messaging, log, car, custom
from openpilot.common.params import Params
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import OnroadBrightness
from openpilot.sunnypilot.sunnylink.sunnylink_state import SunnylinkState
@@ -132,6 +132,8 @@ class UIStateSP:
if CP_SP_bytes is not None:
self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP)
self.has_icbm = self.CP_SP.intelligentCruiseButtonManagementAvailable and self.params.get_bool("IntelligentCruiseButtonManagement")
+
+ self._enforce_sp_constraints()
self.active_bundle = self.params.get("ModelManager_ActiveBundle")
self.blindspot = self.params.get_bool("BlindSpot")
self.chevron_metrics = self.params.get("ChevronInfo")
@@ -152,6 +154,48 @@ class UIStateSP:
self.boot_offroad_mode = self.params.get("DeviceBootMode", return_default=True)
self._remote_cycle_pending = self.params.get_bool("OnroadCyclePendingRemote")
+ def _enforce_sp_constraints(self) -> None:
+ has_long = self.has_longitudinal_control if hasattr(self, 'has_longitudinal_control') else False
+ has_icbm = self.has_icbm
+ CP = self.CP if hasattr(self, 'CP') else None
+
+ if CP is not None:
+ # Angle steering: no torque-based lateral controls
+ if CP.steerControlType == car.CarParams.SteerControlType.angle:
+ self.params.remove("EnforceTorqueControl")
+ self.params.remove("NeuralNetworkLateralControl")
+
+ # Alpha longitudinal: clear if not available or on release branch
+ if not CP.alphaLongitudinalAvailable or self.params.get_bool("IsReleaseBranch"):
+ self.params.remove("AlphaLongitudinalEnabled")
+
+ # BSM not available: clear BSM-dependent settings
+ if not CP.enableBsm:
+ self.params.remove("AutoLaneChangeBsmDelay")
+ else:
+ # No CarParams: clear all car-dependent params as safety default
+ self.params.remove("EnforceTorqueControl")
+ self.params.remove("NeuralNetworkLateralControl")
+ self.params.remove("AlphaLongitudinalEnabled")
+
+ # No longitudinal control: no experimental mode
+ if not has_long:
+ self.params.remove("ExperimentalMode")
+
+ # ICBM: clear if not available or if full longitudinal control is active
+ if self.CP_SP is not None:
+ if not self.CP_SP.intelligentCruiseButtonManagementAvailable or has_long:
+ self.params.remove("IntelligentCruiseButtonManagement")
+ else:
+ self.params.remove("IntelligentCruiseButtonManagement")
+
+ # Cruise features requiring longitudinal or ICBM
+ if not (has_long or has_icbm):
+ self.params.remove("CustomAccIncrementsEnabled")
+ self.params.remove("DynamicExperimentalControl")
+ self.params.remove("SmartCruiseControlVision")
+ self.params.remove("SmartCruiseControlMap")
+
def check_remote_cycle_pending(self, ui_state_ref) -> None:
"""Check and handle pending remote onroad cycle when not engaged."""
if not self._remote_cycle_pending or self._remote_cycle_dialog_shown: