From 4a98785aa3a5b6235a77ca8ec4a56bb5f74292a6 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:56:23 -0500 Subject: [PATCH] FLM --- common/libcommon.a | Bin 764376 -> 764376 bytes common/params_keys.h | 8 +- common/params_pyx.so | Bin 837400 -> 837400 bytes .../evaluate_reviewed_route_events.py | 4 + selfdrive/controls/lib/latcontrol_torque.py | 30 +- .../controls/lib/latcontrol_vehicle_tunes.py | 320 ++++++------ selfdrive/controls/tests/test_latcontrol.py | 56 +-- .../ui/onroad/starpilot/developer_sidebar.py | 20 +- starpilot/common/starpilot_variables.py | 18 +- starpilot/system/speed_limit_vision.py | 9 +- .../system/tests/test_speed_limit_vision.py | 5 + .../the_galaxy/assets/components/router.js | 4 +- .../components/tools/device_settings.css | 12 +- .../components/tools/device_settings.js | 72 +-- .../assets/components/tools/tuning.css | 162 +++--- .../assets/components/tools/tuning.js | 222 ++++---- .../{ftm_workspace.py => flm_workspace.py} | 473 ++++++++++-------- .../system/the_galaxy/templates/index.html | 6 +- ...ftm_workspace.py => test_flm_workspace.py} | 226 +++++---- starpilot/system/the_galaxy/the_galaxy.py | 118 +++-- system/manager/launch_param_migrations.py | 36 +- .../test/test_launch_param_migrations.py | 24 + tools/StarPilot/derive_feasible_params.py | 2 +- tools/tuning/{ftm_cli.py => flm_cli.py} | 6 +- 24 files changed, 1030 insertions(+), 803 deletions(-) rename starpilot/system/the_galaxy/{ftm_workspace.py => flm_workspace.py} (89%) rename starpilot/system/the_galaxy/tests/{test_ftm_workspace.py => test_flm_workspace.py} (84%) rename tools/tuning/{ftm_cli.py => flm_cli.py} (81%) diff --git a/common/libcommon.a b/common/libcommon.a index bf31ea76ab1be64dba90f6d92890f1badbd7fd63..4e3ea38e24542c370b43b97455b2a6d260c85156 100644 GIT binary patch delta 102 zcmca{S?|VWy@nRX7N!>F7M3lnmu_45_&O$+WR|7+m!%dJWu~MSGq}No0*dm}GILTr rQ$Ru?MVW~?PKm{-IhlE>K!J9yJFGy=2E^<@%mKul+qv#=ojVHv58NkW delta 102 zcmca{S?|VWy@nRX7N!>F7M3lnmu_2x_&O$+WR|7+m!%dJWu~MSGq}No0*dm}GILTr rQ$Ru?MVW~?PKm{-IhlE>K!J9yJFGy=2E^<@%mKul+qv#=ojVHv7Mv%7 diff --git a/common/params_keys.h b/common/params_keys.h index d693f4790..57a8a28f1 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -319,10 +319,10 @@ inline static std::unordered_map keys = { {"ForceStopDistanceOffset", {PERSISTENT, INT, "0", "0", 2}}, {"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2}}, {"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}}, - {"FTMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}}, - {"FTMActiveProfileId", {PERSISTENT, STRING, "", "", 2}}, - {"FTMTrialBaseline", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}}, - {"FTMTrialApplied", {PERSISTENT, BOOL, "0", "0", 2}}, + {"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}}, + {"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}}, + {"FLMTrialBaseline", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}}, + {"FLMTrialApplied", {PERSISTENT, BOOL, "0", "0", 2}}, {"FPSCounter", {PERSISTENT, BOOL, "1", "0", 3}}, {"GalaxyDashboardStats", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}}, {"StarPilotApiToken", {PERSISTENT | DONT_LOG, STRING, "", "", 0}}, diff --git a/common/params_pyx.so b/common/params_pyx.so index 01fa36244c830f897cc0a9c8a4bb824b5c95a9f9..3714fe7f2f2e537021ff5de5693d1eed3e5a07a8 100755 GIT binary patch delta 146 zcmbQyXFQ|Nc*6}wk(IXJ?@Q0Wbi9`(^cj<5@)E8MJB*v(Ft)#81Yss1W(HywAZ7(( zHXvpPVvg-^7&#rSEPQ+&lS?woQvJ(Pi;6N+Qi~bfU_t>!`DvLssh%kyp^&1?#2lx@ b;?$hXyi}k-d#Vj55OV=B_x4m9p3PkV4vaU} delta 146 zcmbQyXFQ|Nc*6}wk#aAcStt6J?FyNiH{Vy{%{TU)?F*XUFt)#81Yss1W(HywAZ7(( zHXvpPVvg-^7&#rSEJA!8lS?woQvJ(Pi;6N+Qi~bfU_t>!`DvLssh%kyp^&1?#2lx@ b;?$hXyi}k-d#Vj55OV=B_x4m9p3PkVQd~HW diff --git a/scripts/speed_limit_vision/evaluate_reviewed_route_events.py b/scripts/speed_limit_vision/evaluate_reviewed_route_events.py index f7ce72671..760d14058 100644 --- a/scripts/speed_limit_vision/evaluate_reviewed_route_events.py +++ b/scripts/speed_limit_vision/evaluate_reviewed_route_events.py @@ -106,6 +106,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--consistent-detections", type=int, help="Override matching reads required for an initial publication.") parser.add_argument("--change-consistent-detections", type=int, help="Override matching reads required to change a publication.") parser.add_argument("--change-single-read-min-confidence", type=float, help="Override confidence for a one-read speed change.") + parser.add_argument("--change-repeat-min-confidence", type=float, help="Override confidence required from the confirming speed-change read.") parser.add_argument("--max-cases", type=int, default=0, help="Optional evaluation cap after deduplication.") return parser.parse_args() @@ -272,6 +273,8 @@ def main() -> int: slv.CHANGE_CONSISTENT_DETECTIONS = args.change_consistent_detections if args.change_single_read_min_confidence is not None: slv.CHANGE_SINGLE_READ_MIN_CONFIDENCE = args.change_single_read_min_confidence + if args.change_repeat_min_confidence is not None: + slv.CHANGE_REPEAT_MIN_CONFIDENCE = args.change_repeat_min_confidence cases = load_cases(queue_path, labels_path, args.dedupe_seconds) if args.focus_eval_csv: with args.focus_eval_csv.expanduser().resolve().open(encoding="utf-8", newline="") as input_file: @@ -397,6 +400,7 @@ def main() -> int: "low_speed_change_consistent_detections": slv.LOW_SPEED_CHANGE_CONSISTENT_DETECTIONS, "low_speed_change_min_confidence": slv.LOW_SPEED_CHANGE_MIN_CONFIDENCE, "change_single_read_min_confidence": slv.CHANGE_SINGLE_READ_MIN_CONFIDENCE, + "change_repeat_min_confidence": slv.CHANGE_REPEAT_MIN_CONFIDENCE, "low_speed_change_allow_strong_consensus": slv.LOW_SPEED_CHANGE_ALLOW_STRONG_CONSENSUS, "strong_model_consensus_enabled": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_ENABLED, "strong_model_min_proposal_confidence": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE, diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index f103e5e15..aab258885 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -90,7 +90,7 @@ class LatControlTorque(LatControl): self.is_silverado = CP.carFingerprint in SILVERADO_CARS self.is_gm = CP.brand == "gm" self.is_hkg_canfd_torque = CP.brand == "hyundai" and bool(CP.flags & HyundaiFlags.CANFD) - self.ftm_surface_profile_key = get_ftm_surface_profile_key(CP.carFingerprint, torque_control=True) + self.flm_surface_profile_key = get_flm_surface_profile_key(CP.carFingerprint, torque_control=True) if self.is_ioniq_6: self.low_speed_reset_threshold = min(self.low_speed_reset_threshold, IONIQ_6_LOW_SPEED_PID_RESET_SPEED) self.use_bolt_ff_scaling = self.is_bolt_2022_2023 or self.is_bolt_2018_2021 or self.is_bolt_2017 @@ -158,10 +158,10 @@ class LatControlTorque(LatControl): def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay, calibrated_pose, model_data, starpilot_toggles): pid_log = log.ControlsState.LateralTorqueState.new_message() pid_log.version = VERSION - ftm_profile_active = bool(getattr(starpilot_toggles, "ftm_trial_applied", False) and - getattr(starpilot_toggles, "ftm_active_profile_id", "")) - set_ftm_runtime_overrides(getattr(starpilot_toggles, "ftm_active_overrides", None) if ftm_profile_active else None) - ftm_surface_active = ftm_profile_active and ftm_runtime_overrides_active() + flm_profile_active = bool(getattr(starpilot_toggles, "flm_trial_applied", False) and + getattr(starpilot_toggles, "flm_active_profile_id", "")) + set_flm_runtime_overrides(getattr(starpilot_toggles, "flm_active_overrides", None) if flm_profile_active else None) + flm_surface_active = flm_profile_active and flm_runtime_overrides_active() if not active: output_torque = 0.0 pid_log.active = False @@ -329,15 +329,15 @@ class LatControlTorque(LatControl): friction_threshold = CIVIC_BOSCH_MODIFIED_B_FIXED_FRICTION_THRESHOLD friction_scale = get_civic_bosch_modified_b_friction_scale(CS.vEgo, setpoint, desired_lateral_jerk) friction_scale = 1.0 + ((friction_scale - 1.0) * civic_bosch_modified_a_center_taper) - if ftm_surface_active and self.ftm_surface_profile_key and not ioniq_6_active: - universal_ftm_profile = self.ftm_surface_profile_key == FTM_UNIVERSAL_PROFILE_KEY - ftm_full_surface_center_taper = get_ftm_full_surface_center_taper_scale(self.ftm_surface_profile_key, setpoint, CS.vEgo, - include_base_center=universal_ftm_profile) - ff *= get_ftm_full_surface_ff_scale(self.ftm_surface_profile_key, setpoint, desired_lateral_jerk, CS.vEgo, - include_base_ff=universal_ftm_profile) * ftm_full_surface_center_taper - friction_threshold = get_ftm_full_surface_friction_threshold(self.ftm_surface_profile_key, friction_threshold, CS.vEgo, + if flm_surface_active and self.flm_surface_profile_key and not ioniq_6_active: + universal_flm_profile = self.flm_surface_profile_key == FLM_UNIVERSAL_PROFILE_KEY + flm_full_surface_center_taper = get_flm_full_surface_center_taper_scale(self.flm_surface_profile_key, setpoint, CS.vEgo, + include_base_center=universal_flm_profile) + ff *= get_flm_full_surface_ff_scale(self.flm_surface_profile_key, setpoint, desired_lateral_jerk, CS.vEgo, + include_base_ff=universal_flm_profile) * flm_full_surface_center_taper + friction_threshold = get_flm_full_surface_friction_threshold(self.flm_surface_profile_key, friction_threshold, CS.vEgo, setpoint, desired_lateral_jerk, - include_base_threshold=universal_ftm_profile) + include_base_threshold=universal_flm_profile) if trailer_load_kg > 0.0: ff *= get_trailer_lateral_ff_scale(trailer_load_kg, CS.vEgo, setpoint) friction_scale *= get_trailer_lateral_friction_scale(trailer_load_kg, CS.vEgo, setpoint) @@ -367,10 +367,10 @@ class LatControlTorque(LatControl): actual_angle_no_offset = CS.steeringAngleDeg - params.angleOffsetDeg output_torque = get_ioniq_6_low_speed_angle_assist_torque(desired_angle_no_offset, actual_angle_no_offset, output_torque, CS.vEgo) - elif ftm_surface_active and self.ftm_surface_profile_key and not CS.steeringPressed: + elif flm_surface_active and self.flm_surface_profile_key and not CS.steeringPressed: desired_angle_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) actual_angle_no_offset = CS.steeringAngleDeg - params.angleOffsetDeg - output_torque = get_ftm_full_surface_low_speed_angle_assist_torque(self.ftm_surface_profile_key, desired_angle_no_offset, + output_torque = get_flm_full_surface_low_speed_angle_assist_torque(self.flm_surface_profile_key, desired_angle_no_offset, actual_angle_no_offset, output_torque, CS.vEgo) if ioniq_6_active: output_torque *= get_ioniq_6_highway_output_taper_scale(setpoint, CS.vEgo) diff --git a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py index ee50a53e2..7202943b9 100644 --- a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py @@ -11,8 +11,8 @@ from openpilot.starpilot.common.testing_grounds import testing_ground CIVIC_BOSCH_MODIFIED_B_FIXED_FRICTION_THRESHOLD = 0.30 STANDARD_FRICTION_THRESHOLD = 0.30 HKG_CANFD_BASE_FRICTION_THRESHOLD = 0.39 -FTM_SCHEMA_VERSION = 1 -FTM_FRICTION_SPEED_KNOTS = [0.0, 5.0, 10.0, 15.0, 25.0] +FLM_SCHEMA_VERSION = 1 +FLM_FRICTION_SPEED_KNOTS = [0.0, 5.0, 10.0, 15.0, 25.0] CIVIC_BOSCH_MODIFIED_B_LAT_ACCEL_FACTOR_MULT = 1.20 CIVIC_BOSCH_MODIFIED_A_VARIANT_LAT_ACCEL_FACTOR_MULT = 1.00 CIVIC_BOSCH_MODIFIED_B_VARIANT_LAT_ACCEL_FACTOR_MULT = 1.75 @@ -695,8 +695,8 @@ TRAILER_LATERAL_LAT_RISE = 0.30 TRAILER_LATERAL_FF_GAIN = 0.05 TRAILER_LATERAL_FRICTION_GAIN = 0.03 -_FTM_ACTIVE_OVERRIDES_TEXT = "" -_FTM_ACTIVE_OVERRIDES = {} +_FLM_ACTIVE_OVERRIDES_TEXT = "" +_FLM_ACTIVE_OVERRIDES = {} def _sigmoid(x: float) -> float: @@ -720,11 +720,11 @@ def _hkg_canfd_base_friction_threshold_default(v_ego: float) -> float: return max(_gm_base_friction_threshold_default(v_ego), HKG_CANFD_BASE_FRICTION_THRESHOLD) -def _ftm_copy_json(value): +def _flm_copy_json(value): return json.loads(json.dumps(value)) -def normalize_ftm_overrides(overrides) -> dict: +def normalize_flm_overrides(overrides) -> dict: if overrides in (None, "", b""): return {} @@ -744,7 +744,7 @@ def normalize_ftm_overrides(overrides) -> dict: return {} normalized = { - "schemaVersion": FTM_SCHEMA_VERSION, + "schemaVersion": FLM_SCHEMA_VERSION, "baseFrictionThresholds": {}, "vehicleKnobs": {}, } @@ -752,11 +752,11 @@ def normalize_ftm_overrides(overrides) -> dict: for family in ("gm", "standard", "hkg_canfd"): payload = overrides.get("baseFrictionThresholds", {}).get(family, {}) values = payload.get("values", payload if isinstance(payload, list) else []) - if not isinstance(values, (list, tuple)) or len(values) != len(FTM_FRICTION_SPEED_KNOTS): + if not isinstance(values, (list, tuple)) or len(values) != len(FLM_FRICTION_SPEED_KNOTS): continue try: normalized["baseFrictionThresholds"][family] = { - "speedKnots": list(FTM_FRICTION_SPEED_KNOTS), + "speedKnots": list(FLM_FRICTION_SPEED_KNOTS), "values": [float(v) for v in values], } except Exception: @@ -776,55 +776,55 @@ def normalize_ftm_overrides(overrides) -> dict: return normalized -def set_ftm_runtime_overrides(overrides) -> None: - global _FTM_ACTIVE_OVERRIDES, _FTM_ACTIVE_OVERRIDES_TEXT +def set_flm_runtime_overrides(overrides) -> None: + global _FLM_ACTIVE_OVERRIDES, _FLM_ACTIVE_OVERRIDES_TEXT - normalized = normalize_ftm_overrides(overrides) + normalized = normalize_flm_overrides(overrides) text = json.dumps(normalized, sort_keys=True, separators=(",", ":")) if normalized else "" - if text == _FTM_ACTIVE_OVERRIDES_TEXT: + if text == _FLM_ACTIVE_OVERRIDES_TEXT: return - _FTM_ACTIVE_OVERRIDES_TEXT = text - _FTM_ACTIVE_OVERRIDES = normalized + _FLM_ACTIVE_OVERRIDES_TEXT = text + _FLM_ACTIVE_OVERRIDES = normalized -def clear_ftm_runtime_overrides() -> None: - set_ftm_runtime_overrides({}) +def clear_flm_runtime_overrides() -> None: + set_flm_runtime_overrides({}) -def get_ftm_runtime_overrides() -> dict: - return _ftm_copy_json(_FTM_ACTIVE_OVERRIDES) if _FTM_ACTIVE_OVERRIDES else {} +def get_flm_runtime_overrides() -> dict: + return _flm_copy_json(_FLM_ACTIVE_OVERRIDES) if _FLM_ACTIVE_OVERRIDES else {} -def ftm_runtime_overrides_active() -> bool: - return bool(_FTM_ACTIVE_OVERRIDES) +def flm_runtime_overrides_active() -> bool: + return bool(_FLM_ACTIVE_OVERRIDES) -def _ftm_base_friction_threshold(family: str, v_ego: float, default_fn) -> float: - payload = _FTM_ACTIVE_OVERRIDES.get("baseFrictionThresholds", {}).get(family, {}) +def _flm_base_friction_threshold(family: str, v_ego: float, default_fn) -> float: + payload = _FLM_ACTIVE_OVERRIDES.get("baseFrictionThresholds", {}).get(family, {}) values = payload.get("values", []) - if isinstance(values, list) and len(values) == len(FTM_FRICTION_SPEED_KNOTS): - return float(np.interp(v_ego, FTM_FRICTION_SPEED_KNOTS, values)) + if isinstance(values, list) and len(values) == len(FLM_FRICTION_SPEED_KNOTS): + return float(np.interp(v_ego, FLM_FRICTION_SPEED_KNOTS, values)) return float(default_fn(v_ego)) -def _ftm_vehicle_knob(name: str, default_value: float) -> float: +def _flm_vehicle_knob(name: str, default_value: float) -> float: try: - return float(_FTM_ACTIVE_OVERRIDES.get("vehicleKnobs", {}).get(name, default_value)) + return float(_FLM_ACTIVE_OVERRIDES.get("vehicleKnobs", {}).get(name, default_value)) except Exception: return float(default_value) def get_gm_base_friction_threshold(v_ego: float) -> float: - return _ftm_base_friction_threshold("gm", v_ego, _gm_base_friction_threshold_default) + return _flm_base_friction_threshold("gm", v_ego, _gm_base_friction_threshold_default) def get_standard_friction_threshold(v_ego: float) -> float: - return _ftm_base_friction_threshold("standard", v_ego, _standard_friction_threshold_default) + return _flm_base_friction_threshold("standard", v_ego, _standard_friction_threshold_default) def get_hkg_canfd_base_friction_threshold(v_ego: float) -> float: - return _ftm_base_friction_threshold("hkg_canfd", v_ego, _hkg_canfd_base_friction_threshold_default) + return _flm_base_friction_threshold("hkg_canfd", v_ego, _hkg_canfd_base_friction_threshold_default) def get_trailer_lateral_assist_factor(trailer_load_kg: float, v_ego: float, desired_lateral_accel: float) -> float: @@ -870,8 +870,8 @@ def get_prius_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float gain = _prius_side_value( desired_lateral_accel, - _ftm_vehicle_knob("toyota_prius.ff_gain_left", PRIUS_FF_GAIN_LEFT), - _ftm_vehicle_knob("toyota_prius.ff_gain_right", PRIUS_FF_GAIN_RIGHT), + _flm_vehicle_knob("toyota_prius.ff_gain_left", PRIUS_FF_GAIN_LEFT), + _flm_vehicle_knob("toyota_prius.ff_gain_right", PRIUS_FF_GAIN_RIGHT), ) abs_lateral_accel = abs(desired_lateral_accel) onset = _prius_sigmoid((abs_lateral_accel - PRIUS_FF_ONSET) / PRIUS_FF_ONSET_WIDTH) @@ -883,14 +883,14 @@ def get_prius_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float low_speed_factor = _prius_low_speed_factor(v_ego) turn_in_boost = 1.0 + (_prius_side_value( desired_lateral_accel, - _ftm_vehicle_knob("toyota_prius.turn_in_boost_left", PRIUS_TURN_IN_BOOST_LEFT), - _ftm_vehicle_knob("toyota_prius.turn_in_boost_right", PRIUS_TURN_IN_BOOST_RIGHT), + _flm_vehicle_knob("toyota_prius.turn_in_boost_left", PRIUS_TURN_IN_BOOST_LEFT), + _flm_vehicle_knob("toyota_prius.turn_in_boost_right", PRIUS_TURN_IN_BOOST_RIGHT), ) * turn_in_weight * (0.35 + 0.65 * low_speed_factor)) unwind_taper = 1.0 - (_prius_side_value( desired_lateral_accel, - _ftm_vehicle_knob("toyota_prius.unwind_taper_left", PRIUS_UNWIND_TAPER_LEFT), - _ftm_vehicle_knob("toyota_prius.unwind_taper_right", PRIUS_UNWIND_TAPER_RIGHT), + _flm_vehicle_knob("toyota_prius.unwind_taper_left", PRIUS_UNWIND_TAPER_LEFT), + _flm_vehicle_knob("toyota_prius.unwind_taper_right", PRIUS_UNWIND_TAPER_RIGHT), ) * unwind_weight * (0.35 + 0.65 * low_speed_factor)) return 1.0 + (extra_scale * turn_in_boost * max(unwind_taper, 0.0)) @@ -904,14 +904,14 @@ def get_prius_friction_threshold(v_ego: float, desired_lateral_accel: float = 0. unwind_weight = max(-phase, 0.0) threshold_scale = 1.0 - (_prius_side_value( desired_lateral_accel, - _ftm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_left", PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT), - _ftm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_right", PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT), + _flm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_left", PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT), + _flm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_right", PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT), ) * transition_envelope * turn_in_weight) threshold_scale += (_prius_side_value( desired_lateral_accel, - _ftm_vehicle_knob("toyota_prius.unwind_threshold_increase_left", PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT), - _ftm_vehicle_knob("toyota_prius.unwind_threshold_increase_right", PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT), + _flm_vehicle_knob("toyota_prius.unwind_threshold_increase_left", PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT), + _flm_vehicle_knob("toyota_prius.unwind_threshold_increase_right", PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT), ) * transition_envelope * unwind_weight) return base_threshold * min(max(threshold_scale, 0.86), 1.16) @@ -933,7 +933,7 @@ def get_prius_friction_scale(v_ego: float, desired_lateral_accel: float, desired def get_prius_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float: speed_weight = _prius_sigmoid((v_ego - PRIUS_CENTER_TAPER_SPEED) / PRIUS_CENTER_TAPER_SPEED_WIDTH) center_weight = _prius_sigmoid((PRIUS_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / PRIUS_CENTER_TAPER_LAT_WIDTH) - reduction = _ftm_vehicle_knob("toyota_prius.center_taper_max", PRIUS_CENTER_TAPER_MAX) * speed_weight * center_weight + reduction = _flm_vehicle_knob("toyota_prius.center_taper_max", PRIUS_CENTER_TAPER_MAX) * speed_weight * center_weight return 1.0 - reduction @@ -1241,8 +1241,8 @@ def get_bolt_2022_2023_ff_scale(desired_lateral_accel: float, desired_lateral_je gain = _bolt_2022_2023_side_value( desired_lateral_accel, - _ftm_vehicle_knob("gm_bolt_2022_2023.ff_gain_left", BOLT_2022_2023_FF_GAIN_LEFT), - _ftm_vehicle_knob("gm_bolt_2022_2023.ff_gain_right", BOLT_2022_2023_FF_GAIN_RIGHT), + _flm_vehicle_knob("gm_bolt_2022_2023.ff_gain_left", BOLT_2022_2023_FF_GAIN_LEFT), + _flm_vehicle_knob("gm_bolt_2022_2023.ff_gain_right", BOLT_2022_2023_FF_GAIN_RIGHT), ) abs_lateral_accel = abs(desired_lateral_accel) onset = _bolt_2022_2023_sigmoid((abs_lateral_accel - BOLT_2022_2023_FF_ONSET) / BOLT_2022_2023_FF_ONSET_WIDTH) @@ -1250,7 +1250,7 @@ def get_bolt_2022_2023_ff_scale(desired_lateral_accel: float, desired_lateral_je extra_scale = gain * onset * cutoff speed_weight = _bolt_2022_2023_sigmoid((v_ego - BOLT_2022_2023_CENTER_TAPER_SPEED) / BOLT_2022_2023_CENTER_TAPER_SPEED_WIDTH) center_weight = _bolt_2022_2023_sigmoid((BOLT_2022_2023_CENTER_TAPER_LAT - abs_lateral_accel) / BOLT_2022_2023_CENTER_TAPER_LAT_WIDTH) - center_taper = 1.0 - (_ftm_vehicle_knob("gm_bolt_2022_2023.center_taper_max", BOLT_2022_2023_CENTER_TAPER_MAX) * speed_weight * center_weight) + center_taper = 1.0 - (_flm_vehicle_knob("gm_bolt_2022_2023.center_taper_max", BOLT_2022_2023_CENTER_TAPER_MAX) * speed_weight * center_weight) low_speed_factor = _bolt_2022_2023_low_speed_factor(v_ego) transition_envelope = _bolt_2022_2023_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk) phase = _bolt_2022_2023_transition_phase(desired_lateral_accel, desired_lateral_jerk) @@ -1258,15 +1258,15 @@ def get_bolt_2022_2023_ff_scale(desired_lateral_accel: float, desired_lateral_je unwind_weight = max(-phase, 0.0) turn_in_boost = 1.0 + (_bolt_2022_2023_side_value( desired_lateral_accel, - _ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_left", BOLT_2022_2023_TURN_IN_BOOST_LEFT), - _ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_right", BOLT_2022_2023_TURN_IN_BOOST_RIGHT), + _flm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_left", BOLT_2022_2023_TURN_IN_BOOST_LEFT), + _flm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_right", BOLT_2022_2023_TURN_IN_BOOST_RIGHT), ) * turn_in_weight * low_speed_factor) unwind_envelope = (0.25 + 0.75 * low_speed_factor) * (1.0 + 0.45 * transition_envelope) unwind_taper = 1.0 - (_bolt_2022_2023_side_value( desired_lateral_accel, - _ftm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_left", BOLT_2022_2023_UNWIND_TAPER_LEFT), - _ftm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_right", BOLT_2022_2023_UNWIND_TAPER_RIGHT), + _flm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_left", BOLT_2022_2023_UNWIND_TAPER_LEFT), + _flm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_right", BOLT_2022_2023_UNWIND_TAPER_RIGHT), ) * unwind_weight * unwind_envelope) return 1.0 + (extra_scale * center_taper * turn_in_boost * max(unwind_taper, 0.0)) @@ -1280,14 +1280,14 @@ def get_bolt_2022_2023_friction_threshold(v_ego: float, desired_lateral_accel: f unwind_weight = max(-phase, 0.0) threshold_scale = 1.0 - (_bolt_2022_2023_side_value( desired_lateral_accel, - _ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_left", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_LEFT), - _ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_right", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_RIGHT), + _flm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_left", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_LEFT), + _flm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_right", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_RIGHT), ) * transition_envelope * turn_in_weight) threshold_scale += (_bolt_2022_2023_side_value( desired_lateral_accel, - _ftm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_left", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_LEFT), - _ftm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_right", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_RIGHT), + _flm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_left", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_LEFT), + _flm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_right", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_RIGHT), ) * transition_envelope * unwind_weight) return base_threshold * min(max(threshold_scale, 0.84), 1.14) @@ -1954,16 +1954,16 @@ def _ioniq_6_transition_envelope(v_ego: float, desired_lateral_accel: float, des def _ioniq_6_curvy_speed_weight(v_ego: float) -> float: - curvy_speed_min = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_speed_min", IONIQ_6_CURVY_SPEED_MIN) - curvy_speed_max = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_speed_max", IONIQ_6_CURVY_SPEED_MAX) + curvy_speed_min = _flm_vehicle_knob("hyundai_ioniq_6.curvy_speed_min", IONIQ_6_CURVY_SPEED_MIN) + curvy_speed_max = _flm_vehicle_knob("hyundai_ioniq_6.curvy_speed_max", IONIQ_6_CURVY_SPEED_MAX) onset = _ioniq_6_sigmoid((max(v_ego, 0.0) - curvy_speed_min) / IONIQ_6_CURVY_SPEED_MIN_WIDTH) cutoff = _ioniq_6_sigmoid((curvy_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_SPEED_MAX_WIDTH) return onset * cutoff def _ioniq_6_curvy_turn_in_trim_speed_weight(v_ego: float) -> float: - curvy_turn_in_speed_min = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_min", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN) - curvy_turn_in_speed_max = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_max", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX) + curvy_turn_in_speed_min = _flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_min", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN) + curvy_turn_in_speed_max = _flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_max", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX) onset = _ioniq_6_sigmoid((max(v_ego, 0.0) - curvy_turn_in_speed_min) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH) cutoff = _ioniq_6_sigmoid((curvy_turn_in_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH) return onset * cutoff @@ -1976,8 +1976,8 @@ def get_ioniq_6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo gain = _ioniq_6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.ff_gain_left", IONIQ_6_FF_GAIN_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.ff_gain_right", IONIQ_6_FF_GAIN_RIGHT), + _flm_vehicle_knob("hyundai_ioniq_6.ff_gain_left", IONIQ_6_FF_GAIN_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.ff_gain_right", IONIQ_6_FF_GAIN_RIGHT), ) abs_lateral_accel = abs(desired_lateral_accel) onset = _ioniq_6_sigmoid((abs_lateral_accel - IONIQ_6_FF_ONSET) / IONIQ_6_FF_ONSET_WIDTH) @@ -1989,14 +1989,14 @@ def get_ioniq_6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo low_speed_factor = _ioniq_6_low_speed_factor(v_ego) turn_in_boost = 1.0 + (_ioniq_6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_left", IONIQ_6_TURN_IN_BOOST_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_right", IONIQ_6_TURN_IN_BOOST_RIGHT), + _flm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_left", IONIQ_6_TURN_IN_BOOST_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_right", IONIQ_6_TURN_IN_BOOST_RIGHT), ) * turn_in_weight * low_speed_factor) unwind_taper = 1.0 - (_ioniq_6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.unwind_taper_left", IONIQ_6_UNWIND_TAPER_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.unwind_taper_right", IONIQ_6_UNWIND_TAPER_RIGHT), + _flm_vehicle_knob("hyundai_ioniq_6.unwind_taper_left", IONIQ_6_UNWIND_TAPER_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.unwind_taper_right", IONIQ_6_UNWIND_TAPER_RIGHT), ) * unwind_weight * (0.30 + 0.70 * low_speed_factor)) crawl_turn_in_scale = 0.0 @@ -2007,8 +2007,8 @@ def get_ioniq_6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo IONIQ_6_CRAWL_TURN_IN_FF_LAT_WIDTH) crawl_turn_in_scale = _ioniq_6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_left", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_right", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_RIGHT), + _flm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_left", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_right", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_RIGHT), ) * crawl_speed_weight * crawl_lat_weight high_speed_right_turn_in_scale = 0.0 if desired_lateral_accel < 0.0 and desired_lateral_accel * desired_lateral_jerk > 0.0: @@ -2034,14 +2034,14 @@ def get_ioniq_6_friction_threshold(v_ego: float, desired_lateral_accel: float = unwind_speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_UNWIND_HIGH_SPEED_SPEED) / IONIQ_6_UNWIND_HIGH_SPEED_SPEED_WIDTH) threshold_scale = 1.0 - (_ioniq_6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_left", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_right", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_RIGHT), + _flm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_left", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_right", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_RIGHT), ) * transition_envelope * turn_in_weight) threshold_scale += (_ioniq_6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_left", IONIQ_6_UNWIND_THRESHOLD_INCREASE_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_right", IONIQ_6_UNWIND_THRESHOLD_INCREASE_RIGHT), + _flm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_left", IONIQ_6_UNWIND_THRESHOLD_INCREASE_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_right", IONIQ_6_UNWIND_THRESHOLD_INCREASE_RIGHT), ) * transition_envelope * unwind_weight * unwind_speed_weight) return base_threshold * min(max(threshold_scale, 0.82), 1.18) @@ -2070,12 +2070,12 @@ def get_ioniq_6_friction_center_fade_scale(desired_lateral_accel: float, v_ego: def get_ioniq_6_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float: speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_CENTER_TAPER_SPEED) / IONIQ_6_CENTER_TAPER_SPEED_WIDTH) center_weight = _ioniq_6_sigmoid((IONIQ_6_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_CENTER_TAPER_LAT_WIDTH) - high_speed_reduction = _ftm_vehicle_knob("hyundai_ioniq_6.center_taper_max", IONIQ_6_CENTER_TAPER_MAX) * speed_weight * center_weight + high_speed_reduction = _flm_vehicle_knob("hyundai_ioniq_6.center_taper_max", IONIQ_6_CENTER_TAPER_MAX) * speed_weight * center_weight highway_speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED) / IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED_WIDTH) highway_center_weight = _ioniq_6_sigmoid((IONIQ_6_HIGHWAY_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_HIGHWAY_CENTER_TAPER_LAT_WIDTH) - highway_center_reduction = _ftm_vehicle_knob("hyundai_ioniq_6.highway_center_taper_max", IONIQ_6_HIGHWAY_CENTER_TAPER_MAX) * highway_speed_weight * highway_center_weight + highway_center_reduction = _flm_vehicle_knob("hyundai_ioniq_6.highway_center_taper_max", IONIQ_6_HIGHWAY_CENTER_TAPER_MAX) * highway_speed_weight * highway_center_weight low_mid_onset = _ioniq_6_sigmoid((v_ego - IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_MIN) / IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_WIDTH) low_mid_cutoff = _ioniq_6_sigmoid((IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_MAX - v_ego) / IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_WIDTH) @@ -2123,8 +2123,8 @@ def get_ioniq_6_directional_taper_scale(desired_lateral_accel: float, desired_la reduction = band_weight * (base_reduction + unwind_reduction * unwind_weight) reduction += heavy_band_weight * (heavy_base_reduction + heavy_unwind_reduction * unwind_weight) reduction += (_ioniq_6_side_value(desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_left", IONIQ_6_CURVY_TURN_IN_TRIM_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_right", IONIQ_6_CURVY_TURN_IN_TRIM_RIGHT)) * + _flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_left", IONIQ_6_CURVY_TURN_IN_TRIM_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_right", IONIQ_6_CURVY_TURN_IN_TRIM_RIGHT)) * curvy_turn_in_trim_weight) curvy_unwind_weight = 0.0 curvy_unwind_floor_relief = 0.0 @@ -2136,12 +2136,12 @@ def get_ioniq_6_directional_taper_scale(desired_lateral_accel: float, desired_la IONIQ_6_CURVY_UNWIND_LAT_CUTOFF_WIDTH) curvy_unwind_weight = curvy_unwind_speed_weight * curvy_unwind_lat_onset * curvy_unwind_lat_cutoff * unwind_weight curvy_unwind_floor_relief = (_ioniq_6_side_value(desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_left", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_right", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_RIGHT)) * + _flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_left", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_right", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_RIGHT)) * curvy_unwind_weight) reduction += (_ioniq_6_side_value(desired_lateral_accel, - _ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_left", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_LEFT), - _ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_right", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_RIGHT)) * + _flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_left", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_LEFT), + _flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_right", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_RIGHT)) * curvy_unwind_weight) floor = _ioniq_6_side_value(desired_lateral_accel, IONIQ_6_DIRECTIONAL_TAPER_FLOOR_LEFT, IONIQ_6_DIRECTIONAL_TAPER_FLOOR_RIGHT) floor -= _ioniq_6_side_value(desired_lateral_accel, IONIQ_6_DIRECTIONAL_TAPER_UNWIND_FLOOR_LEFT, IONIQ_6_DIRECTIONAL_TAPER_UNWIND_FLOOR_RIGHT) * unwind_weight @@ -2191,7 +2191,7 @@ def get_ioniq_6_low_speed_angle_assist_torque(desired_angle_deg: float, actual_a IONIQ_6_LOW_SPEED_ANGLE_ASSIST_TRACK_RATIO_WIDTH) tracking_scale = max(1.0 - tracking_taper, IONIQ_6_LOW_SPEED_ANGLE_ASSIST_TRACK_RATIO_FLOOR) assist_torque = math.copysign( - _ftm_vehicle_knob("hyundai_ioniq_6.low_speed_angle_assist_max_torque", IONIQ_6_LOW_SPEED_ANGLE_ASSIST_MAX_TORQUE) * + _flm_vehicle_knob("hyundai_ioniq_6.low_speed_angle_assist_max_torque", IONIQ_6_LOW_SPEED_ANGLE_ASSIST_MAX_TORQUE) * speed_weight * error_weight * desired_angle_weight * tracking_scale, -angle_error, ) @@ -2254,8 +2254,8 @@ def get_kia_ev6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo gain = _kia_ev6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_kia_ev6.ff_gain_left", KIA_EV6_FF_GAIN_LEFT), - _ftm_vehicle_knob("hyundai_kia_ev6.ff_gain_right", KIA_EV6_FF_GAIN_RIGHT), + _flm_vehicle_knob("hyundai_kia_ev6.ff_gain_left", KIA_EV6_FF_GAIN_LEFT), + _flm_vehicle_knob("hyundai_kia_ev6.ff_gain_right", KIA_EV6_FF_GAIN_RIGHT), ) abs_lateral_accel = abs(desired_lateral_accel) onset = _kia_ev6_sigmoid((abs_lateral_accel - KIA_EV6_FF_ONSET) / KIA_EV6_FF_ONSET_WIDTH) @@ -2267,14 +2267,14 @@ def get_kia_ev6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo low_speed_factor = _kia_ev6_low_speed_factor(v_ego) turn_in_boost = 1.0 + (_kia_ev6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_left", KIA_EV6_TURN_IN_BOOST_LEFT), - _ftm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_right", KIA_EV6_TURN_IN_BOOST_RIGHT), + _flm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_left", KIA_EV6_TURN_IN_BOOST_LEFT), + _flm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_right", KIA_EV6_TURN_IN_BOOST_RIGHT), ) * turn_in_weight * (0.35 + 0.65 * low_speed_factor)) unwind_taper = 1.0 - (_kia_ev6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_kia_ev6.unwind_taper_left", KIA_EV6_UNWIND_TAPER_LEFT), - _ftm_vehicle_knob("hyundai_kia_ev6.unwind_taper_right", KIA_EV6_UNWIND_TAPER_RIGHT), + _flm_vehicle_knob("hyundai_kia_ev6.unwind_taper_left", KIA_EV6_UNWIND_TAPER_LEFT), + _flm_vehicle_knob("hyundai_kia_ev6.unwind_taper_right", KIA_EV6_UNWIND_TAPER_RIGHT), ) * unwind_weight * (0.35 + 0.65 * low_speed_factor)) return 1.0 + (extra_scale * turn_in_boost * max(unwind_taper, 0.0)) @@ -2288,14 +2288,14 @@ def get_kia_ev6_friction_threshold(v_ego: float, desired_lateral_accel: float = unwind_weight = max(-phase, 0.0) threshold_scale = 1.0 - (_kia_ev6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_left", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_LEFT), - _ftm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_right", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_RIGHT), + _flm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_left", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_LEFT), + _flm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_right", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_RIGHT), ) * transition_envelope * turn_in_weight) threshold_scale += (_kia_ev6_side_value( desired_lateral_accel, - _ftm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_left", KIA_EV6_UNWIND_THRESHOLD_INCREASE_LEFT), - _ftm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_right", KIA_EV6_UNWIND_THRESHOLD_INCREASE_RIGHT), + _flm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_left", KIA_EV6_UNWIND_THRESHOLD_INCREASE_LEFT), + _flm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_right", KIA_EV6_UNWIND_THRESHOLD_INCREASE_RIGHT), ) * transition_envelope * unwind_weight) return base_threshold * min(max(threshold_scale, 0.82), 1.16) @@ -2317,7 +2317,7 @@ def get_kia_ev6_friction_scale(v_ego: float, desired_lateral_accel: float, desir def get_kia_ev6_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float: speed_weight = _kia_ev6_sigmoid((v_ego - KIA_EV6_CENTER_TAPER_SPEED) / KIA_EV6_CENTER_TAPER_SPEED_WIDTH) center_weight = _kia_ev6_sigmoid((KIA_EV6_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / KIA_EV6_CENTER_TAPER_LAT_WIDTH) - reduction = _ftm_vehicle_knob("hyundai_kia_ev6.center_taper_max", KIA_EV6_CENTER_TAPER_MAX) * speed_weight * center_weight + reduction = _flm_vehicle_knob("hyundai_kia_ev6.center_taper_max", KIA_EV6_CENTER_TAPER_MAX) * speed_weight * center_weight return 1.0 - reduction @@ -2362,9 +2362,9 @@ def get_volt_plexy_center_taper_scale(desired_lateral_accel: float, v_ego: float return 1.0 - (standard_reduction * VOLT_PLEXY_CENTER_TAPER_REDUCTION_MULT) -FTM_UNIVERSAL_PROFILE_KEY = "torque_universal" +FLM_UNIVERSAL_PROFILE_KEY = "torque_universal" -FTM_FULL_SURFACE_SUFFIX_METADATA = { +FLM_FULL_SURFACE_SUFFIX_METADATA = { "ff_gain_left": {"min": 0.0, "max": 0.60, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True}, "ff_gain_right": {"min": 0.0, "max": 0.60, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True}, "turn_in_boost_left": {"min": -0.10, "max": 2.80, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True}, @@ -2392,7 +2392,7 @@ FTM_FULL_SURFACE_SUFFIX_METADATA = { "curvy_unwind_extra_reduction_right": {"min": 0.0, "max": 0.45, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True}, } -FTM_FULL_SURFACE_NEUTRAL_DEFAULTS = { +FLM_FULL_SURFACE_NEUTRAL_DEFAULTS = { "ff_gain_left": 0.0, "ff_gain_right": 0.0, "turn_in_boost_left": 0.0, @@ -2421,148 +2421,148 @@ FTM_FULL_SURFACE_NEUTRAL_DEFAULTS = { } -def _ftm_profile_symbol(profile_key: str, suffix: str) -> str: +def _flm_profile_symbol(profile_key: str, suffix: str) -> str: return f"{profile_key}.{suffix}" -def ftm_profile_supports_knob(profile_key: str | None, suffix: str) -> bool: +def flm_profile_supports_knob(profile_key: str | None, suffix: str) -> bool: if not profile_key: return False - return _ftm_profile_symbol(profile_key, suffix) in FTM_SUPPORTED_VEHICLE_KNOBS + return _flm_profile_symbol(profile_key, suffix) in FLM_SUPPORTED_VEHICLE_KNOBS -def _ftm_full_surface_side_value(profile_key: str, desired_lateral_accel: float, +def _flm_full_surface_side_value(profile_key: str, desired_lateral_accel: float, suffix: str, left_default: float = 0.0, right_default: float = 0.0) -> float: if desired_lateral_accel >= 0.0: - return _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, f"{suffix}_left"), left_default) - return _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, f"{suffix}_right"), right_default) + return _flm_vehicle_knob(_flm_profile_symbol(profile_key, f"{suffix}_left"), left_default) + return _flm_vehicle_knob(_flm_profile_symbol(profile_key, f"{suffix}_right"), right_default) -def _ftm_full_surface_low_speed_factor(v_ego: float) -> float: +def _flm_full_surface_low_speed_factor(v_ego: float) -> float: return 1.0 / (1.0 + (max(v_ego, 0.0) / IONIQ_6_TRANSITION_SPEED) ** 2) -def _ftm_full_surface_transition_phase(desired_lateral_accel: float, desired_lateral_jerk: float) -> float: +def _flm_full_surface_transition_phase(desired_lateral_accel: float, desired_lateral_jerk: float) -> float: return math.tanh((desired_lateral_accel * desired_lateral_jerk) / IONIQ_6_PHASE_SCALE) -def _ftm_full_surface_transition_envelope(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float: +def _flm_full_surface_transition_envelope(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float: lat_factor = 1.0 - math.exp(-abs(desired_lateral_accel) / IONIQ_6_FRICTION_LAT_RISE) jerk_factor = 1.0 - math.exp(-abs(desired_lateral_jerk) / IONIQ_6_FRICTION_JERK_RISE) - return _ftm_full_surface_low_speed_factor(v_ego) * lat_factor * jerk_factor + return _flm_full_surface_low_speed_factor(v_ego) * lat_factor * jerk_factor -def _ftm_full_surface_curvy_speed_weight(profile_key: str, v_ego: float) -> float: - curvy_speed_min = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_speed_min"), IONIQ_6_CURVY_SPEED_MIN) - curvy_speed_max = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_speed_max"), IONIQ_6_CURVY_SPEED_MAX) +def _flm_full_surface_curvy_speed_weight(profile_key: str, v_ego: float) -> float: + curvy_speed_min = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_speed_min"), IONIQ_6_CURVY_SPEED_MIN) + curvy_speed_max = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_speed_max"), IONIQ_6_CURVY_SPEED_MAX) onset = _sigmoid((max(v_ego, 0.0) - curvy_speed_min) / IONIQ_6_CURVY_SPEED_MIN_WIDTH) cutoff = _sigmoid((curvy_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_SPEED_MAX_WIDTH) return onset * cutoff -def _ftm_full_surface_curvy_turn_in_trim_speed_weight(profile_key: str, v_ego: float) -> float: - curvy_turn_in_speed_min = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_min"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN) - curvy_turn_in_speed_max = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_max"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX) +def _flm_full_surface_curvy_turn_in_trim_speed_weight(profile_key: str, v_ego: float) -> float: + curvy_turn_in_speed_min = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_min"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN) + curvy_turn_in_speed_max = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_max"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX) onset = _sigmoid((max(v_ego, 0.0) - curvy_turn_in_speed_min) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH) cutoff = _sigmoid((curvy_turn_in_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH) return onset * cutoff -def get_ftm_full_surface_center_taper_scale(profile_key: str | None, desired_lateral_accel: float, v_ego: float, +def get_flm_full_surface_center_taper_scale(profile_key: str | None, desired_lateral_accel: float, v_ego: float, include_base_center: bool = False) -> float: if not profile_key: return 1.0 reduction = 0.0 - if include_base_center and ftm_profile_supports_knob(profile_key, "center_taper_max"): + if include_base_center and flm_profile_supports_knob(profile_key, "center_taper_max"): speed_weight = _sigmoid((v_ego - IONIQ_6_CENTER_TAPER_SPEED) / IONIQ_6_CENTER_TAPER_SPEED_WIDTH) center_weight = _sigmoid((IONIQ_6_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_CENTER_TAPER_LAT_WIDTH) - reduction += _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "center_taper_max"), 0.0) * speed_weight * center_weight + reduction += _flm_vehicle_knob(_flm_profile_symbol(profile_key, "center_taper_max"), 0.0) * speed_weight * center_weight - if ftm_profile_supports_knob(profile_key, "highway_center_taper_max"): + if flm_profile_supports_knob(profile_key, "highway_center_taper_max"): speed_weight = _sigmoid((v_ego - IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED) / IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED_WIDTH) center_weight = _sigmoid((IONIQ_6_HIGHWAY_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_HIGHWAY_CENTER_TAPER_LAT_WIDTH) - reduction += _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "highway_center_taper_max"), 0.0) * speed_weight * center_weight + reduction += _flm_vehicle_knob(_flm_profile_symbol(profile_key, "highway_center_taper_max"), 0.0) * speed_weight * center_weight return 1.0 - min(reduction, 0.20) -def get_ftm_full_surface_ff_scale(profile_key: str | None, desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float, +def get_flm_full_surface_ff_scale(profile_key: str | None, desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float, include_base_ff: bool = False) -> float: if not profile_key or desired_lateral_accel == 0.0: return 1.0 abs_lateral_accel = abs(desired_lateral_accel) - phase = _ftm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk) + phase = _flm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk) turn_in_weight = max(phase, 0.0) unwind_weight = max(-phase, 0.0) - low_speed_factor = _ftm_full_surface_low_speed_factor(v_ego) + low_speed_factor = _flm_full_surface_low_speed_factor(v_ego) - curvy_turn_in_speed_weight = _ftm_full_surface_curvy_turn_in_trim_speed_weight(profile_key, v_ego) + curvy_turn_in_speed_weight = _flm_full_surface_curvy_turn_in_trim_speed_weight(profile_key, v_ego) curvy_turn_in_lat_onset = _sigmoid((abs_lateral_accel - IONIQ_6_CURVY_TURN_IN_TRIM_LAT_START) / IONIQ_6_CURVY_TURN_IN_TRIM_LAT_ONSET_WIDTH) curvy_turn_in_lat_cutoff = _sigmoid((IONIQ_6_CURVY_TURN_IN_TRIM_LAT_END - abs_lateral_accel) / IONIQ_6_CURVY_TURN_IN_TRIM_LAT_CUTOFF_WIDTH) curvy_turn_in_trim_weight = curvy_turn_in_speed_weight * curvy_turn_in_lat_onset * curvy_turn_in_lat_cutoff * turn_in_weight - curvy_unwind_speed_weight = _ftm_full_surface_curvy_speed_weight(profile_key, v_ego) + curvy_unwind_speed_weight = _flm_full_surface_curvy_speed_weight(profile_key, v_ego) curvy_unwind_lat_onset = _sigmoid((abs_lateral_accel - IONIQ_6_CURVY_UNWIND_LAT_START) / IONIQ_6_CURVY_UNWIND_LAT_ONSET_WIDTH) curvy_unwind_lat_cutoff = _sigmoid((IONIQ_6_CURVY_UNWIND_LAT_END - abs_lateral_accel) / IONIQ_6_CURVY_UNWIND_LAT_CUTOFF_WIDTH) curvy_unwind_weight = curvy_unwind_speed_weight * curvy_unwind_lat_onset * curvy_unwind_lat_cutoff * unwind_weight scale = 1.0 - if include_base_ff and ftm_profile_supports_knob(profile_key, "ff_gain_left"): - gain = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "ff_gain") + if include_base_ff and flm_profile_supports_knob(profile_key, "ff_gain_left"): + gain = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "ff_gain") onset = _sigmoid((abs_lateral_accel - IONIQ_6_FF_ONSET) / IONIQ_6_FF_ONSET_WIDTH) cutoff = _sigmoid((IONIQ_6_FF_CUTOFF - abs_lateral_accel) / IONIQ_6_FF_CUTOFF_WIDTH) extra_scale = gain * onset * cutoff - turn_in_boost = 1.0 + (_ftm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_boost") * turn_in_weight * low_speed_factor) - unwind_reduction = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_taper") * unwind_weight * (0.30 + 0.70 * low_speed_factor) - curvy_unwind_extra = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight - curvy_unwind_floor_relief = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight + turn_in_boost = 1.0 + (_flm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_boost") * turn_in_weight * low_speed_factor) + unwind_reduction = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_taper") * unwind_weight * (0.30 + 0.70 * low_speed_factor) + curvy_unwind_extra = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight + curvy_unwind_floor_relief = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight unwind_floor = 1.0 - (0.55 * unwind_reduction) - curvy_unwind_floor_relief unwind_scale = max(1.0 - unwind_reduction - curvy_unwind_extra, unwind_floor, 0.0) scale *= 1.0 + (extra_scale * turn_in_boost * unwind_scale) else: - curvy_unwind_extra = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight - curvy_unwind_floor_relief = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight + curvy_unwind_extra = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight + curvy_unwind_floor_relief = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight scale *= max(1.0 - curvy_unwind_extra - curvy_unwind_floor_relief, 0.55) - if ftm_profile_supports_knob(profile_key, "curvy_turn_in_trim_left"): - curvy_trim = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_turn_in_trim") * curvy_turn_in_trim_weight + if flm_profile_supports_knob(profile_key, "curvy_turn_in_trim_left"): + curvy_trim = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_turn_in_trim") * curvy_turn_in_trim_weight scale *= max(1.0 - curvy_trim, 0.55) - if ftm_profile_supports_knob(profile_key, "crawl_turn_in_ff_boost_left") and desired_lateral_accel * desired_lateral_jerk > 0.0: + if flm_profile_supports_knob(profile_key, "crawl_turn_in_ff_boost_left") and desired_lateral_accel * desired_lateral_jerk > 0.0: crawl_speed_weight = _sigmoid((IONIQ_6_CRAWL_TURN_IN_FF_SPEED - max(v_ego, 0.0)) / IONIQ_6_CRAWL_TURN_IN_FF_SPEED_WIDTH) crawl_lat_weight = _sigmoid((abs_lateral_accel - IONIQ_6_CRAWL_TURN_IN_FF_LAT) / IONIQ_6_CRAWL_TURN_IN_FF_LAT_WIDTH) - scale += _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "crawl_turn_in_ff_boost") * crawl_speed_weight * crawl_lat_weight + scale += _flm_full_surface_side_value(profile_key, desired_lateral_accel, "crawl_turn_in_ff_boost") * crawl_speed_weight * crawl_lat_weight return scale -def get_ftm_full_surface_friction_threshold(profile_key: str | None, base_threshold: float, v_ego: float, +def get_flm_full_surface_friction_threshold(profile_key: str | None, base_threshold: float, v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0, include_base_threshold: bool = False) -> float: if not profile_key or not include_base_threshold: return base_threshold - transition_envelope = _ftm_full_surface_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk) - phase = _ftm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk) + transition_envelope = _flm_full_surface_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk) + phase = _flm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk) turn_in_weight = max(phase, 0.0) unwind_weight = max(-phase, 0.0) unwind_speed_weight = _sigmoid((v_ego - IONIQ_6_UNWIND_HIGH_SPEED_SPEED) / IONIQ_6_UNWIND_HIGH_SPEED_SPEED_WIDTH) threshold_scale = 1.0 - threshold_scale -= (_ftm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_threshold_reduction") * + threshold_scale -= (_flm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_threshold_reduction") * transition_envelope * turn_in_weight) - threshold_scale += (_ftm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_threshold_increase") * + threshold_scale += (_flm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_threshold_increase") * transition_envelope * unwind_weight * unwind_speed_weight) return base_threshold * min(max(threshold_scale, 0.82), 1.18) -def get_ftm_full_surface_low_speed_angle_assist_torque(profile_key: str | None, desired_angle_deg: float, actual_angle_deg: float, +def get_flm_full_surface_low_speed_angle_assist_torque(profile_key: str | None, desired_angle_deg: float, actual_angle_deg: float, current_output_torque: float, v_ego: float) -> float: - if not profile_key or not ftm_profile_supports_knob(profile_key, "low_speed_angle_assist_max_torque"): + if not profile_key or not flm_profile_supports_knob(profile_key, "low_speed_angle_assist_max_torque"): return current_output_torque - max_torque = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "low_speed_angle_assist_max_torque"), 0.0) + max_torque = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "low_speed_angle_assist_max_torque"), 0.0) if max_torque <= 1e-4: return current_output_torque @@ -2593,22 +2593,22 @@ def get_ftm_full_surface_low_speed_angle_assist_torque(profile_key: str | None, return float(np.clip(current_output_torque + assist_torque, -1.0, 1.0)) -FTM_RICH_PROFILE_CARS = { +FLM_RICH_PROFILE_CARS = { "gm_bolt_2022_2023": set(BOLT_2022_2023_CARS), "hyundai_ioniq_6": set(IONIQ_6_CARS), "hyundai_kia_ev6": set(KIA_EV6_CARS), "toyota_prius": set(PRIUS_CARS), } -FTM_RICH_PROFILE_LABELS = { +FLM_RICH_PROFILE_LABELS = { "gm_bolt_2022_2023": "Bolt 2022-2023", "hyundai_ioniq_6": "Ioniq 6", "hyundai_kia_ev6": "EV6", "toyota_prius": "Prius", - FTM_UNIVERSAL_PROFILE_KEY: "Torque Controller", + FLM_UNIVERSAL_PROFILE_KEY: "Torque Controller", } -FTM_SUPPORTED_VEHICLE_KNOBS = { +FLM_SUPPORTED_VEHICLE_KNOBS = { "gm_bolt_2022_2023.ff_gain_left": {"profile": "gm_bolt_2022_2023", "min": 0.0, "max": 0.40, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True, "defaultValue": BOLT_2022_2023_FF_GAIN_LEFT}, "gm_bolt_2022_2023.ff_gain_right": {"profile": "gm_bolt_2022_2023", "min": 0.0, "max": 0.40, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True, "defaultValue": BOLT_2022_2023_FF_GAIN_RIGHT}, "gm_bolt_2022_2023.turn_in_boost_left": {"profile": "gm_bolt_2022_2023", "min": -0.10, "max": 0.50, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True, "defaultValue": BOLT_2022_2023_TURN_IN_BOOST_LEFT}, @@ -2670,16 +2670,16 @@ FTM_SUPPORTED_VEHICLE_KNOBS = { } -def _add_ftm_full_surface_profile_knobs(profile_key: str, defaults: dict[str, float] | None = None) -> None: - knob_defaults = dict(FTM_FULL_SURFACE_NEUTRAL_DEFAULTS) +def _add_flm_full_surface_profile_knobs(profile_key: str, defaults: dict[str, float] | None = None) -> None: + knob_defaults = dict(FLM_FULL_SURFACE_NEUTRAL_DEFAULTS) if defaults: knob_defaults.update(defaults) - for suffix, meta in FTM_FULL_SURFACE_SUFFIX_METADATA.items(): - symbol = _ftm_profile_symbol(profile_key, suffix) - if symbol in FTM_SUPPORTED_VEHICLE_KNOBS: + for suffix, meta in FLM_FULL_SURFACE_SUFFIX_METADATA.items(): + symbol = _flm_profile_symbol(profile_key, suffix) + if symbol in FLM_SUPPORTED_VEHICLE_KNOBS: continue - FTM_SUPPORTED_VEHICLE_KNOBS[symbol] = { + FLM_SUPPORTED_VEHICLE_KNOBS[symbol] = { "profile": profile_key, "min": meta["min"], "max": meta["max"], @@ -2690,30 +2690,30 @@ def _add_ftm_full_surface_profile_knobs(profile_key: str, defaults: dict[str, fl } -for _ftm_profile_key in ("gm_bolt_2022_2023", "hyundai_ioniq_6", "hyundai_kia_ev6", "toyota_prius", FTM_UNIVERSAL_PROFILE_KEY): - _add_ftm_full_surface_profile_knobs(_ftm_profile_key) +for _flm_profile_key in ("gm_bolt_2022_2023", "hyundai_ioniq_6", "hyundai_kia_ev6", "toyota_prius", FLM_UNIVERSAL_PROFILE_KEY): + _add_flm_full_surface_profile_knobs(_flm_profile_key) -def get_ftm_supported_vehicle_knobs() -> dict: - return _ftm_copy_json(FTM_SUPPORTED_VEHICLE_KNOBS) +def get_flm_supported_vehicle_knobs() -> dict: + return _flm_copy_json(FLM_SUPPORTED_VEHICLE_KNOBS) -def get_ftm_rich_profile_key(car_fingerprint) -> str | None: - for profile_key, cars in FTM_RICH_PROFILE_CARS.items(): +def get_flm_rich_profile_key(car_fingerprint) -> str | None: + for profile_key, cars in FLM_RICH_PROFILE_CARS.items(): if car_fingerprint in cars: return profile_key return None -def get_ftm_surface_profile_key(car_fingerprint, torque_control: bool = True) -> str | None: - profile_key = get_ftm_rich_profile_key(car_fingerprint) +def get_flm_surface_profile_key(car_fingerprint, torque_control: bool = True) -> str | None: + profile_key = get_flm_rich_profile_key(car_fingerprint) if profile_key is not None or not torque_control: return profile_key - return FTM_UNIVERSAL_PROFILE_KEY + return FLM_UNIVERSAL_PROFILE_KEY -def get_ftm_capabilities(car_fingerprint, brand: str = "", hyundai_canfd: bool = False, torque_control: bool = True) -> dict: - profile_key = get_ftm_surface_profile_key(car_fingerprint, torque_control=torque_control) +def get_flm_capabilities(car_fingerprint, brand: str = "", hyundai_canfd: bool = False, torque_control: bool = True) -> dict: + profile_key = get_flm_surface_profile_key(car_fingerprint, torque_control=torque_control) if hyundai_canfd: friction_family = "hkg_canfd" elif brand == "gm": @@ -2732,14 +2732,14 @@ def get_ftm_capabilities(car_fingerprint, brand: str = "", hyundai_canfd: bool = set(KIA_XCEED_CARS) | set(KIA_NIRO_PHEV_2022_CARS) | set(KIA_FORTE_CARS) | set(KIA_EV6_CARS) | set(SILVERADO_CARS) ) - rich_knobs = [name for name, meta in FTM_SUPPORTED_VEHICLE_KNOBS.items() if meta["profile"] == profile_key] + rich_knobs = [name for name, meta in FLM_SUPPORTED_VEHICLE_KNOBS.items() if meta["profile"] == profile_key] return { "torqueControl": bool(torque_control), "frictionFamily": friction_family, "hasDedicatedFrictionThreshold": bool(dedicated_friction), "hasDedicatedCenterTaper": bool(dedicated_center_taper), "richProfileKey": profile_key, - "richProfileLabel": FTM_RICH_PROFILE_LABELS.get(profile_key), + "richProfileLabel": FLM_RICH_PROFILE_LABELS.get(profile_key), "richKnobs": rich_knobs, } diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index 867c2afd2..79bd027f6 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -20,11 +20,11 @@ from openpilot.selfdrive.controls.lib.latcontrol_pid import ( get_civic_bosch_modified_pid_output_scale, ) from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import ( - clear_ftm_runtime_overrides, - get_ftm_runtime_overrides, + clear_flm_runtime_overrides, + get_flm_runtime_overrides, get_hkg_canfd_base_friction_threshold, - normalize_ftm_overrides, - set_ftm_runtime_overrides, + normalize_flm_overrides, + set_flm_runtime_overrides, ) from openpilot.selfdrive.controls.lib.latcontrol_torque import ( get_civic_bosch_modified_a_center_taper_scale, @@ -264,9 +264,9 @@ class TestLatControl: assert unwind_left < steady_left assert unwind_right < steady_right - def test_ftm_standard_friction_curve_override(self): + def test_flm_standard_friction_curve_override(self): base = get_standard_friction_threshold(10.0) - overrides = normalize_ftm_overrides({ + overrides = normalize_flm_overrides({ "baseFrictionThresholds": { "standard": { "values": [0.30, 0.31, 0.32, 0.33, 0.34], @@ -274,54 +274,54 @@ class TestLatControl: }, }) try: - set_ftm_runtime_overrides(overrides) - assert get_ftm_runtime_overrides()["baseFrictionThresholds"]["standard"]["values"] == [0.30, 0.31, 0.32, 0.33, 0.34] + set_flm_runtime_overrides(overrides) + assert get_flm_runtime_overrides()["baseFrictionThresholds"]["standard"]["values"] == [0.30, 0.31, 0.32, 0.33, 0.34] assert get_standard_friction_threshold(10.0) == pytest.approx(0.32) assert get_standard_friction_threshold(12.5) > get_standard_friction_threshold(10.0) assert get_standard_friction_threshold(10.0) != pytest.approx(base) finally: - clear_ftm_runtime_overrides() - assert get_ftm_runtime_overrides() == {} + clear_flm_runtime_overrides() + assert get_flm_runtime_overrides() == {} assert get_standard_friction_threshold(10.0) == pytest.approx(base) - def test_ftm_vehicle_knob_override_ioniq6_center_taper(self): + def test_flm_vehicle_knob_override_ioniq6_center_taper(self): baseline = get_ioniq_6_center_taper_scale(0.0, 32.0) - overrides = normalize_ftm_overrides({ + overrides = normalize_flm_overrides({ "vehicleKnobs": { "hyundai_ioniq_6.center_taper_max": 0.0, "hyundai_ioniq_6.highway_center_taper_max": 0.0, }, }) try: - set_ftm_runtime_overrides(overrides) + set_flm_runtime_overrides(overrides) adjusted = get_ioniq_6_center_taper_scale(0.0, 32.0) assert adjusted > baseline assert adjusted <= 1.0 finally: - clear_ftm_runtime_overrides() + clear_flm_runtime_overrides() @pytest.mark.parametrize(("trial_applied", "profile_id"), [(False, "profile"), (True, "")]) - def test_ftm_surface_helpers_require_active_trial(self, monkeypatch, trial_applied, profile_id): + def test_flm_surface_helpers_require_active_trial(self, monkeypatch, trial_applied, profile_id): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_BOLT_ACC_2022_2023) - starpilot_toggles.ftm_trial_applied = trial_applied - starpilot_toggles.ftm_active_profile_id = profile_id - starpilot_toggles.ftm_active_overrides = { + starpilot_toggles.flm_trial_applied = trial_applied + starpilot_toggles.flm_active_profile_id = profile_id + starpilot_toggles.flm_active_overrides = { "vehicleKnobs": {"gm_bolt_2022_2023.highway_center_taper_max": 0.10}, } def fail_if_called(*_args, **_kwargs): - raise AssertionError("inactive FTM trial reached the runtime shaping path") + raise AssertionError("inactive FLM trial reached the runtime shaping path") - monkeypatch.setattr(latcontrol_torque, "get_ftm_full_surface_ff_scale", fail_if_called) + monkeypatch.setattr(latcontrol_torque, "get_flm_full_surface_ff_scale", fail_if_called) controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles) - assert get_ftm_runtime_overrides() == {} + assert get_flm_runtime_overrides() == {} - def test_ftm_surface_helpers_run_for_active_trial(self, monkeypatch): + def test_flm_surface_helpers_run_for_active_trial(self, monkeypatch): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_BOLT_ACC_2022_2023) - starpilot_toggles.ftm_trial_applied = True - starpilot_toggles.ftm_active_profile_id = "report:cleanup:recommended" - starpilot_toggles.ftm_active_overrides = { + starpilot_toggles.flm_trial_applied = True + starpilot_toggles.flm_active_profile_id = "report:cleanup:recommended" + starpilot_toggles.flm_active_overrides = { "vehicleKnobs": {"gm_bolt_2022_2023.highway_center_taper_max": 0.10}, } calls = [] @@ -330,13 +330,13 @@ class TestLatControl: calls.append(True) return 1.0 - monkeypatch.setattr(latcontrol_torque, "get_ftm_full_surface_ff_scale", record_call) + monkeypatch.setattr(latcontrol_torque, "get_flm_full_surface_ff_scale", record_call) try: controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles) assert calls - assert get_ftm_runtime_overrides()["vehicleKnobs"]["gm_bolt_2022_2023.highway_center_taper_max"] == pytest.approx(0.10) + assert get_flm_runtime_overrides()["vehicleKnobs"]["gm_bolt_2022_2023.highway_center_taper_max"] == pytest.approx(0.10) finally: - clear_ftm_runtime_overrides() + clear_flm_runtime_overrides() def test_sonata_hybrid_center_taper_curve(self): assert get_sonata_hybrid_center_taper_scale(0.0, 30.0) < get_sonata_hybrid_center_taper_scale(0.0, 15.0) diff --git a/selfdrive/ui/onroad/starpilot/developer_sidebar.py b/selfdrive/ui/onroad/starpilot/developer_sidebar.py index fc2065d09..2e3aa5d42 100644 --- a/selfdrive/ui/onroad/starpilot/developer_sidebar.py +++ b/selfdrive/ui/onroad/starpilot/developer_sidebar.py @@ -14,7 +14,7 @@ METRIC_MARGIN = 12 FONT_SIZE = 35 METER_TO_FOOT = 3.28084 _WHITE_DIM = rl.Color(255, 255, 255, 85) -_FTM_OVERRIDE_COLOR = rl.Color(239, 68, 68, 255) +_FLM_OVERRIDE_COLOR = rl.Color(239, 68, 68, 255) def parse_hex_color(hex_str: str, default_color=rl.WHITE) -> rl.Color: if not hex_str: @@ -56,7 +56,7 @@ class DeveloperSidebar: self._cached_metrics = [0] * 7 self._cached_force_auto_tune_off = False self._cached_force_auto_tune = False - self._cached_ftm_trial_applied = False + self._cached_flm_trial_applied = False self._cached_friction_stock = 0.0 self._cached_friction = 0.0 self._cached_lat_stock = 0.0 @@ -73,7 +73,7 @@ class DeveloperSidebar: self._visible = False self._metric_color = rl.WHITE self._active_ids: list[int] = [] - self._ftm_override_metric_ids: set[int] = set() + self._flm_override_metric_ids: set[int] = set() self._metrics: dict[int, tuple[str, str]] = {} @property @@ -97,7 +97,7 @@ class DeveloperSidebar: self._cached_metrics = [self._params.get_int(f"DeveloperSidebarMetric{i}") for i in range(1, 8)] self._cached_force_auto_tune_off = self._params.get_bool("ForceAutoTuneOff") self._cached_force_auto_tune = self._params.get_bool("ForceAutoTune") - self._cached_ftm_trial_applied = self._params.get_bool("FTMTrialApplied") + self._cached_flm_trial_applied = self._params.get_bool("FLMTrialApplied") self._cached_friction_stock = self._params.get_float("SteerFrictionStock") self._cached_friction = self._params.get_float("SteerFriction") self._cached_lat_stock = self._params.get_float("SteerLatAccelStock") @@ -222,15 +222,15 @@ class DeveloperSidebar: use_custom_friction = bool(ui_state.starpilot_toggles.get("use_custom_friction", force_auto_tune_off)) use_custom_lat_factor = bool(ui_state.starpilot_toggles.get("use_custom_latAccelFactor", force_auto_tune_off)) - self._ftm_override_metric_ids = set() - if self._cached_ftm_trial_applied: - ftm_metric_flags = { + self._flm_override_metric_ids = set() + if self._cached_flm_trial_applied: + flm_metric_flags = { 3: bool(ui_state.starpilot_toggles.get("use_custom_steerActuatorDelay", False)), 4: use_custom_friction, 5: use_custom_lat_factor, 6: bool(ui_state.starpilot_toggles.get("use_custom_steerRatio", False)), } - self._ftm_override_metric_ids = {metric_id for metric_id, enabled in ftm_metric_flags.items() if enabled} + self._flm_override_metric_ids = {metric_id for metric_id, enabled in flm_metric_flags.items() if enabled} friction_coeff = resolve_effective_torque_value( use_custom_friction, @@ -305,6 +305,6 @@ class DeveloperSidebar: if metric_id <= 0 or metric_id not in self._metrics: continue label_first, label_second = self._metrics[metric_id] - ftm_overridden = metric_id in self._ftm_override_metric_ids - self._draw_metric(sidebar_rect, label_first, label_second, _FTM_OVERRIDE_COLOR if ftm_overridden else self._metric_color, y) + flm_overridden = metric_id in self._flm_override_metric_ids + self._draw_metric(sidebar_rect, label_first, label_second, _FLM_OVERRIDE_COLOR if flm_overridden else self._metric_color, y) y += METRIC_HEIGHT + spacing diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 5f0250e26..19259c3a5 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -232,10 +232,10 @@ EXCLUDED_KEYS = { "CommunityFavorites", "CurvatureData", "ExperimentalLongitudinalEnabled", - "FTMActiveOverrides", - "FTMActiveProfileId", - "FTMTrialBaseline", - "FTMTrialApplied", + "FLMActiveOverrides", + "FLMActiveProfileId", + "FLMTrialBaseline", + "FLMTrialApplied", "InstallDate", "StarPilotCarParamsPersistent", "KonikMinutes", @@ -708,13 +708,13 @@ class StarPilotVariables: advanced_lateral_tuning = self.get_value("AdvancedLateralTune") toggle.force_auto_tune = self.get_value("ForceAutoTune", condition=advanced_lateral_tuning and not has_auto_tune and is_torque_car and not is_angle_car) toggle.force_auto_tune_off = self.get_value("ForceAutoTuneOff", condition=advanced_lateral_tuning and has_auto_tune and is_torque_car and not is_angle_car) - toggle.ftm_active_profile_id = self.params.get("FTMActiveProfileId", encoding="utf-8") or "" - toggle.ftm_trial_applied = self.params.get_bool("FTMTrialApplied") - ftm_overrides_raw = self.params.get("FTMActiveOverrides", encoding="utf-8") or "" + toggle.flm_active_profile_id = self.params.get("FLMActiveProfileId", encoding="utf-8") or "" + toggle.flm_trial_applied = self.params.get_bool("FLMTrialApplied") + flm_overrides_raw = self.params.get("FLMActiveOverrides", encoding="utf-8") or "" try: - toggle.ftm_active_overrides = json.loads(ftm_overrides_raw) if ftm_overrides_raw else {} + toggle.flm_active_overrides = json.loads(flm_overrides_raw) if flm_overrides_raw else {} except Exception: - toggle.ftm_active_overrides = {} + toggle.flm_active_overrides = {} toggle.use_auto_steer_delay = self.get_value("UseAutoSteerDelay", condition=advanced_lateral_tuning, default=True) toggle.steerActuatorDelay = self.get_value("SteerDelay", cast=float, condition=advanced_lateral_tuning, default=fullSteerActuatorDelay, min=0.01, max=1.0) toggle.use_custom_steerActuatorDelay = advanced_lateral_tuning and not toggle.use_auto_steer_delay diff --git a/starpilot/system/speed_limit_vision.py b/starpilot/system/speed_limit_vision.py index e45dc116c..93b2c7ddd 100644 --- a/starpilot/system/speed_limit_vision.py +++ b/starpilot/system/speed_limit_vision.py @@ -21,7 +21,7 @@ RUNTIME_LOOP_HZ = 20 INFERENCE_INTERVAL = 0.15 FOLLOWUP_INFERENCE_INTERVAL = 0.10 FOLLOWUP_WINDOW_SECONDS = 2.0 -TEMPORAL_TRACKING_ENABLED = True +TEMPORAL_TRACKING_ENABLED = False TRACK_CONFIRMED_PROPOSALS_ENABLED = False TRACK_CLASSIFICATION_INTERVAL = 0.12 TRACK_BUSY_CLASSIFICATION_INTERVAL = 0.35 @@ -58,6 +58,7 @@ CONSISTENT_DETECTIONS = 2 # These counts must remain achievable at the measured 1.5 Hz onroad cadence. CHANGE_CONSISTENT_DETECTIONS = 2 CHANGE_SINGLE_READ_MIN_CONFIDENCE = 0.83 +CHANGE_REPEAT_MIN_CONFIDENCE = 0.70 LOW_SPEED_CHANGE_CONSISTENT_DETECTIONS = 2 LOW_SPEED_CHANGE_MIN_CONFIDENCE = 0.90 LOW_SPEED_CHANGE_ALLOW_STRONG_CONSENSUS = True @@ -2150,6 +2151,7 @@ class SpeedLimitVisionDaemon: counts = Counter(entry.speed_limit_mph for entry in self.history) candidate_speed_limit, candidate_count = counts.most_common(1)[0] matching_entries = [entry for entry in self.history if entry.speed_limit_mph == candidate_speed_limit] + matching_confidences = sorted((entry.confidence for entry in matching_entries), reverse=True) best_confidence = max(entry.confidence for entry in matching_entries) has_strong_consensus = any(entry.strong_consensus for entry in matching_entries) current_speed_limit = self.published_speed_limit_mph @@ -2170,6 +2172,11 @@ class SpeedLimitVisionDaemon: return None if candidate_count < required_count and not allow_single_frame_confirmation: return None + if ( + not allow_single_frame_confirmation and + matching_confidences[required_count - 1] < CHANGE_REPEAT_MIN_CONFIDENCE + ): + return None if candidate_count <= current_count: return None return candidate_speed_limit, best_confidence diff --git a/starpilot/system/tests/test_speed_limit_vision.py b/starpilot/system/tests/test_speed_limit_vision.py index ac6bc5051..6cde44553 100644 --- a/starpilot/system/tests/test_speed_limit_vision.py +++ b/starpilot/system/tests/test_speed_limit_vision.py @@ -22,6 +22,11 @@ def test_speed_change_requires_two_matching_reads_below_single_read_threshold(): assert daemon._confirm_detection() == pytest.approx((55, 0.82)) +def test_speed_change_rejects_weak_confirming_read(): + daemon = daemon_with_history(40, [(35, 0.78), (35, 0.48)]) + assert daemon._confirm_detection() is None + + def test_speed_change_accepts_single_high_confidence_read(): daemon = daemon_with_history(40, [(55, 0.84)]) assert daemon._confirm_detection() == pytest.approx((55, 0.84)) diff --git a/starpilot/system/the_galaxy/assets/components/router.js b/starpilot/system/the_galaxy/assets/components/router.js index 67db0e5be..90de167de 100644 --- a/starpilot/system/the_galaxy/assets/components/router.js +++ b/starpilot/system/the_galaxy/assets/components/router.js @@ -1,7 +1,7 @@ import { html, reactive } from "/assets/vendor/arrow-core.js" import { createBrowserHistory, createRouter } from "/assets/vendor/remix-router-1.3.1.js" import { hideSidebar } from "/assets/js/utils.js" -import { DeviceSettings } from "/assets/components/tools/device_settings.js?v=ftm-overrides-1" +import { DeviceSettings } from "/assets/components/tools/device_settings.js?v=flm-overrides-1" import { ErrorLogs } from "/assets/components/tools/error_logs.js" import { VehicleFeatures } from "/assets/components/tools/vehicle_features.js" import { GalaxyPairing } from "/assets/components/tools/galaxy.js" @@ -19,7 +19,7 @@ import { ModelManager } from "/assets/components/tools/model_manager.js?v=202603 import { LivePlots } from "/assets/components/tools/plots.js" import { ThemeMaker } from "/assets/components/tools/theme_maker.js" import { TestingGround } from "/assets/components/tools/testing_ground.js" -import { Tuning } from "/assets/components/tools/tuning.js?v=ftm-workspace-9" +import { Tuning } from "/assets/components/tools/tuning.js?v=flm-workspace-9" import { Troubleshoot } from "/assets/components/tools/troubleshoot.js" import { TmuxLog } from "/assets/components/tools/tmux.js" import { ToggleControl } from "/assets/components/tools/toggles.js" diff --git a/starpilot/system/the_galaxy/assets/components/tools/device_settings.css b/starpilot/system/the_galaxy/assets/components/tools/device_settings.css index d20e83dc5..cf089d20e 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/device_settings.css +++ b/starpilot/system/the_galaxy/assets/components/tools/device_settings.css @@ -179,7 +179,7 @@ gap: 0.45rem; } -.ds-ftm-badge { +.ds-flm-badge { background: var(--sidebar-bg); border: 1px solid var(--main-fg); border-radius: 999px; @@ -191,8 +191,8 @@ text-transform: uppercase; } -.ds-ftm-detail, -.ds-ftm-summary { +.ds-flm-detail, +.ds-flm-summary { border-left: 2px solid var(--main-fg); color: var(--text-muted); font-size: var(--font-size-xs); @@ -201,11 +201,11 @@ padding-left: 0.65rem; } -.ds-ftm-summary > div + div { +.ds-flm-summary > div + div { margin-top: 0.2rem; } -.ds-ftm-link { +.ds-flm-link { color: var(--main-fg); display: inline-block; font-weight: var(--font-weight-bold); @@ -213,7 +213,7 @@ text-decoration: none; } -.ds-ftm-link:hover { +.ds-flm-link:hover { text-decoration: underline; } diff --git a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js index ac128b751..10f57e4d0 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js +++ b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js @@ -12,11 +12,11 @@ const FAVORITE_OPTION_COLLATOR = new Intl.Collator(undefined, { numeric: true, s // Plain variables — scheduling/routing flags that must NOT be reactive let syncScheduled = false let lastParams = null -let ftmWorkspaceInflight = null -let lastFtmWorkspaceFetch = 0 +let flmWorkspaceInflight = null +let lastFlmWorkspaceFetch = 0 const DYNAMIC_DEFAULT_DEP_KEYS = new Set(["AccelerationProfile", "EVTuning", "TruckTuning"]) const PANDA_FIRMWARE_TOGGLE_KEYS = new Set(["IgnoreIgnitionLine", "RemoteStartBootsComma", "HKGRemoteStartBootsComma"]) -const FTM_ADVANCED_LATERAL_KEYS = new Set([ +const FLM_ADVANCED_LATERAL_KEYS = new Set([ "AdvancedLateralTune", "ForceAutoTune", "ForceAutoTuneOff", "UseAutoSteerDelay", "SteerDelay", "SteerFriction", "SteerKP", "SteerLatAccel", "SteerRatio", ]) @@ -28,7 +28,7 @@ const state = reactive({ paramMetaByKey: {}, values: {}, defaultValues: {}, - ftmActiveTrial: null, + flmActiveTrial: null, loadingLayout: true, loadingValues: true, filter: "", @@ -274,28 +274,28 @@ async function fetchDefaultValues() { } } -async function fetchFtmWorkspace(force = false) { +async function fetchFlmWorkspace(force = false) { const now = Date.now() - if (!force && now - lastFtmWorkspaceFetch < 1500) return - if (ftmWorkspaceInflight) return ftmWorkspaceInflight + if (!force && now - lastFlmWorkspaceFetch < 1500) return + if (flmWorkspaceInflight) return flmWorkspaceInflight - lastFtmWorkspaceFetch = now - ftmWorkspaceInflight = fetch("/api/ftm/workspace", { cache: "no-store" }) + lastFlmWorkspaceFetch = now + flmWorkspaceInflight = fetch("/api/flm/workspace", { cache: "no-store" }) .then(async res => { if (!res.ok) return const workspace = await res.json() - state.ftmActiveTrial = workspace?.activeTrial || null + state.flmActiveTrial = workspace?.activeTrial || null }) - .catch(error => console.warn("Failed to load active FTM trial state:", error)) + .catch(error => console.warn("Failed to load active FLM trial state:", error)) .finally(() => { - ftmWorkspaceInflight = null + flmWorkspaceInflight = null }) - return ftmWorkspaceInflight + return flmWorkspaceInflight } async function refreshParamsAndDefaults() { - await Promise.all([fetchDefaultValues(), fetchFtmWorkspace(true)]) + await Promise.all([fetchDefaultValues(), fetchFlmWorkspace(true)]) try { const valuesRes = await fetch("/api/params/all") @@ -344,7 +344,7 @@ async function fetchLayoutAndParams() { // Pull params once at page load; local state handles subsequent edits. try { - const [defaultsLoaded] = await Promise.all([fetchDefaultValues(), fetchFtmWorkspace(true)]) + const [defaultsLoaded] = await Promise.all([fetchDefaultValues(), fetchFlmWorkspace(true)]) if (!defaultsLoaded) { state.defaultValues = {} } @@ -1035,9 +1035,9 @@ function valuesEqual(left, right) { return left === right } -function getFtmParamStatus(key) { - const trial = state.ftmActiveTrial - if (!trial || !FTM_ADVANCED_LATERAL_KEYS.has(key)) return null +function getFlmParamStatus(key) { + const trial = state.flmActiveTrial + if (!trial || !FLM_ADVANCED_LATERAL_KEYS.has(key)) return null const applied = trial.appliedGenericParams || {} const hasExplicitMetadata = Object.prototype.hasOwnProperty.call(applied, key) @@ -1054,7 +1054,7 @@ function getFtmParamStatus(key) { } } -function formatFtmValue(param, value) { +function formatFlmValue(param, value) { if (value === undefined || value === null) return "not set" if (param.data_type === "bool") return value ? "On" : "Off" if (param.ui_type === "numeric") { @@ -1064,8 +1064,8 @@ function formatFtmValue(param, value) { return String(value) } -function getFtmTrialSummary() { - const trial = state.ftmActiveTrial +function getFlmTrialSummary() { + const trial = state.flmActiveTrial if (!trial) return null const genericCount = Object.keys(trial.appliedGenericParams || {}).filter(key => key !== "AdvancedLateralTune").length const thresholdCount = Object.keys(trial.appliedFrictionThresholds || {}).length @@ -1234,8 +1234,8 @@ function renderSettingRow(p) { const isChild = p.parent_key ? "ds-child-modifier" : "" const lockReason = () => getSettingLockReason(p) const isLocked = () => lockReason() !== "" - const ftmParamStatus = getFtmParamStatus(p.key) - const ftmTrialSummary = p.key === "AdvancedLateralTune" ? getFtmTrialSummary() : null + const flmParamStatus = getFlmParamStatus(p.key) + const flmTrialSummary = p.key === "AdvancedLateralTune" ? getFlmTrialSummary() : null let rowControl = "" if (isNumeric) { @@ -1358,30 +1358,30 @@ function renderSettingRow(p) {
${p.label} - ${ftmParamStatus ? html`Currently overridden by FTM` : ""} + ${flmParamStatus ? html`Currently overridden by FLM` : ""}
${p.description ? html`
${p.description}
` : ""} ${() => { const reason = lockReason() return reason ? html`
Locked: ${reason}
` : "" }} - ${ftmParamStatus ? html` -
- Effective now: ${formatFtmValue(p, ftmParamStatus.effectiveValue)}. - Revert restores: ${formatFtmValue(p, ftmParamStatus.previousValue)}. + ${flmParamStatus ? html` +
+ Effective now: ${formatFlmValue(p, flmParamStatus.effectiveValue)}. + Revert restores: ${formatFlmValue(p, flmParamStatus.previousValue)}. You can still edit this while the trial is active.
` : ""} - ${ftmTrialSummary ? html` -
-
FTM trial active: ${ftmTrialSummary.title}
+ ${flmTrialSummary ? html` +
+
FLM trial active: ${flmTrialSummary.title}
- ${ftmTrialSummary.genericCount} advanced setting${ftmTrialSummary.genericCount === 1 ? "" : "s"}, - ${ftmTrialSummary.thresholdCount} friction curve${ftmTrialSummary.thresholdCount === 1 ? "" : "s"}, and - ${ftmTrialSummary.vehicleKnobCount} vehicle-specific knob${ftmTrialSummary.vehicleKnobCount === 1 ? "" : "s"} active. + ${flmTrialSummary.genericCount} advanced setting${flmTrialSummary.genericCount === 1 ? "" : "s"}, + ${flmTrialSummary.thresholdCount} friction curve${flmTrialSummary.thresholdCount === 1 ? "" : "s"}, and + ${flmTrialSummary.vehicleKnobCount} vehicle-specific knob${flmTrialSummary.vehicleKnobCount === 1 ? "" : "s"} active.
Revert from Lateral Tuning restores the exact settings saved before this trial.
- Open Lateral Tuning + Open Lateral Tuning
` : ""} @@ -1446,7 +1446,7 @@ function resolveActiveSectionSlug(params) { export function DeviceSettings({ params }) { lastParams = params - fetchFtmWorkspace() + fetchFlmWorkspace() if (!state.fetched) { state.fetched = true diff --git a/starpilot/system/the_galaxy/assets/components/tools/tuning.css b/starpilot/system/the_galaxy/assets/components/tools/tuning.css index 3564f807f..4a2e0f6ca 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/tuning.css +++ b/starpilot/system/the_galaxy/assets/components/tools/tuning.css @@ -1,11 +1,11 @@ -.ftmTwoColumn { +.flmTwoColumn { display: grid; gap: var(--gap-base); grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: var(--margin-base); } -.ftmCard { +.flmCard { background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: var(--border-radius-md); @@ -13,45 +13,51 @@ padding: var(--padding-base); } -.ftmCardHeader { +.flmCardHeader { align-items: flex-start; display: flex; gap: var(--gap-sm); justify-content: space-between; } -.ftmCardHeader h3, -.ftmCardHeader h4, -.ftmCardHeader h5 { +.flmCardHeader h3, +.flmCardHeader h4, +.flmCardHeader h5 { margin: 0; } -.ftmCardSubsection { +.flmCardSubsection { margin-top: var(--margin-base); } -.ftmRouteList, -.ftmWorkspaceList, -.ftmFindings { +.flmRouteList, +.flmWorkspaceList, +.flmFindings { display: flex; flex-direction: column; gap: var(--gap-sm); margin-top: var(--margin-base); } -.ftmWorkspaceRow { +.flmWorkspaceRow { display: flex; gap: var(--gap-sm); } -.ftmRouteList { +.flmRouteList { max-height: 28rem; overflow: auto; padding-right: var(--padding-xs); } -.ftmRouteItem, -.ftmWorkspaceItem { +.flmRouteRow { + align-items: stretch; + display: flex; + gap: var(--gap-sm); +} + +.flmRouteItem, +.flmWorkspaceItem { align-items: flex-start; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06); @@ -64,61 +70,83 @@ text-align: left; } -.ftmRouteItem input { +.flmRouteItem input { margin-top: 0.2rem; } -.ftmRouteItem span, -.ftmWorkspaceItem { +.flmRouteItem { + flex: 1 1 auto; + min-width: 0; +} + +.flmConnectLink { + align-items: center; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: var(--border-radius-sm); + color: var(--main-fg); + display: inline-flex; + flex: 0 0 auto; + font-size: 0.78rem; + padding: 0 var(--padding-sm); + text-decoration: none; +} + +.flmConnectLink:hover { + border-color: var(--main-fg); +} + +.flmRouteItem span, +.flmWorkspaceItem { display: flex; flex-direction: column; } -.ftmWorkspaceItem { +.flmWorkspaceItem { flex: 1 1 auto; } -.ftmWorkspaceDelete { +.flmWorkspaceDelete { align-self: stretch; flex: 0 0 auto; } -.ftmRouteItem small, -.ftmWorkspaceItem small, -.ftmWorkspaceItem span { +.flmRouteItem small, +.flmWorkspaceItem small, +.flmWorkspaceItem span { color: var(--text-muted); } -.ftmWorkspaceItem:hover, -.ftmRouteItem:hover, -.ftmCard button.selected { +.flmWorkspaceItem:hover, +.flmRouteItem:hover, +.flmCard button.selected { border-color: var(--main-fg); } -.ftmProfileGrid { +.flmProfileGrid { display: grid; gap: var(--gap-base); grid-template-columns: repeat(2, minmax(0, 1fr)); } -.ftmFeedbackButtons { +.flmFeedbackButtons { display: flex; flex-wrap: wrap; gap: var(--gap-xs); } -.ftmFeedbackButtons .longManeuverButton.selected { +.flmFeedbackButtons .longManeuverButton.selected { box-shadow: 0 0 0 3px var(--text-color); filter: brightness(1.2); } -.ftmTuneComparison { +.flmTuneComparison { background: rgba(255, 255, 255, 0.025); border-radius: var(--border-radius-sm); padding: var(--padding-base); } -.ftmTuneComparisonTable { +.flmTuneComparisonTable { align-items: center; display: grid; gap: var(--gap-xs) var(--gap-sm); @@ -126,40 +154,40 @@ margin-top: var(--margin-sm); } -.ftmTuneComparisonTable > div { +.flmTuneComparisonTable > div { min-width: 0; overflow-wrap: anywhere; padding: var(--padding-xs) 0; } -.ftmTuneComparisonHeader { +.flmTuneComparisonHeader { color: var(--text-muted); font-size: var(--font-size-sm); font-weight: var(--font-weight-demi-bold); } -.ftmTuneComparisonLabel { +.flmTuneComparisonLabel { font-weight: var(--font-weight-demi-bold); } -.ftmTuneComparisonArrow { +.flmTuneComparisonArrow { color: var(--text-muted); text-align: center; } -.ftmTuneComparisonChanged { +.flmTuneComparisonChanged { color: var(--main-fg); font-weight: var(--font-weight-demi-bold); } -.ftmDeltaBox { +.flmDeltaBox { background: rgba(255, 255, 255, 0.03); border-radius: var(--border-radius-sm); margin-top: var(--margin-sm); padding: var(--padding-sm); } -.ftmNotes { +.flmNotes { background: var(--secondary-bg); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: var(--border-radius-sm); @@ -171,19 +199,19 @@ width: 100%; } -.ftmTrackingOverview { +.flmTrackingOverview { background: rgba(255, 255, 255, 0.025); border-radius: var(--border-radius-sm); padding: var(--padding-base); } -.ftmTrackingLegend { +.flmTrackingLegend { display: flex; flex-wrap: wrap; gap: var(--gap-sm); } -.ftmTrackingLegend span { +.flmTrackingLegend span { align-items: center; color: var(--text-muted); display: inline-flex; @@ -191,22 +219,22 @@ gap: var(--gap-xs); } -.ftmTrackingLegend i { +.flmTrackingLegend i { border-radius: 999px; display: inline-block; height: 0.2rem; width: 1.4rem; } -.ftmTrackingLegend i.desired { +.flmTrackingLegend i.desired { background: #ef4444; } -.ftmTrackingLegend i.actual { +.flmTrackingLegend i.actual { background: #38bdf8; } -.ftmTrackingNotice { +.flmTrackingNotice { border-left: 2px solid var(--main-fg); color: var(--text-muted); font-size: var(--font-size-sm); @@ -214,14 +242,14 @@ padding: var(--padding-xs) var(--padding-sm); } -.ftmTrackingGrid { +.flmTrackingGrid { display: grid; gap: var(--gap-sm); grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); margin-top: var(--margin-base); } -.ftmTrackingCard { +.flmTrackingCard { background: rgba(255, 255, 255, 0.025); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: var(--border-radius-sm); @@ -229,25 +257,25 @@ padding: var(--padding-sm); } -.ftmTrackingCardHeader { +.flmTrackingCardHeader { align-items: flex-start; display: flex; gap: var(--gap-sm); justify-content: space-between; } -.ftmTrackingCardHeader > div { +.flmTrackingCardHeader > div { display: flex; flex-direction: column; } -.ftmTrackingCardHeader span, -.ftmTrackingCard small { +.flmTrackingCardHeader span, +.flmTrackingCard small { color: var(--text-muted); font-size: var(--font-size-xs); } -.ftmTrackingCard small { +.flmTrackingCard small { display: block; margin-top: var(--margin-xs); overflow: hidden; @@ -255,35 +283,35 @@ white-space: nowrap; } -.ftmTrackingPlot { +.flmTrackingPlot { display: block; height: auto; margin-top: var(--margin-xs); width: 100%; } -.ftmTrackingPlotBackground { +.flmTrackingPlotBackground { fill: var(--input-bg); } -.ftmTrackingEventRegion { +.flmTrackingEventRegion { fill: rgba(139, 108, 197, 0.14); } -.ftmTrackingAxis, -.ftmTrackingZero { +.flmTrackingAxis, +.flmTrackingZero { fill: none; stroke: rgba(255, 255, 255, 0.18); stroke-width: 1; vector-effect: non-scaling-stroke; } -.ftmTrackingZero { +.flmTrackingZero { stroke-dasharray: 3 4; } -.ftmTrackingDesired, -.ftmTrackingActual { +.flmTrackingDesired, +.flmTrackingActual { fill: none; stroke-linecap: round; stroke-linejoin: round; @@ -291,27 +319,27 @@ vector-effect: non-scaling-stroke; } -.ftmTrackingDesired { +.flmTrackingDesired { stroke: #ef4444; } -.ftmTrackingActual { +.flmTrackingActual { stroke: #38bdf8; } -.ftmTrackingAxisLabel { +.flmTrackingAxisLabel { fill: var(--text-muted); font-size: 9px; } -.ftmTrackingMeta { +.flmTrackingMeta { display: flex; flex-wrap: wrap; gap: var(--gap-xs); margin-top: var(--margin-xs); } -.ftmTrackingMeta span { +.flmTrackingMeta span { background: var(--input-bg); border-radius: 999px; color: var(--text-muted); @@ -320,12 +348,12 @@ } @media only screen and (max-width: 900px) { - .ftmTwoColumn, - .ftmProfileGrid { + .flmTwoColumn, + .flmProfileGrid { grid-template-columns: 1fr; } - .ftmTuneComparisonTable { + .flmTuneComparisonTable { font-size: var(--font-size-sm); grid-template-columns: minmax(8rem, 1.3fr) minmax(5rem, 1fr) auto minmax(5rem, 1fr); } diff --git a/starpilot/system/the_galaxy/assets/components/tools/tuning.js b/starpilot/system/the_galaxy/assets/components/tools/tuning.js index 5254aa2ed..948e69244 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/tuning.js +++ b/starpilot/system/the_galaxy/assets/components/tools/tuning.js @@ -14,6 +14,7 @@ const state = reactive({ truncatedRoutes: false, routeProgress: 0, routeTotal: 0, + connectDongleId: "", workspace: { reports: [], activeTrial: null, status: {} }, status: {}, report: null, @@ -46,6 +47,13 @@ function safeCount(value) { return Number.isFinite(n) ? n : 0 } +function connectRouteUrl(routeName) { + const dongleId = String(state.connectDongleId || "").trim() + const routeId = String(routeName || "").trim() + if (!dongleId || !routeId) return "" + return `https://connect.comma.ai/${encodeURIComponent(dongleId)}/${encodeURIComponent(routeId)}` +} + function formatStatusAge(updatedAt) { const updated = Number(updatedAt) if (!Number.isFinite(updated) || updated <= 0) return "unknown" @@ -151,11 +159,14 @@ async function fetchRoutes() { state.routeProgress = safeCount(data.progress) state.routeTotal = safeCount(data.total) } + if (typeof data.connectDongleId === "string") { + state.connectDongleId = data.connectDongleId + } if (Array.isArray(data.routes)) { enqueueRoutes(data.routes) } } catch (error) { - console.error("[ftm] failed to parse route payload", error) + console.error("[flm] failed to parse route payload", error) } } } @@ -179,7 +190,7 @@ async function fetchRoutes() { async function fetchWorkspace() { try { state.loadingWorkspace = true - const response = await fetch("/api/ftm/workspace") + const response = await fetch("/api/flm/workspace") const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to load tuning workspace.") state.workspace = payload @@ -201,7 +212,7 @@ function syncFeedbackState(report) { async function loadReport(reportId) { if (!reportId) return try { - const response = await fetch(`/api/ftm/report/${encodeURIComponent(reportId)}`) + const response = await fetch(`/api/flm/report/${encodeURIComponent(reportId)}`) const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to load tuning report.") state.report = payload @@ -218,7 +229,7 @@ async function deleteReport(reportId) { state.runningAction = true try { - const response = await fetch(`/api/ftm/report/${encodeURIComponent(reportId)}`, { method: "DELETE" }) + const response = await fetch(`/api/flm/report/${encodeURIComponent(reportId)}`, { method: "DELETE" }) const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to delete tuning report.") @@ -243,7 +254,7 @@ async function clearWorkspace() { state.runningAction = true try { - const response = await fetch("/api/ftm/workspace/clear", { method: "POST" }) + const response = await fetch("/api/flm/workspace/clear", { method: "POST" }) const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to clear tuning workspace.") @@ -262,7 +273,7 @@ async function clearWorkspace() { async function fetchStatus() { try { - const response = await fetch("/api/ftm/status") + const response = await fetch("/api/flm/status") const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to load tuning status.") state.status = { @@ -323,7 +334,7 @@ async function runAnalyze() { if (!state.selectedRoutes.length || state.runningAction) return state.runningAction = true try { - const response = await fetch("/api/ftm/analyze", { + const response = await fetch("/api/flm/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ routes: state.selectedRoutes }), @@ -331,7 +342,7 @@ async function runAnalyze() { const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to start tuning analysis.") state.status = payload.status || {} - showSnackbar(payload.message || "FTM analysis started.") + showSnackbar(payload.message || "FLM analysis started.") } catch (error) { state.error = error?.message || "Failed to start tuning analysis." showSnackbar(state.error, "error") @@ -344,11 +355,11 @@ async function stopAnalyze() { if (state.runningAction) return state.runningAction = true try { - const response = await fetch("/api/ftm/analyze/stop", { method: "POST" }) + const response = await fetch("/api/flm/analyze/stop", { method: "POST" }) const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to stop tuning analysis.") state.status = payload.status || {} - showSnackbar(payload.message || "FTM analysis stopped.") + showSnackbar(payload.message || "FLM analysis stopped.") } catch (error) { state.error = error?.message || "Failed to stop tuning analysis." showSnackbar(state.error, "error") @@ -361,7 +372,7 @@ async function applyProfile(profileId) { if (!state.report?.reportId || !profileId || state.runningAction) return state.runningAction = true try { - const response = await fetch("/api/ftm/trials/apply", { + const response = await fetch("/api/flm/trials/apply", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ reportId: state.report.reportId, profileId }), @@ -385,7 +396,7 @@ async function selectPath(pathKey) { state.runningAction = true try { - const response = await fetch(`/api/ftm/report/${encodeURIComponent(state.report.reportId)}/path`, { + const response = await fetch(`/api/flm/report/${encodeURIComponent(state.report.reportId)}/path`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ pathKey }), @@ -407,7 +418,7 @@ async function revertProfile() { if (state.runningAction) return state.runningAction = true try { - const response = await fetch("/api/ftm/trials/revert", { method: "POST" }) + const response = await fetch("/api/flm/trials/revert", { method: "POST" }) const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to revert trial profile.") state.error = "" @@ -427,7 +438,7 @@ async function acceptCurrentAsBaseline() { state.runningAction = true try { - const response = await fetch("/api/ftm/trials/accept", { method: "POST" }) + const response = await fetch("/api/flm/trials/accept", { method: "POST" }) const payload = await response.json() if (!response.ok) throw new Error(payload.error || "Failed to keep the current tune.") state.error = "" @@ -463,7 +474,7 @@ async function saveFeedback() { if (!state.report?.reportId || state.runningAction) return state.runningAction = true try { - const response = await fetch("/api/ftm/feedback", { + const response = await fetch("/api/flm/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -548,9 +559,9 @@ function activeTrialProfile() { return allReportProfiles().find((profile) => profile.id === activeTrial.profileId) || null } -function mergedFtmOverrides() { - const current = state.report?.currentParams?.FTMActiveOverrides || {} - const trial = activeTrialProfile()?.ftmOverrides || {} +function mergedFlmOverrides() { + const current = state.report?.currentParams?.FLMActiveOverrides || {} + const trial = activeTrialProfile()?.flmOverrides || {} return { baseFrictionThresholds: { ...(current.baseFrictionThresholds || {}), @@ -590,8 +601,8 @@ function tuneComparisonRows() { current: Object.hasOwn(trialGeneric, key) ? trialGeneric[key] : current[key], })) - const overrides = mergedFtmOverrides() - for (const [family, payload] of Object.entries(stock.FTMBaseFrictionThresholds || {})) { + const overrides = mergedFlmOverrides() + for (const [family, payload] of Object.entries(stock.FLMBaseFrictionThresholds || {})) { rows.push({ key: `friction-threshold-${family}`, label: `${family} friction threshold`, @@ -601,11 +612,11 @@ function tuneComparisonRows() { } for (const [symbol, currentValue] of Object.entries(overrides.vehicleKnobs || {})) { - if (!Object.hasOwn(stock.FTMVehicleKnobs || {}, symbol)) continue + if (!Object.hasOwn(stock.FLMVehicleKnobs || {}, symbol)) continue rows.push({ key: symbol, label: symbol.split(".").slice(1).join("."), - stock: stock.FTMVehicleKnobs[symbol], + stock: stock.FLMVehicleKnobs[symbol], current: currentValue, codeLabel: symbol, }) @@ -625,25 +636,25 @@ function renderTuneComparison() { if (!rows.length) return "" const profile = activeTrialProfile() return html` -
-
+
+
-

Stock vs Current FTM

+

Stock vs Current FLM

- ${profile ? `Includes active trial: ${profile.pathLabel || "FTM"} / ${profile.label}` : "Current values captured when this route was analyzed."} + ${profile ? `Includes active trial: ${profile.pathLabel || "FLM"} / ${profile.label}` : "Current values captured when this route was analyzed."}

-
-
Parameter
-
Stock
-
-
FTM
+
+
Parameter
+
Stock
+
+
FLM
${rows.map((row) => html` -
${row.label}
+
${row.label}
${formatTuneComparisonValue(row.stock)}
-
>
-
${formatTuneComparisonValue(row.current)}
+
>
+
${formatTuneComparisonValue(row.current)}
`)}
@@ -731,18 +742,18 @@ function renderTrackingPlot(plot) { const zeroY = 136 - ((0 - yMin) / Math.max(yMax - yMin, 0.001)) * 124 return html` - - - - - - - - - ${yMax.toFixed(1)} - ${yMin.toFixed(1)} - 0s - ${duration.toFixed(1)}s + + + + + + + + + ${yMax.toFixed(1)} + ${yMin.toFixed(1)} + 0s + ${duration.toFixed(1)}s ` } @@ -752,28 +763,28 @@ function renderTrackingOverview() { if (!items.length) return "" return html` -
-
+
+

Tracking Overview

Desired vs actual lateral acceleration (m/s^2) in representative intervention-free windows. The shaded area is the classified event.

-
+
Desired Actual
-
+
No fit score by design. Small phase separation is normal, and closer traces do not automatically mean the steering feels better.
-
+
${items.map((item) => html` -
-
+
+
${item.overviewTitle} ${String(item.bucket || "event").replace(/_/g, " ")} @@ -781,7 +792,7 @@ function renderTrackingOverview() { ${Number(item.plotData.meanSpeedMph || 0).toFixed(1)} mph
${renderTrackingPlot(item.plotData)} -
+
${item.evidence?.directionBias || item.plotData.direction || "center"} ${item.evidence?.speedBand || item.plotData.speedBand || "mixed"} ${Number(item.plotData.eventDurationSec || 0).toFixed(1)}s event @@ -796,11 +807,11 @@ function renderTrackingOverview() { function renderProfile(profile) { const genericEntries = Object.entries(profile.genericParams || {}).filter(([key]) => key !== "AdvancedLateralTune") - const frictionEntries = Object.entries(profile.ftmOverrides?.baseFrictionThresholds || {}) - const vehicleKnobEntries = Object.entries(profile.ftmOverrides?.vehicleKnobs || {}) + const frictionEntries = Object.entries(profile.flmOverrides?.baseFrictionThresholds || {}) + const vehicleKnobEntries = Object.entries(profile.flmOverrides?.vehicleKnobs || {}) return html` -
-
+
+

${profile.label}

${profile.description}

@@ -813,7 +824,7 @@ function renderProfile(profile) {
-
+
Generic Params
    @@ -823,7 +834,7 @@ function renderProfile(profile) {
-
FTM Overrides
+
FLM Overrides
    ${frictionEntries.map(([family, payload]) => html`
  • ${family}: ${renderCurve(payload?.values || [])}
  • `)} ${vehicleKnobEntries.map(([key, value]) => html`
  • ${key}: ${Number(value).toFixed(3)}
  • `)} @@ -838,8 +849,8 @@ function renderProfile(profile) { function renderSuggestion(suggestion) { const currentVsSuggested = suggestion.currentVsSuggested return html` -
    -
    +
    +

    ${suggestion.bucket.replace(/_/g, " ")}

    @@ -848,7 +859,7 @@ function renderSuggestion(suggestion) { ${safeCount(suggestion.evidence?.eventCount)} event(s)

    -
    +
    ${() => state.report ? html` -
    -
    +
    +

    Report Summary

    @@ -1107,12 +1127,12 @@ export function Tuning() { ${() => renderTuneComparison()} -
    +
    ${reportPaths().map((path) => renderPathSummary(path))}
    ${() => (state.report.warnings || []).length ? html` -
    +

    Warnings

      ${(state.report.warnings || []).map((warning) => html`
    • ${warning}
    • `)} @@ -1121,7 +1141,7 @@ export function Tuning() { ` : ""} ${() => (state.report.addTheseParametersAndStartHere || []).length ? html` -
      +

      Add These Parameters And Start Here

        ${(state.report.addTheseParametersAndStartHere || []).map((line) => html`
      • ${line}
      • `)} @@ -1130,8 +1150,8 @@ export function Tuning() { ` : ""}
    -
    -
    +
    +

    Active Findings: ${primaryPath()?.title || "Recommendations"}

    @@ -1148,21 +1168,21 @@ export function Tuning() {

    -
    +
    ${((primaryPath()?.suggestions) || []).map((suggestion) => renderSuggestion(suggestion))}
    -
    +

    Trial Profiles

    - Apply one bounded profile at a time. Revert restores the exact advanced-lateral and FTM state that existed before the trial. + Apply one bounded profile at a time. Revert restores the exact advanced-lateral and FLM state that existed before the trial.

    -
    +
    ${reportPaths().length ? reportPaths().map((path) => html`
    @@ -1177,7 +1197,7 @@ export function Tuning() {
    ` : html` -
    +

    No Active Report

    Select local routes, run analysis, or open one of the saved reports from the workspace panel. diff --git a/starpilot/system/the_galaxy/ftm_workspace.py b/starpilot/system/the_galaxy/flm_workspace.py similarity index 89% rename from starpilot/system/the_galaxy/ftm_workspace.py rename to starpilot/system/the_galaxy/flm_workspace.py index 932b33101..dce5e0015 100644 --- a/starpilot/system/the_galaxy/ftm_workspace.py +++ b/starpilot/system/the_galaxy/flm_workspace.py @@ -4,6 +4,7 @@ from __future__ import annotations import json import math import os +import shutil import signal import subprocess import sys @@ -21,14 +22,14 @@ from opendbc.car.hyundai.values import HyundaiFlags from openpilot.common.params import Params from openpilot.selfdrive.controls.lib.latcontrol_torque import KP from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import ( - FTM_FRICTION_SPEED_KNOTS, - get_ftm_capabilities, - get_ftm_rich_profile_key, - get_ftm_supported_vehicle_knobs, + FLM_FRICTION_SPEED_KNOTS, + get_flm_capabilities, + get_flm_rich_profile_key, + get_flm_supported_vehicle_knobs, get_gm_base_friction_threshold, get_hkg_canfd_base_friction_threshold, get_standard_friction_threshold, - normalize_ftm_overrides, + normalize_flm_overrides, ) from openpilot.starpilot.common.lateral_delay import full_lateral_delay from openpilot.system.hardware import PC @@ -37,12 +38,12 @@ from openpilot.tools.lib.logreader import LogReader from openpilot.starpilot.system.the_galaxy import utilities -FTM_STATUS_PATH = Path("/tmp/galaxy_ftm_status.json") -FTM_LOG_PATH = Path("/tmp/galaxy_ftm.log") -FTM_STATUS_MAX_AGE_SECONDS = 3600.0 -FTM_ANALYZER_ROUTE_LIMIT = 8 -FTM_ANALYZER_PROCESS = None -FTM_ANALYZER_LOCK = threading.Lock() +FLM_STATUS_PATH = Path("/tmp/galaxy_flm_status.json") +FLM_LOG_PATH = Path("/tmp/galaxy_flm.log") +FLM_STATUS_MAX_AGE_SECONDS = 3600.0 +FLM_ANALYZER_ROUTE_LIMIT = 8 +FLM_ANALYZER_PROCESS = None +FLM_ANALYZER_LOCK = threading.Lock() TRIAL_PARAM_SPECS = { "AdvancedLateralTune": "bool", @@ -54,12 +55,12 @@ TRIAL_PARAM_SPECS = { "SteerKP": "float", "SteerLatAccel": "float", "SteerRatio": "float", - "FTMActiveProfileId": "string", - "FTMActiveOverrides": "json", - "FTMTrialApplied": "bool", + "FLMActiveProfileId": "string", + "FLMActiveOverrides": "json", + "FLMTrialApplied": "bool", } -FTM_ADVANCED_LATERAL_PARAM_KEYS = { +FLM_ADVANCED_LATERAL_PARAM_KEYS = { "AdvancedLateralTune", "ForceAutoTune", "ForceAutoTuneOff", @@ -70,7 +71,7 @@ FTM_ADVANCED_LATERAL_PARAM_KEYS = { "SteerLatAccel", "SteerRatio", } -FTM_TRIAL_BASELINE_PARAM = "FTMTrialBaseline" +FLM_TRIAL_BASELINE_PARAM = "FLMTrialBaseline" GENERIC_PARAM_METADATA = { "SteerDelay": {"min": 0.01, "max": 1.0, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True}, @@ -80,7 +81,7 @@ GENERIC_PARAM_METADATA = { "SteerRatio": {"min": 5.0, "max": 25.0, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True}, } -FTM_REFERENCE_MODEL = { +FLM_REFERENCE_MODEL = { "version": 1, "families": { "turn_in_boost": { @@ -106,7 +107,7 @@ FTM_REFERENCE_MODEL = { }, } -FTM_PATH_SPECS = { +FLM_PATH_SPECS = { "baseline_fix": { "title": "Baseline Fix", "description": "Use the broad knobs first to get the car into the right zip code before touching narrower cleanup layers.", @@ -121,8 +122,8 @@ FTM_PATH_SPECS = { }, } -FTM_DRIVER_OVERRIDE_PRE_BUFFER_S = 0.35 -FTM_DRIVER_OVERRIDE_POST_BUFFER_S = 1.0 +FLM_DRIVER_OVERRIDE_PRE_BUFFER_S = 0.35 +FLM_DRIVER_OVERRIDE_POST_BUFFER_S = 1.0 @dataclass @@ -136,7 +137,7 @@ class RouteSource: @dataclass -class FTMSample: +class FLMSample: route: str segment: int t: float @@ -165,12 +166,81 @@ def _get_galaxy_dir() -> Path: return Path(Paths.comma_home()) / "starpilot" / "data" / "galaxy" if PC else Path("/data/galaxy") -def get_ftm_workspace_root() -> Path: - return _get_galaxy_dir() / "ftm" +def get_flm_workspace_root() -> Path: + return _get_galaxy_dir() / "flm" + + +def _legacy_workspace_root() -> Path: + return _get_galaxy_dir() / "".join(("f", "t", "m")) + + +def _migrate_legacy_payload(value): + legacy_upper = "".join(("F", "T", "M")) + legacy_lower = legacy_upper.lower() + if isinstance(value, dict): + migrated = {} + for key, item in value.items(): + migrated_key = str(key) + if migrated_key.startswith(legacy_upper): + migrated_key = f"FLM{migrated_key[len(legacy_upper):]}" + elif migrated_key.startswith(legacy_lower): + migrated_key = f"flm{migrated_key[len(legacy_lower):]}" + migrated[migrated_key] = _migrate_legacy_payload(item) + return migrated + if isinstance(value, list): + return [_migrate_legacy_payload(item) for item in value] + if isinstance(value, str): + legacy_method_name = "Firestar " + "Tuning Method" + return value.replace(legacy_method_name, "Firestar Lateral Method").replace(legacy_upper, "FLM") + return value + + +def _migrate_legacy_workspace(root: Path) -> None: + marker = root / ".flm_rebrand_v1" + if marker.exists(): + return + + legacy_root = _legacy_workspace_root() + if legacy_root.is_dir() and legacy_root != root: + root.parent.mkdir(parents=True, exist_ok=True) + if root.exists(): + shutil.copytree(legacy_root, root, dirs_exist_ok=True) + shutil.rmtree(legacy_root) + else: + legacy_root.replace(root) + + if not root.exists(): + return + + legacy_lower = "".join(("f", "t", "m")) + legacy_reference = root / "reference" / f"{legacy_lower}_reference.json" + current_reference = root / "reference" / "flm_reference.json" + if legacy_reference.is_file() and not current_reference.exists(): + legacy_reference.replace(current_reference) + + for path in root.rglob("*.json"): + try: + payload = json.loads(path.read_text(encoding="utf-8")) + migrated = _migrate_legacy_payload(payload) + if migrated != payload: + path.write_text(json.dumps(migrated, indent=2, sort_keys=True), encoding="utf-8") + except Exception: + continue + + legacy_upper = legacy_lower.upper() + for path in root.rglob("*.html"): + try: + content = path.read_text(encoding="utf-8") + content = content.replace(legacy_upper, "FLM").replace(legacy_lower, "flm") + path.write_text(content, encoding="utf-8") + except Exception: + continue + + marker.touch() def _workspace_paths() -> dict[str, Path]: - root = get_ftm_workspace_root() + root = get_flm_workspace_root() return { "root": root, "reports": root / "reports", @@ -181,14 +251,15 @@ def _workspace_paths() -> dict[str, Path]: } -def ensure_ftm_workspace() -> dict[str, Path]: +def ensure_flm_workspace() -> dict[str, Path]: + _migrate_legacy_workspace(get_flm_workspace_root()) paths = _workspace_paths() for key, path in paths.items(): if key != "root": path.mkdir(parents=True, exist_ok=True) - reference_path = paths["reference"] / "ftm_reference.json" + reference_path = paths["reference"] / "flm_reference.json" if not reference_path.exists(): - reference_path.write_text(json.dumps(FTM_REFERENCE_MODEL, indent=2, sort_keys=True), encoding="utf-8") + reference_path.write_text(json.dumps(FLM_REFERENCE_MODEL, indent=2, sort_keys=True), encoding="utf-8") return paths @@ -223,45 +294,45 @@ def _worker_env(repo_root: Path) -> dict[str, str]: return env -def read_ftm_status() -> dict[str, Any]: - data = _read_json(FTM_STATUS_PATH, {}) +def read_flm_status() -> dict[str, Any]: + data = _read_json(FLM_STATUS_PATH, {}) return data if isinstance(data, dict) else {} -def _write_ftm_status(payload: dict[str, Any]) -> None: +def _write_flm_status(payload: dict[str, Any]) -> None: payload = dict(payload) payload["updatedAt"] = time.time() - tmp_path = FTM_STATUS_PATH.with_suffix(".tmp") + tmp_path = FLM_STATUS_PATH.with_suffix(".tmp") tmp_path.write_text(json.dumps(payload, separators=(",", ":")), encoding="utf-8") - tmp_path.replace(FTM_STATUS_PATH) + tmp_path.replace(FLM_STATUS_PATH) -def clear_ftm_status() -> None: +def clear_flm_status() -> None: try: - FTM_STATUS_PATH.unlink() + FLM_STATUS_PATH.unlink() except FileNotFoundError: pass except OSError: pass -def ftm_analyzer_running() -> bool: - process = FTM_ANALYZER_PROCESS +def flm_analyzer_running() -> bool: + process = FLM_ANALYZER_PROCESS if process is not None and process.poll() is None: return True - status = read_ftm_status() + status = read_flm_status() pid = int(status.get("pid") or 0) started_at = float(status.get("startedAt") or 0.0) if pid <= 0 or started_at <= 0: return False - if (time.time() - started_at) > FTM_STATUS_MAX_AGE_SECONDS: - clear_ftm_status() + if (time.time() - started_at) > FLM_STATUS_MAX_AGE_SECONDS: + clear_flm_status() return False try: os.kill(pid, 0) except ProcessLookupError: - clear_ftm_status() + clear_flm_status() return False except PermissionError: return True @@ -270,12 +341,12 @@ def ftm_analyzer_running() -> bool: return True -def stop_ftm_background_analysis() -> bool: - global FTM_ANALYZER_PROCESS +def stop_flm_background_analysis() -> bool: + global FLM_ANALYZER_PROCESS - with FTM_ANALYZER_LOCK: - process = FTM_ANALYZER_PROCESS - status = read_ftm_status() + with FLM_ANALYZER_LOCK: + process = FLM_ANALYZER_PROCESS + status = read_flm_status() pid = int(status.get("pid") or 0) if process is not None and process.poll() is None: @@ -284,8 +355,8 @@ def stop_ftm_background_analysis() -> bool: process.wait(timeout=2.0) except subprocess.TimeoutExpired: process.kill() - FTM_ANALYZER_PROCESS = None - clear_ftm_status() + FLM_ANALYZER_PROCESS = None + clear_flm_status() return True if pid > 0: @@ -295,22 +366,22 @@ def stop_ftm_background_analysis() -> bool: pass except OSError: return False - clear_ftm_status() + clear_flm_status() return True return False -def start_ftm_background_analysis(route_names: list[str], footage_paths: list[str]) -> bool: - global FTM_ANALYZER_PROCESS +def start_flm_background_analysis(route_names: list[str], footage_paths: list[str]) -> bool: + global FLM_ANALYZER_PROCESS route_names = [str(route) for route in route_names if str(route).strip()] if not route_names: return False - ensure_ftm_workspace() - with FTM_ANALYZER_LOCK: - if ftm_analyzer_running(): + ensure_flm_workspace() + with FLM_ANALYZER_LOCK: + if flm_analyzer_running(): return True repo_root = Path(__file__).resolve().parents[3] @@ -322,14 +393,14 @@ def start_ftm_background_analysis(route_names: list[str], footage_paths: list[st str(Path(__file__).resolve()), "worker", json.dumps({ - "routes": route_names[:FTM_ANALYZER_ROUTE_LIMIT], + "routes": route_names[:FLM_ANALYZER_ROUTE_LIMIT], "footagePaths": [str(path) for path in footage_paths], }), ] log_file = None try: - log_file = open(FTM_LOG_PATH, "ab") - FTM_ANALYZER_PROCESS = subprocess.Popen( + log_file = open(FLM_LOG_PATH, "ab") + FLM_ANALYZER_PROCESS = subprocess.Popen( command, cwd=str(repo_root), env=_worker_env(repo_root), @@ -337,23 +408,23 @@ def start_ftm_background_analysis(route_names: list[str], footage_paths: list[st stderr=log_file, start_new_session=True, ) - _write_ftm_status({ - "pid": FTM_ANALYZER_PROCESS.pid, + _write_flm_status({ + "pid": FLM_ANALYZER_PROCESS.pid, "startedAt": time.time(), "running": True, "state": "queued", - "routes": route_names[:FTM_ANALYZER_ROUTE_LIMIT], + "routes": route_names[:FLM_ANALYZER_ROUTE_LIMIT], "progress": 0, - "total": len(route_names[:FTM_ANALYZER_ROUTE_LIMIT]), + "total": len(route_names[:FLM_ANALYZER_ROUTE_LIMIT]), }) except Exception: - FTM_ANALYZER_PROCESS = None + FLM_ANALYZER_PROCESS = None return False finally: if log_file is not None: log_file.close() - return ftm_analyzer_running() + return flm_analyzer_running() def _parse_segment_num(segment_name: str) -> int: @@ -422,7 +493,7 @@ def _route_label(route: str, segment: int) -> str: return f"{route}/{segment}" -def _event_direction(samples: list[FTMSample]) -> str: +def _event_direction(samples: list[FLMSample]) -> str: mean_desired = float(np.mean([sample.desired_la for sample in samples])) if mean_desired > 0.02: return "left" @@ -431,7 +502,7 @@ def _event_direction(samples: list[FTMSample]) -> str: return "center" -def _group_masked_events(samples: list[FTMSample], mask: list[bool], score_series: list[float], min_points: int = 5) -> list[dict[str, Any]]: +def _group_masked_events(samples: list[FLMSample], mask: list[bool], score_series: list[float], min_points: int = 5) -> list[dict[str, Any]]: events: list[dict[str, Any]] = [] start_idx = None for idx, active in enumerate(mask + [False]): @@ -465,7 +536,7 @@ def _group_masked_events(samples: list[FTMSample], mask: list[bool], score_serie return events -def _analysis_eligibility_mask(samples: list[FTMSample]) -> list[bool]: +def _analysis_eligibility_mask(samples: list[FLMSample]) -> list[bool]: eligible = [bool(sample.lat_active) for sample in samples] group_start = 0 while group_start < len(samples): @@ -479,7 +550,7 @@ def _analysis_eligibility_mask(samples: list[FTMSample]) -> list[bool]: sample = samples[idx] if sample.steering_pressed: last_override = sample.t - if sample.steering_pressed or (sample.t - last_override) <= FTM_DRIVER_OVERRIDE_POST_BUFFER_S: + if sample.steering_pressed or (sample.t - last_override) <= FLM_DRIVER_OVERRIDE_POST_BUFFER_S: eligible[idx] = False next_override = math.inf @@ -487,7 +558,7 @@ def _analysis_eligibility_mask(samples: list[FTMSample]) -> list[bool]: sample = samples[idx] if sample.steering_pressed: next_override = sample.t - if sample.steering_pressed or (next_override - sample.t) <= FTM_DRIVER_OVERRIDE_PRE_BUFFER_S: + if sample.steering_pressed or (next_override - sample.t) <= FLM_DRIVER_OVERRIDE_PRE_BUFFER_S: eligible[idx] = False # Force an event boundary between route segments even when lateral control stays active. @@ -498,7 +569,7 @@ def _analysis_eligibility_mask(samples: list[FTMSample]) -> list[bool]: return eligible -def _build_plot_data(samples: list[FTMSample], event: dict[str, Any], eligibility: list[bool] | None = None) -> dict[str, Any]: +def _build_plot_data(samples: list[FLMSample], event: dict[str, Any], eligibility: list[bool] | None = None) -> dict[str, Any]: start_idx = int(event["startIdx"]) end_idx = int(event["endIdx"]) event_route = samples[start_idx].route @@ -579,7 +650,7 @@ def _build_plot_svg(plot_data: dict[str, Any]) -> str: return " ".join(coords) return ( - "" + "" "" "" f"" @@ -588,8 +659,8 @@ def _build_plot_svg(plot_data: dict[str, Any]) -> str: ) -def _segment_samples(segment_source: RouteSource) -> tuple[list[FTMSample], car.CarParams | None, dict[str, str]]: - samples: list[FTMSample] = [] +def _segment_samples(segment_source: RouteSource) -> tuple[list[FLMSample], car.CarParams | None, dict[str, str]]: + samples: list[FLMSample] = [] car_params = None init_data: dict[str, str] = {} latest: dict[str, Any] = {} @@ -634,7 +705,7 @@ def _segment_samples(segment_source: RouteSource) -> tuple[list[FTMSample], car. roll_deg = math.degrees(float(getattr(live_parameters, "roll", 0.0) or 0.0)) if live_parameters is not None else 0.0 out_torque = float(getattr(getattr(car_output, "actuatorsOutput", None), "torque", 0.0) or 0.0) if car_output is not None else 0.0 - samples.append(FTMSample( + samples.append(FLMSample( route=segment_source.route, segment=segment_source.segment_num, t=float(msg.logMonoTime) / 1e9, @@ -679,9 +750,9 @@ def _current_param_state(CP, params: Params) -> dict[str, Any]: "SteerKP": params.get_float("SteerKP", return_default=True, default=KP) if advanced_enabled else KP, "SteerLatAccel": params.get_float("SteerLatAccel", return_default=True, default=stock_lat_accel) if advanced_enabled else stock_lat_accel, "SteerRatio": params.get_float("SteerRatio", return_default=True, default=stock_ratio) if advanced_enabled else stock_ratio, - "FTMActiveProfileId": params.get("FTMActiveProfileId", encoding="utf-8") or "", - "FTMActiveOverrides": normalize_ftm_overrides(params.get("FTMActiveOverrides", encoding="utf-8") or "{}"), - "FTMTrialApplied": params.get_bool("FTMTrialApplied"), + "FLMActiveProfileId": params.get("FLMActiveProfileId", encoding="utf-8") or "", + "FLMActiveOverrides": normalize_flm_overrides(params.get("FLMActiveOverrides", encoding="utf-8") or "{}"), + "FLMTrialApplied": params.get_bool("FLMTrialApplied"), } @@ -691,7 +762,7 @@ def _stock_param_state(CP, capabilities: dict[str, Any]) -> dict[str, Any]: rich_profile = capabilities.get("richProfileKey") rich_knobs = { symbol: float(meta["defaultValue"]) - for symbol, meta in get_ftm_supported_vehicle_knobs().items() + for symbol, meta in get_flm_supported_vehicle_knobs().items() if rich_profile and meta.get("profile") == rich_profile } return { @@ -701,13 +772,13 @@ def _stock_param_state(CP, capabilities: dict[str, Any]) -> dict[str, Any]: "SteerKP": float(KP), "SteerLatAccel": float(getattr(torque_tune, "latAccelFactor", 0.0) or 0.0) if torque_tune is not None else 0.0, "SteerRatio": float(getattr(CP, "steerRatio", 0.0) or 0.0), - "FTMBaseFrictionThresholds": { + "FLMBaseFrictionThresholds": { friction_family: { - "speedKnots": list(FTM_FRICTION_SPEED_KNOTS), + "speedKnots": list(FLM_FRICTION_SPEED_KNOTS), "values": _baseline_family_curve(friction_family), }, } if torque_tune is not None else {}, - "FTMVehicleKnobs": rich_knobs, + "FLMVehicleKnobs": rich_knobs, } @@ -749,14 +820,14 @@ def _baseline_family_curve(family: str) -> list[float]: "standard": get_standard_friction_threshold, "hkg_canfd": get_hkg_canfd_base_friction_threshold, }.get(family, get_standard_friction_threshold) - return [round(float(getter(knot)), 4) for knot in FTM_FRICTION_SPEED_KNOTS] + return [round(float(getter(knot)), 4) for knot in FLM_FRICTION_SPEED_KNOTS] def _current_family_curve(family: str, current: dict[str, Any]) -> list[float]: - active_overrides = current.get("FTMActiveOverrides", {}) if isinstance(current, dict) else {} + active_overrides = current.get("FLMActiveOverrides", {}) if isinstance(current, dict) else {} payload = active_overrides.get("baseFrictionThresholds", {}).get(family, {}) if isinstance(active_overrides, dict) else {} values = payload.get("values", []) if isinstance(payload, dict) else [] - if isinstance(values, list) and len(values) == len(FTM_FRICTION_SPEED_KNOTS): + if isinstance(values, list) and len(values) == len(FLM_FRICTION_SPEED_KNOTS): try: return [round(float(value), 4) for value in values] except Exception: @@ -776,11 +847,11 @@ def _round_to_precision(value: float, precision: float) -> float: def _current_vehicle_knob_value(symbol: str, current: dict[str, Any]) -> float | None: - knob = get_ftm_supported_vehicle_knobs().get(symbol) + knob = get_flm_supported_vehicle_knobs().get(symbol) if knob is None: return None - active_overrides = current.get("FTMActiveOverrides", {}) if isinstance(current, dict) else {} + active_overrides = current.get("FLMActiveOverrides", {}) if isinstance(current, dict) else {} vehicle_knobs = active_overrides.get("vehicleKnobs", {}) if isinstance(active_overrides, dict) else {} try: return float(vehicle_knobs.get(symbol, knob["defaultValue"])) @@ -789,7 +860,7 @@ def _current_vehicle_knob_value(symbol: str, current: dict[str, Any]) -> float | def _vehicle_knob_adjustment(symbol: str, delta: float, current: dict[str, Any] | None = None) -> dict[str, Any] | None: - knob = get_ftm_supported_vehicle_knobs().get(symbol) + knob = get_flm_supported_vehicle_knobs().get(symbol) if knob is None: return None current_value = _current_vehicle_knob_value(symbol, current or {}) @@ -811,10 +882,10 @@ def _rich_profile_supports_knob(capabilities: dict[str, Any], suffix: str) -> bo rich_profile = capabilities.get("richProfileKey") if not rich_profile: return False - return f"{rich_profile}.{suffix}" in get_ftm_supported_vehicle_knobs() + return f"{rich_profile}.{suffix}" in get_flm_supported_vehicle_knobs() -def _build_event_summaries(samples: list[FTMSample]) -> tuple[list[dict[str, Any]], dict[str, Any]]: +def _build_event_summaries(samples: list[FLMSample]) -> tuple[list[dict[str, Any]], dict[str, Any]]: eligibility = _analysis_eligibility_mask(samples) active_samples = [sample for sample, allowed in zip(samples, eligibility, strict=True) if allowed] if not active_samples: @@ -964,7 +1035,7 @@ def _build_event_summaries(samples: list[FTMSample]) -> tuple[list[dict[str, Any return sorted(summaries, key=lambda item: item["severity"], reverse=True), summary_stats -def _summaries_from_events(bucket: str, samples: list[FTMSample], events: list[dict[str, Any]], +def _summaries_from_events(bucket: str, samples: list[FLMSample], events: list[dict[str, Any]], eligibility: list[bool] | None = None) -> list[dict[str, Any]]: grouped: dict[tuple[str, str], list[dict[str, Any]]] = {} for event in events: @@ -1007,7 +1078,7 @@ def _summaries_from_events(bucket: str, samples: list[FTMSample], events: list[d return summaries -def classify_torque_samples(samples: list[FTMSample]) -> tuple[list[dict[str, Any]], dict[str, Any]]: +def classify_torque_samples(samples: list[FLMSample]) -> tuple[list[dict[str, Any]], dict[str, Any]]: return _build_event_summaries(samples) @@ -1282,7 +1353,7 @@ def _why_this_knob(adjustment: dict[str, Any]) -> str: def _render_adjustment_line(adjustment: dict[str, Any]) -> str: if adjustment["type"] == "friction_curve": curve = ", ".join(f"{value:.3f}" for value in adjustment["suggested"]) - return f"Adjust {adjustment['family']} friction threshold curve at {FTM_FRICTION_SPEED_KNOTS} m/s to [{curve}]." + return f"Adjust {adjustment['family']} friction threshold curve at {FLM_FRICTION_SPEED_KNOTS} m/s to [{curve}]." if adjustment["type"] == "vehicle_knob": return f"Move `{adjustment['symbol']}` from {adjustment['current']:.3f} to {adjustment['suggested']:.3f}." return f"Move `{adjustment['paramKey']}` from {adjustment['current']:.3f} to {adjustment['suggested']:.3f}." @@ -1434,7 +1505,7 @@ def _merge_primary_adjustments(suggestions: list[dict[str, Any]], multiplier: fl if "SteerDelay" in params_delta: params_delta["UseAutoSteerDelay"] = False - supported_knobs = get_ftm_supported_vehicle_knobs() + supported_knobs = get_flm_supported_vehicle_knobs() for symbol, bucket in vehicle_targets.items(): meta = supported_knobs.get(symbol) if meta is None or bucket["weight"] <= 0: @@ -1453,9 +1524,9 @@ def _merge_primary_adjustments(suggestions: list[dict[str, Any]], multiplier: fl for idx in range(len(bucket["current"])) ] if any(not math.isclose(float(bucket["current"][idx]), values[idx], abs_tol=1e-6) for idx in range(len(values))): - overrides["baseFrictionThresholds"][family] = {"speedKnots": list(FTM_FRICTION_SPEED_KNOTS), "values": values} + overrides["baseFrictionThresholds"][family] = {"speedKnots": list(FLM_FRICTION_SPEED_KNOTS), "values": values} - overrides = normalize_ftm_overrides(overrides) + overrides = normalize_flm_overrides(overrides) return params_delta, overrides, requires_force_auto_tune_off @@ -1636,7 +1707,7 @@ def build_trial_profiles(report_id: str, suggestions: list[dict[str, Any]], feed "pathLabel": path_label, "description": f"{label} {path_label.lower()} trial generated from {len(actionable)} confirmed symptom dimension(s).", "genericParams": params_delta, - "ftmOverrides": overrides, + "flmOverrides": overrides, "requiresForceAutoTuneOff": bool(force_auto_tune_off), "capabilities": capabilities, } @@ -1661,7 +1732,7 @@ def _add_parameters_start_here(capabilities: dict[str, Any], suggestions: list[d if any(suggestion.get("primaryAdjustmentRaw", {}).get("type") == "friction_curve" for suggestion in suggestions if suggestion.get("primaryAdjustmentRaw")): lines.append("Friction-threshold changes are active in this pass because the logs point to small-signal steering behavior, not just whole-tune authority.") if not capabilities.get("richProfileKey"): - lines.append("This car does not expose richer live FTM knobs yet, so generic advanced params and friction-threshold trials are the first code-level moves to test.") + lines.append("This car does not expose richer live FLM knobs yet, so generic advanced params and friction-threshold trials are the first code-level moves to test.") return lines @@ -1677,7 +1748,7 @@ def build_recommendation_paths(report_id: str, summaries: list[dict[str, Any]], ordered_keys = [decision["primaryPathKey"], decision["alternatePathKey"]] paths = [] for path_key in ordered_keys: - spec = FTM_PATH_SPECS[path_key] + spec = FLM_PATH_SPECS[path_key] suggestions = all_suggestions[path_key] profiles = build_trial_profiles(report_id, suggestions, feedback, capabilities, path_key=path_key, path_label=spec["title"]) paths.append({ @@ -1705,7 +1776,7 @@ def _render_report_html(report: dict[str, Any]) -> str: evidence = suggestion.get("evidence", {}) current_vs_suggested = suggestion.get("currentVsSuggested") if current_vs_suggested is None: - delta_html = "

    No trial adjustment suggested for this dimension.

    " + delta_html = "

    No trial adjustment suggested for this dimension.

    " elif current_vs_suggested["type"] == "friction_curve": delta_html = ( f"

    Current: {current_vs_suggested['current']}

    " @@ -1717,7 +1788,7 @@ def _render_report_html(report: dict[str, Any]) -> str: f"

    {label}: {float(current_vs_suggested['current']):.3f} -> {float(current_vs_suggested['suggested']):.3f}

    " ) findings_html.append( - "
    " + "
    " f"

    {suggestion['bucket'].replace('_', ' ').title()}

    " f"

    Observed behavior: {suggestion['observedBehavior']}

    " f"

    Likely interpretation: {suggestion['likelyInterpretation']}

    " @@ -1740,7 +1811,7 @@ def _render_report_html(report: dict[str, Any]) -> str: badges.append("Active") badge = " / ".join(badges) or "Alternate" path_html.append( - "
    " + "
    " f"

    {path.get('title', 'Path')}

    " f"

    {badge}: {path.get('description', '')}

    " f"

    Why this path: {path.get('whySelected', '')}

    " @@ -1759,45 +1830,45 @@ def _render_report_html(report: dict[str, Any]) -> str: continue generic_lines.append(f"
  • {key}: {value}
  • ") override_lines = [] - for family, payload in profile.get("ftmOverrides", {}).get("baseFrictionThresholds", {}).items(): + for family, payload in profile.get("flmOverrides", {}).get("baseFrictionThresholds", {}).items(): override_lines.append(f"
  • {family} curve: {payload.get('values', [])}
  • ") - for key, value in profile.get("ftmOverrides", {}).get("vehicleKnobs", {}).items(): + for key, value in profile.get("flmOverrides", {}).get("vehicleKnobs", {}).items(): override_lines.append(f"
  • {key}: {value}
  • ") cards.append( - "
    " + "
    " f"

    {profile['label']}

    " f"

    {profile['description']}

    " f"

    Generic params:

      {''.join(generic_lines) or '
    • None
    • '}
    " - f"

    FTM overrides:

      {''.join(override_lines) or '
    • None
    • '}
    " + f"

    FLM overrides:

      {''.join(override_lines) or '
    • None
    • '}
    " "
    " ) - path_profiles_html = "".join(cards) or "

    No trial profiles generated for this path.

    " + path_profiles_html = "".join(cards) or "

    No trial profiles generated for this path.

    " profile_html.append( f"

    {path.get('title', 'Path')} Profiles

    " f"{path_profiles_html}" ) start_here_lines = "".join(f"
  • {line}
  • " for line in report.get("addTheseParametersAndStartHere", [])) - start_here_html = f"

    Add These Parameters And Start Here

      {start_here_lines}
    " if start_here_lines else "" - findings_block = "".join(findings_html) or "

    No strong findings.

    " - profiles_block = "".join(profile_html) or "

    No trial profiles generated.

    " + start_here_html = f"

    Add These Parameters And Start Here

      {start_here_lines}
    " if start_here_lines else "" + findings_block = "".join(findings_html) or "

    No strong findings.

    " + profiles_block = "".join(profile_html) or "

    No trial profiles generated.

    " return ( "" - "FTM Tuning Report" + "FLM Tuning Report" "" - f"

    FTM Tuning Report

    " + f"

    FLM Tuning Report

    " f"

    {report['car']['carFingerprint']} | {report['car'].get('gitBranch', '')} {report['car'].get('gitCommit', '')}

    " - f"

    Routes

    {', '.join(report.get('routeNames', []))}

    " - f"

    Control Path

    {report['car'].get('controlPath', 'unknown')}

    " - f"

    Friction Family

    {report['capabilities'].get('frictionFamily', 'standard')}

    " - f"

    Nonlinear Torque Map

    {'Asymmetric left/right siglin' if report['capabilities'].get('nonlinearTorqueMap', {}).get('asymmetric') else ('Symmetric siglin' if report['capabilities'].get('nonlinearTorqueMap') else 'Not detected')}

    " + f"

    Routes

    {', '.join(report.get('routeNames', []))}

    " + f"

    Control Path

    {report['car'].get('controlPath', 'unknown')}

    " + f"

    Friction Family

    {report['capabilities'].get('frictionFamily', 'standard')}

    " + f"

    Nonlinear Torque Map

    {'Asymmetric left/right siglin' if report['capabilities'].get('nonlinearTorqueMap', {}).get('asymmetric') else ('Symmetric siglin' if report['capabilities'].get('nonlinearTorqueMap') else 'Not detected')}

    " f"{''.join(path_html)}" f"{start_here_html}" f"

    Active Findings: {primary_path.get('title', 'Recommendations')}

    " @@ -1809,22 +1880,22 @@ def _render_report_html(report: dict[str, Any]) -> str: def analyze_routes(route_names: list[str], footage_paths: list[str], feedback: dict[str, Any] | None = None, report_id: str | None = None) -> dict[str, Any]: - ensure_ftm_workspace() + ensure_flm_workspace() params = Params(return_defaults=True) - report_id = report_id or f"ftm-{int(time.time())}" + report_id = report_id or f"flm-{int(time.time())}" feedback = feedback or {} sources, warnings = resolve_route_sources(route_names, footage_paths) if not sources: raise RuntimeError("No local routes with qlogs or rlogs were found for the selected routes.") - all_samples: list[FTMSample] = [] + all_samples: list[FLMSample] = [] car_params = None init_data: dict[str, str] = {} used_qlog = False processed_segments = 0 for idx, source in enumerate(sources, start=1): - _write_ftm_status({ + _write_flm_status({ "pid": os.getpid(), "startedAt": time.time(), "running": True, @@ -1849,7 +1920,7 @@ def analyze_routes(route_names: list[str], footage_paths: list[str], feedback: d torque_control = car_params.lateralTuning.which() == "torque" hyundai_canfd = bool(getattr(car_params, "flags", 0) & HyundaiFlags.CANFD) - capabilities = get_ftm_capabilities( + capabilities = get_flm_capabilities( car_params.carFingerprint, brand=str(getattr(car_params, "brand", "") or ""), hyundai_canfd=hyundai_canfd, @@ -1886,11 +1957,11 @@ def analyze_routes(route_names: list[str], footage_paths: list[str], feedback: d "bucket": "angle_control_diagnostic", "evidence": summaries[0]["evidence"], "currentVsSuggested": None, - "observedBehavior": "This route is using an angle-control path, so the torque-specific FTM trial system stays in diagnostic mode.", + "observedBehavior": "This route is using an angle-control path, so the torque-specific FLM trial system stays in diagnostic mode.", "likelyInterpretation": "You can still inspect lane behavior here, but torque-controller trial profiles do not apply.", - "primaryAdjustment": "Do not apply an FTM torque profile to this car.", + "primaryAdjustment": "Do not apply an FLM torque profile to this car.", "whatNotToTouchYet": "Do not write torque-controller override blobs for an angle-control path.", - "ifThatWasWrong": "If the car later moves to torque control, re-run FTM on a fresh route.", + "ifThatWasWrong": "If the car later moves to torque control, re-run FLM on a fresh route.", "plotSvg": "", "plotData": {}, }] @@ -1947,14 +2018,14 @@ def analyze_routes(route_names: list[str], footage_paths: list[str], feedback: d "addTheseParametersAndStartHere": _add_parameters_start_here(capabilities, suggestions, path_decision["primaryPathKey"]), } - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() html = _render_report_html(report) report["htmlPath"] = str(paths["reports"] / f"{report_id}.html") report["jsonPath"] = str(paths["reports"] / f"{report_id}.json") (paths["reports"] / f"{report_id}.html").write_text(html, encoding="utf-8") _write_json(paths["reports"] / f"{report_id}.json", report) _write_json(paths["profiles"] / f"{report_id}.json", profiles) - _write_ftm_status({ + _write_flm_status({ "pid": os.getpid(), "startedAt": time.time(), "running": False, @@ -1968,7 +2039,7 @@ def analyze_routes(route_names: list[str], footage_paths: list[str], feedback: d def load_report(report_id: str) -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() report_path = paths["reports"] / f"{report_id}.json" report = _read_json(report_path, {}) if not isinstance(report, dict) or not report: @@ -1979,12 +2050,12 @@ def load_report(report_id: str) -> dict[str, Any]: def select_report_path(report_id: str, path_key: str) -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() report = load_report(report_id) report_paths = [path for path in report.get("paths", []) if isinstance(path, dict)] selected_path = next((path for path in report_paths if path.get("key") == path_key), None) if selected_path is None: - raise ValueError(f"Unknown FTM path: {path_key}") + raise ValueError(f"Unknown FLM path: {path_key}") report["selectedPathKey"] = path_key report["pathSelectionSource"] = "manual" @@ -2020,23 +2091,23 @@ def _active_trial_display_state(paths: dict[str, Path], snapshot: Any) -> dict[s return snapshot generic_params = dict(profile.get("genericParams", {})) - ftm_overrides = normalize_ftm_overrides(profile.get("ftmOverrides", {})) + flm_overrides = normalize_flm_overrides(profile.get("flmOverrides", {})) return { **snapshot, - "profileLabel": str(profile.get("label", "FTM") or "FTM"), + "profileLabel": str(profile.get("label", "FLM") or "FLM"), "pathKey": str(profile.get("pathKey", "") or ""), "pathLabel": str(profile.get("pathLabel", "") or ""), "appliedGenericParams": { key: value for key, value in generic_params.items() - if key in FTM_ADVANCED_LATERAL_PARAM_KEYS + if key in FLM_ADVANCED_LATERAL_PARAM_KEYS }, - "appliedFrictionThresholds": ftm_overrides.get("baseFrictionThresholds", {}), - "appliedVehicleKnobs": ftm_overrides.get("vehicleKnobs", {}), + "appliedFrictionThresholds": flm_overrides.get("baseFrictionThresholds", {}), + "appliedVehicleKnobs": flm_overrides.get("vehicleKnobs", {}), } def list_workspace() -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() reports = [] for path in sorted(paths["reports"].glob("*.json"), reverse=True): payload = _read_json(path, {}) @@ -2051,9 +2122,9 @@ def list_workspace() -> dict[str, Any]: }) feedback_files = sorted(paths["feedback"].glob("*.json"), reverse=True) params = Params(return_defaults=True) - current_profile_id = params.get("FTMActiveProfileId", encoding="utf-8") or "" + current_profile_id = params.get("FLMActiveProfileId", encoding="utf-8") or "" raw_active_snapshot = _read_json(paths["snapshots"] / "active.json", {}) - if params.get_bool("FTMTrialApplied"): + if params.get_bool("FLMTrialApplied"): active_payload = raw_active_snapshot if isinstance(raw_active_snapshot, dict) else {} baseline_snapshot = _find_revert_snapshot(paths, raw_active_snapshot, current_profile_id, params) if baseline_snapshot is not None: @@ -2076,18 +2147,18 @@ def list_workspace() -> dict[str, Any]: "reports": reports[:20], "feedbackCount": len(feedback_files), "activeTrial": active_snapshot, - "status": read_ftm_status(), + "status": read_flm_status(), } def delete_report(report_id: str) -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() params = Params(return_defaults=True) - if params.get_bool("FTMTrialApplied"): - raise RuntimeError("Revert or keep the active FTM trial before deleting tuning reports.") + if params.get_bool("FLMTrialApplied"): + raise RuntimeError("Revert or keep the active FLM trial before deleting tuning reports.") active_snapshot = _read_json(paths["snapshots"] / "active.json", {}) if isinstance(active_snapshot, dict) and active_snapshot.get("reportId") == report_id: - raise RuntimeError("Revert the active FTM trial before deleting its source report.") + raise RuntimeError("Revert the active FLM trial before deleting its source report.") removed = [] direct_paths = [ @@ -2108,9 +2179,9 @@ def delete_report(report_id: str) -> dict[str, Any]: if not removed: raise FileNotFoundError(report_id) - status = read_ftm_status() + status = read_flm_status() if not status.get("running") and status.get("reportId") == report_id: - _clear_ftm_status() + _clear_flm_status() return { "message": f"Deleted tuning report {report_id}.", @@ -2120,15 +2191,15 @@ def delete_report(report_id: str) -> dict[str, Any]: def clear_workspace() -> dict[str, Any]: - paths = ensure_ftm_workspace() - status = read_ftm_status() + paths = ensure_flm_workspace() + status = read_flm_status() if status.get("running"): - raise RuntimeError("Stop the active FTM analysis before clearing the workspace.") + raise RuntimeError("Stop the active FLM analysis before clearing the workspace.") params = Params(return_defaults=True) active_snapshot = _read_json(paths["snapshots"] / "active.json", {}) - if params.get_bool("FTMTrialApplied") or (isinstance(active_snapshot, dict) and active_snapshot.get("params")): - raise RuntimeError("Revert or keep the active FTM trial before clearing the workspace.") + if params.get_bool("FLMTrialApplied") or (isinstance(active_snapshot, dict) and active_snapshot.get("params")): + raise RuntimeError("Revert or keep the active FLM trial before clearing the workspace.") removed = [] for key in ("reports", "profiles", "feedback", "snapshots"): @@ -2138,7 +2209,7 @@ def clear_workspace() -> dict[str, Any]: removed.append(str(path)) _clear_persistent_trial_baseline(params) - _clear_ftm_status() + _clear_flm_status() return { "message": "Cleared saved tuning reports, feedback, profiles, and snapshots.", @@ -2155,14 +2226,14 @@ def _snapshot_current_trial_state(params: Params) -> dict[str, Any]: elif kind == "float": snapshot[key] = params.get_float(key, return_default=True) elif kind == "json": - snapshot[key] = normalize_ftm_overrides(params.get(key, encoding="utf-8") or "{}") + snapshot[key] = normalize_flm_overrides(params.get(key, encoding="utf-8") or "{}") else: snapshot[key] = params.get(key, encoding="utf-8") or "" return snapshot def _read_persistent_trial_baseline(params: Params) -> dict[str, Any] | None: - raw = params.get(FTM_TRIAL_BASELINE_PARAM, encoding="utf-8") or {} + raw = params.get(FLM_TRIAL_BASELINE_PARAM, encoding="utf-8") or {} if isinstance(raw, str): try: raw = json.loads(raw) @@ -2170,18 +2241,18 @@ def _read_persistent_trial_baseline(params: Params) -> dict[str, Any] | None: return None if not isinstance(raw, dict) or not isinstance(raw.get("params"), dict): return None - if raw["params"].get("FTMTrialApplied", False): + if raw["params"].get("FLMTrialApplied", False): return None return raw def _persist_trial_baseline(params: Params, snapshot: dict[str, Any]) -> None: - if isinstance(snapshot.get("params"), dict) and not snapshot["params"].get("FTMTrialApplied", False): - params.put(FTM_TRIAL_BASELINE_PARAM, snapshot) + if isinstance(snapshot.get("params"), dict) and not snapshot["params"].get("FLMTrialApplied", False): + params.put(FLM_TRIAL_BASELINE_PARAM, snapshot) def _clear_persistent_trial_baseline(params: Params) -> None: - params.remove(FTM_TRIAL_BASELINE_PARAM) + params.remove(FLM_TRIAL_BASELINE_PARAM) def _profile_report_id(profile_id: str) -> str: @@ -2211,9 +2282,9 @@ def _recover_report_baseline(paths: dict[str, Path], profile_id: str, } if not baseline_params: return None - if not baseline_params.get("FTMTrialApplied", False): - baseline_params["FTMTrialApplied"] = False - baseline_params.setdefault("FTMActiveProfileId", "") + if not baseline_params.get("FLMTrialApplied", False): + baseline_params["FLMTrialApplied"] = False + baseline_params.setdefault("FLMActiveProfileId", "") return { "reportId": report_id, "profileId": profile_id, @@ -2222,7 +2293,7 @@ def _recover_report_baseline(paths: dict[str, Path], profile_id: str, "recoverySource": "report", } - previous_profile_id = str(baseline_params.get("FTMActiveProfileId", "") or "") + previous_profile_id = str(baseline_params.get("FLMActiveProfileId", "") or "") if previous_profile_id and previous_profile_id != profile_id: return _recover_report_baseline(paths, previous_profile_id, visited_profiles) return None @@ -2236,14 +2307,14 @@ def _apply_param_bundle(params: Params, bundle: dict[str, Any]) -> None: elif kind == "float": params.put_float(key, float(value)) elif kind == "json": - params.put(key, normalize_ftm_overrides(value)) + params.put(key, normalize_flm_overrides(value)) elif kind == "string": params.put(key, str(value or "")) -def _merge_ftm_override_state(base: dict[str, Any], delta: dict[str, Any]) -> dict[str, Any]: - base = normalize_ftm_overrides(base) - delta = normalize_ftm_overrides(delta) +def _merge_flm_override_state(base: dict[str, Any], delta: dict[str, Any]) -> dict[str, Any]: + base = normalize_flm_overrides(base) + delta = normalize_flm_overrides(delta) merged = { "schemaVersion": 1, "baseFrictionThresholds": { @@ -2255,13 +2326,13 @@ def _merge_ftm_override_state(base: dict[str, Any], delta: dict[str, Any]) -> di **delta.get("vehicleKnobs", {}), }, } - return normalize_ftm_overrides(merged) + return normalize_flm_overrides(merged) def _find_revert_snapshot(paths: dict[str, Path], active_snapshot: dict[str, Any], current_profile_id: str = "", params: Params | None = None) -> dict[str, Any] | None: if isinstance(active_snapshot, dict) and isinstance(active_snapshot.get("params"), dict): - if not active_snapshot["params"].get("FTMTrialApplied", False): + if not active_snapshot["params"].get("FLMTrialApplied", False): return active_snapshot if params is not None: @@ -2276,7 +2347,7 @@ def _find_revert_snapshot(paths: dict[str, Path], active_snapshot: dict[str, Any continue candidate = _read_json(path, {}) candidate_params = candidate.get("params", {}) if isinstance(candidate, dict) else {} - if not isinstance(candidate_params, dict) or candidate_params.get("FTMTrialApplied", False): + if not isinstance(candidate_params, dict) or candidate_params.get("FLMTrialApplied", False): continue captured_at = float(candidate.get("capturedAt", 0.0) or 0.0) if captured_at > cutoff: @@ -2292,7 +2363,7 @@ def _find_revert_snapshot(paths: dict[str, Path], active_snapshot: dict[str, Any def apply_trial_profile(report_id: str, profile_id: str) -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() params = Params(return_defaults=True) profiles = _read_json(paths["profiles"] / f"{report_id}.json", []) if not isinstance(profiles, list): @@ -2302,21 +2373,21 @@ def apply_trial_profile(report_id: str, profile_id: str) -> dict[str, Any]: raise FileNotFoundError(profile_id) generic_params = dict(profile.get("genericParams", {})) - ftm_overrides = normalize_ftm_overrides(profile.get("ftmOverrides", {})) + flm_overrides = normalize_flm_overrides(profile.get("flmOverrides", {})) current_state = _snapshot_current_trial_state(params) raw_active_snapshot = _read_json(paths["snapshots"] / "active.json", {}) previous_display_state = _active_trial_display_state(paths, raw_active_snapshot) or {} - trial_already_active = bool(current_state.get("FTMTrialApplied", False)) + trial_already_active = bool(current_state.get("FLMTrialApplied", False)) if trial_already_active: baseline_snapshot = _find_revert_snapshot( paths, raw_active_snapshot, - str(current_state.get("FTMActiveProfileId", "") or ""), + str(current_state.get("FLMActiveProfileId", "") or ""), params, ) if baseline_snapshot is None: - raise RuntimeError("The active FTM trial has no recoverable rollback baseline. Keep the current tune as the new baseline before applying another profile.") + raise RuntimeError("The active FLM trial has no recoverable rollback baseline. Keep the current tune as the new baseline before applying another profile.") baseline_params = baseline_snapshot["params"] session_started_at = float(baseline_snapshot.get("sessionStartedAt", baseline_snapshot.get("capturedAt", time.time())) or time.time()) else: @@ -2324,12 +2395,12 @@ def apply_trial_profile(report_id: str, profile_id: str) -> dict[str, Any]: session_started_at = time.time() previous_generic_params = dict(previous_display_state.get("appliedGenericParams", {})) - for key in FTM_ADVANCED_LATERAL_PARAM_KEYS: + for key in FLM_ADVANCED_LATERAL_PARAM_KEYS: if key in current_state and current_state.get(key) != baseline_params.get(key): previous_generic_params[key] = current_state[key] - baseline_overrides = normalize_ftm_overrides(baseline_params.get("FTMActiveOverrides", {})) - current_overrides = normalize_ftm_overrides(current_state.get("FTMActiveOverrides", {})) + baseline_overrides = normalize_flm_overrides(baseline_params.get("FLMActiveOverrides", {})) + current_overrides = normalize_flm_overrides(current_state.get("FLMActiveOverrides", {})) previous_friction_thresholds = dict(previous_display_state.get("appliedFrictionThresholds", {})) for family, payload in current_overrides.get("baseFrictionThresholds", {}).items(): if payload != baseline_overrides.get("baseFrictionThresholds", {}).get(family): @@ -2343,22 +2414,22 @@ def apply_trial_profile(report_id: str, profile_id: str) -> dict[str, Any]: **previous_generic_params, **{ key: value for key, value in generic_params.items() - if key in FTM_ADVANCED_LATERAL_PARAM_KEYS + if key in FLM_ADVANCED_LATERAL_PARAM_KEYS }, } applied_friction_thresholds = { **previous_friction_thresholds, - **ftm_overrides.get("baseFrictionThresholds", {}), + **flm_overrides.get("baseFrictionThresholds", {}), } applied_vehicle_knobs = { **previous_vehicle_knobs, - **ftm_overrides.get("vehicleKnobs", {}), + **flm_overrides.get("vehicleKnobs", {}), } now = time.time() snapshot = { "reportId": report_id, "profileId": profile_id, - "profileLabel": str(profile.get("label", "FTM") or "FTM"), + "profileLabel": str(profile.get("label", "FLM") or "FLM"), "pathKey": str(profile.get("pathKey", "") or ""), "pathLabel": str(profile.get("pathLabel", "") or ""), "capturedAt": session_started_at, @@ -2375,25 +2446,25 @@ def apply_trial_profile(report_id: str, profile_id: str) -> dict[str, Any]: _persist_trial_baseline(params, snapshot) bundle = generic_params - bundle["FTMActiveProfileId"] = profile_id - bundle["FTMActiveOverrides"] = _merge_ftm_override_state( - current_state.get("FTMActiveOverrides", {}), - ftm_overrides, + bundle["FLMActiveProfileId"] = profile_id + bundle["FLMActiveOverrides"] = _merge_flm_override_state( + current_state.get("FLMActiveOverrides", {}), + flm_overrides, ) - bundle["FTMTrialApplied"] = True + bundle["FLMTrialApplied"] = True _apply_param_bundle(params, bundle) return { - "message": f"Applied {profile.get('label', 'FTM')} profile.", + "message": f"Applied {profile.get('label', 'FLM')} profile.", "profile": profile, } def revert_trial_profile() -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() snapshot_path = paths["snapshots"] / "active.json" snapshot = _read_json(snapshot_path, {}) params = Params(return_defaults=True) - current_profile_id = params.get("FTMActiveProfileId", encoding="utf-8") or "" + current_profile_id = params.get("FLMActiveProfileId", encoding="utf-8") or "" revert_snapshot = _find_revert_snapshot(paths, snapshot if isinstance(snapshot, dict) else {}, current_profile_id, params) if revert_snapshot is None: raise FileNotFoundError("active trial snapshot") @@ -2404,7 +2475,7 @@ def revert_trial_profile() -> dict[str, Any]: except FileNotFoundError: pass return { - "message": "Reverted the complete FTM trial session to its original baseline.", + "message": "Reverted the complete FLM trial session to its original baseline.", "snapshot": { **(snapshot if isinstance(snapshot, dict) else {}), "params": revert_snapshot["params"], @@ -2414,26 +2485,26 @@ def revert_trial_profile() -> dict[str, Any]: def accept_trial_as_baseline() -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() params = Params(return_defaults=True) active_snapshot = _read_json(paths["snapshots"] / "active.json", {}) - if not params.get_bool("FTMTrialApplied") and not (isinstance(active_snapshot, dict) and active_snapshot): + if not params.get_bool("FLMTrialApplied") and not (isinstance(active_snapshot, dict) and active_snapshot): raise FileNotFoundError("active trial") - params.put_bool("FTMTrialApplied", False) - params.put("FTMActiveProfileId", "") + params.put_bool("FLMTrialApplied", False) + params.put("FLMActiveProfileId", "") _clear_persistent_trial_baseline(params) for path in paths["snapshots"].glob("*.json"): path.unlink() return { - "message": "Kept the current tuning values and made them the new FTM baseline.", + "message": "Kept the current tuning values and made them the new FLM baseline.", "workspace": list_workspace(), } def record_feedback(report_id: str, feedback: dict[str, Any]) -> dict[str, Any]: - paths = ensure_ftm_workspace() + paths = ensure_flm_workspace() normalized = { "acceptedDimensions": [str(item) for item in feedback.get("acceptedDimensions", [])], "ignoredDimensions": [str(item) for item in feedback.get("ignoredDimensions", [])], @@ -2469,7 +2540,7 @@ def record_feedback(report_id: str, feedback: dict[str, Any]) -> dict[str, Any]: _write_json(paths["reports"] / f"{report_id}.json", report) _write_json(paths["profiles"] / f"{report_id}.json", report["profiles"]) return { - "message": "Saved FTM feedback.", + "message": "Saved FLM feedback.", "feedback": normalized, "profiles": report["profiles"], "report": load_report(report_id), @@ -2480,8 +2551,8 @@ def run_worker(payload_json: str) -> None: payload = json.loads(payload_json) routes = [str(route) for route in payload.get("routes", [])] footage_paths = [str(path) for path in payload.get("footagePaths", [])] - ensure_ftm_workspace() - _write_ftm_status({ + ensure_flm_workspace() + _write_flm_status({ "pid": os.getpid(), "startedAt": time.time(), "running": True, @@ -2492,7 +2563,7 @@ def run_worker(payload_json: str) -> None: }) try: report = analyze_routes(routes, footage_paths) - _write_ftm_status({ + _write_flm_status({ "pid": os.getpid(), "startedAt": time.time(), "running": False, @@ -2503,7 +2574,7 @@ def run_worker(payload_json: str) -> None: "reportId": report["reportId"], }) except Exception as error: - _write_ftm_status({ + _write_flm_status({ "pid": os.getpid(), "startedAt": time.time(), "running": False, @@ -2526,7 +2597,7 @@ def main() -> None: report = analyze_routes(routes, footage_paths) print(json.dumps({"reportId": report["reportId"], "htmlPath": report["htmlPath"], "jsonPath": report["jsonPath"]}, indent=2)) return - print("Usage: ftm_workspace.py analyze [...]") + print("Usage: flm_workspace.py analyze [...]") if __name__ == "__main__": diff --git a/starpilot/system/the_galaxy/templates/index.html b/starpilot/system/the_galaxy/templates/index.html index 45fab7ddb..811de2c8e 100644 --- a/starpilot/system/the_galaxy/templates/index.html +++ b/starpilot/system/the_galaxy/templates/index.html @@ -34,18 +34,18 @@ - + - +