LCOV - code coverage report
Current view: top level - src - Config.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 96.7 % 1291 1248
Test Date: 2026-03-03 13:56:25 Functions: 100.0 % 15 15
Branches: 58.3 % 2862 1668

             Branch data     Line data    Source code
       1                 :             : #include "Config.h"
       2                 :             : #include "Version.h"
       3                 :             : #include <fstream>
       4                 :             : #include <sstream>
       5                 :             : #include <iostream>
       6                 :             : #include <algorithm>
       7                 :             : #include <mutex>
       8                 :             : 
       9                 :             : extern std::recursive_mutex g_engine_mutex;
      10                 :             : 
      11                 :             : bool Config::m_always_on_top = true;
      12                 :             : std::string Config::m_last_device_guid = "";
      13                 :             : std::string Config::m_last_preset_name = "Default";
      14                 :             : std::string Config::m_config_path = "config.ini";
      15                 :             : bool Config::m_auto_start_logging = false;
      16                 :             : std::string Config::m_log_path = "logs/";
      17                 :             : 
      18                 :             : // Window Geometry Defaults (v0.5.5)
      19                 :             : int Config::win_pos_x = 100;
      20                 :             : int Config::win_pos_y = 100;
      21                 :             : int Config::win_w_small = 500;   // Narrow (Config Only)
      22                 :             : int Config::win_h_small = 800;
      23                 :             : int Config::win_w_large = 1400;  // Wide (Config + Graphs)
      24                 :             : int Config::win_h_large = 800;
      25                 :             : bool Config::show_graphs = false;
      26                 :             : 
      27                 :             : std::map<std::string, double> Config::m_saved_static_loads;
      28                 :             : std::recursive_mutex Config::m_static_loads_mutex;
      29                 :             : std::atomic<bool> Config::m_needs_save{ false };
      30                 :             : 
      31                 :             : std::vector<Preset> Config::presets;
      32                 :             : 
      33                 :             : // Helper to compare semantic version strings (e.g., "0.7.66" <= "0.7.66")
      34                 :           7 : static bool IsVersionLessEqual(const std::string& v1, const std::string& v2) {
      35         [ +  + ]:           7 :     if (v1.empty()) return true; // Empty version treated as legacy
      36         [ -  + ]:           3 :     if (v2.empty()) return false;
      37                 :             : 
      38   [ +  -  +  - ]:           3 :     std::stringstream ss1(v1), ss2(v2);
      39                 :           3 :     std::string segment1, segment2;
      40                 :             : 
      41                 :             :     while (true) {
      42   [ +  -  +  - ]:          10 :         bool has1 = (bool)std::getline(ss1, segment1, '.');
      43   [ +  -  +  - ]:          10 :         bool has2 = (bool)std::getline(ss2, segment2, '.');
      44                 :             : 
      45   [ +  +  +  - ]:          10 :         if (!has1 && !has2) return true; // Exactly equal
      46                 :             : 
      47                 :           8 :         int val1 = 0;
      48   [ +  -  +  -  :           8 :         try { if (has1) val1 = std::stoi(segment1); } catch (...) {}
                   -  - ]
      49                 :           8 :         int val2 = 0;
      50   [ +  -  +  -  :           8 :         try { if (has2) val2 = std::stoi(segment2); } catch (...) {}
                   -  - ]
      51                 :             : 
      52         [ +  + ]:           8 :         if (val1 < val2) return true;
      53         [ -  + ]:           7 :         if (val1 > val2) return false;
      54                 :             : 
      55   [ +  -  +  - ]:           7 :         if (!has1 || !has2) break;
      56                 :           7 :     }
      57                 :           0 :     return true;
      58                 :           3 : }
      59                 :             : 
      60                 :        1447 : void Config::ParsePresetLine(const std::string& line, Preset& current_preset, std::string& current_preset_version, bool& needs_save, bool& legacy_torque_hack, float& legacy_torque_val) {
      61         [ +  - ]:        1447 :     std::istringstream is_line(line);
      62                 :        1447 :     std::string key;
      63   [ +  -  +  -  :        1447 :     if (std::getline(is_line, key, '=')) {
                   +  - ]
      64                 :        1447 :         std::string value;
      65   [ +  -  +  -  :        1447 :         if (std::getline(is_line, value)) {
                   +  - ]
      66                 :             :             try {
      67                 :             :                 // Map keys to struct members
      68   [ +  -  +  +  :        1447 :                 if (key == "app_version") current_preset_version = value;
                   +  - ]
      69   [ +  -  +  +  :        1429 :                 else if (key == "gain") current_preset.gain = std::stof(value);
                   +  - ]
      70   [ +  -  +  + ]:        1409 :                 else if (key == "understeer") {
      71         [ +  - ]:          17 :                     float val = std::stof(value);
      72         [ -  + ]:          17 :                     if (val > 2.0f) {
      73                 :           0 :                         float old_val = val;
      74                 :           0 :                         val = val / 100.0f; // Migrating 0-200 range to 0-2
      75   [ #  #  #  # ]:           0 :                         std::cout << "[Preset] Migrated legacy understeer: " << old_val
      76   [ #  #  #  #  :           0 :                                     << " -> " << val << std::endl;
                   #  # ]
      77                 :           0 :                         needs_save = true;
      78                 :             :                     }
      79                 :          17 :                     current_preset.understeer = (std::min)(2.0f, (std::max)(0.0f, val));
      80                 :             :                 }
      81   [ +  -  +  +  :        1392 :                 else if (key == "sop") current_preset.sop = (std::min)(2.0f, std::stof(value));
                   +  - ]
      82   [ +  -  +  +  :        1375 :                 else if (key == "sop_scale") current_preset.sop_scale = std::stof(value);
                   +  - ]
      83   [ +  -  +  +  :        1358 :                 else if (key == "sop_smoothing_factor") current_preset.sop_smoothing = std::stof(value);
                   +  - ]
      84   [ +  -  +  +  :        1342 :                 else if (key == "min_force") current_preset.min_force = std::stof(value);
                   +  - ]
      85   [ +  -  +  +  :        1325 :                 else if (key == "oversteer_boost") current_preset.oversteer_boost = std::stof(value);
                   +  - ]
      86   [ +  -  +  +  :        1308 :                 else if (key == "dynamic_weight_gain") current_preset.dynamic_weight_gain = std::stof(value);
                   +  - ]
      87   [ +  -  +  +  :        1292 :                 else if (key == "dynamic_weight_smoothing") current_preset.dynamic_weight_smoothing = std::stof(value);
                   +  - ]
      88   [ +  -  +  +  :        1276 :                 else if (key == "grip_smoothing_steady") current_preset.grip_smoothing_steady = std::stof(value);
                   +  - ]
      89   [ +  -  +  +  :        1260 :                 else if (key == "grip_smoothing_fast") current_preset.grip_smoothing_fast = std::stof(value);
                   +  - ]
      90   [ +  -  +  +  :        1244 :                 else if (key == "grip_smoothing_sensitivity") current_preset.grip_smoothing_sensitivity = std::stof(value);
                   +  - ]
      91   [ +  -  +  +  :        1228 :                 else if (key == "lockup_enabled") current_preset.lockup_enabled = std::stoi(value);
                   +  - ]
      92   [ +  -  +  +  :        1211 :                 else if (key == "lockup_gain") current_preset.lockup_gain = (std::min)(3.0f, std::stof(value));
                   +  - ]
      93   [ +  -  +  +  :        1193 :                 else if (key == "lockup_start_pct") current_preset.lockup_start_pct = std::stof(value);
                   +  - ]
      94   [ +  -  +  +  :        1176 :                 else if (key == "lockup_full_pct") current_preset.lockup_full_pct = std::stof(value);
                   +  - ]
      95   [ +  -  +  +  :        1159 :                 else if (key == "lockup_rear_boost") current_preset.lockup_rear_boost = std::stof(value);
                   +  - ]
      96   [ +  -  +  +  :        1142 :                 else if (key == "lockup_gamma") current_preset.lockup_gamma = std::stof(value);
                   +  - ]
      97   [ +  -  +  +  :        1125 :                 else if (key == "lockup_prediction_sens") current_preset.lockup_prediction_sens = std::stof(value);
                   +  - ]
      98   [ +  -  +  +  :        1108 :                 else if (key == "lockup_bump_reject") current_preset.lockup_bump_reject = std::stof(value);
                   +  - ]
      99   [ +  -  +  +  :        1091 :                 else if (key == "brake_load_cap") current_preset.brake_load_cap = (std::min)(10.0f, std::stof(value));
                   +  - ]
     100   [ +  -  +  +  :        1073 :                 else if (key == "texture_load_cap" || key == "max_load_factor") current_preset.texture_load_cap = std::stof(value); // Includes Legacy Backward Compatibility
          +  -  -  +  +  
                +  +  - ]
     101   [ +  -  +  +  :        1056 :                 else if (key == "abs_pulse_enabled") current_preset.abs_pulse_enabled = std::stoi(value);
                   +  - ]
     102   [ +  -  +  +  :        1039 :                 else if (key == "abs_gain") current_preset.abs_gain = std::stof(value);
                   +  - ]
     103   [ +  -  +  +  :        1022 :                 else if (key == "spin_enabled") current_preset.spin_enabled = std::stoi(value);
                   +  - ]
     104   [ +  -  +  +  :        1005 :                 else if (key == "spin_gain") current_preset.spin_gain = (std::min)(2.0f, std::stof(value));
                   +  - ]
     105   [ +  -  +  +  :         988 :                 else if (key == "slide_enabled") current_preset.slide_enabled = std::stoi(value);
                   +  - ]
     106   [ +  -  +  +  :         971 :                 else if (key == "slide_gain") current_preset.slide_gain = (std::min)(2.0f, std::stof(value));
                   +  - ]
     107   [ +  -  +  +  :         954 :                 else if (key == "slide_freq") current_preset.slide_freq = std::stof(value);
                   +  - ]
     108   [ +  -  +  +  :         937 :                 else if (key == "road_enabled") current_preset.road_enabled = std::stoi(value);
                   +  - ]
     109   [ +  -  +  +  :         920 :                 else if (key == "road_gain") current_preset.road_gain = (std::min)(2.0f, std::stof(value));
                   +  - ]
     110   [ +  -  +  +  :         903 :                 else if (key == "vibration_gain" || key == "tactile_gain") current_preset.vibration_gain = (std::min)(2.0f, std::stof(value));
          +  -  -  +  +  
                +  +  - ]
     111   [ +  -  +  +  :         887 :                 else if (key == "dynamic_normalization_enabled") current_preset.dynamic_normalization_enabled = (value == "1" || value == "true");
          +  -  +  -  +  
                -  -  + ]
     112   [ +  -  +  +  :         871 :                 else if (key == "auto_load_normalization_enabled") current_preset.auto_load_normalization_enabled = (value == "1" || value == "true");
          +  -  +  -  +  
                -  -  + ]
     113   [ +  -  +  +  :         855 :                 else if (key == "soft_lock_enabled") current_preset.soft_lock_enabled = std::stoi(value);
                   +  - ]
     114   [ +  -  +  +  :         838 :                 else if (key == "soft_lock_stiffness") current_preset.soft_lock_stiffness = std::stof(value);
                   +  - ]
     115   [ +  -  +  +  :         821 :                 else if (key == "soft_lock_damping") current_preset.soft_lock_damping = std::stof(value);
                   +  - ]
     116   [ +  -  +  +  :         804 :                 else if (key == "wheelbase_max_nm") current_preset.wheelbase_max_nm = std::stof(value);
                   +  - ]
     117   [ +  -  +  +  :         787 :                 else if (key == "target_rim_nm") current_preset.target_rim_nm = std::stof(value);
                   +  - ]
     118   [ +  -  +  + ]:         770 :                 else if (key == "max_torque_ref") {
     119                 :             :                     // MIGRATION LOGIC (Issue #153 & #211)
     120         [ +  - ]:           1 :                     float old_val = std::stof(value);
     121         [ +  - ]:           1 :                     if (old_val > 40.0f) {
     122                 :             :                         // Likely the 100Nm clipping hack. Reset to safe DD defaults.
     123                 :           1 :                         current_preset.wheelbase_max_nm = 15.0f;
     124                 :           1 :                         current_preset.target_rim_nm = 10.0f;
     125                 :           1 :                         legacy_torque_hack = true;
     126                 :           1 :                         legacy_torque_val = old_val;
     127                 :             :                     } else {
     128                 :             :                         // User actually tuned it to their wheelbase (e.g. 20Nm or 4Nm)
     129                 :           0 :                         current_preset.wheelbase_max_nm = old_val;
     130                 :           0 :                         current_preset.target_rim_nm = old_val;
     131                 :             :                     }
     132                 :           1 :                     needs_save = true;
     133                 :             :                 }
     134   [ +  -  +  +  :         769 :                 else if (key == "abs_freq") current_preset.abs_freq = std::stof(value);
                   +  - ]
     135   [ +  -  +  +  :         752 :                 else if (key == "lockup_freq_scale") current_preset.lockup_freq_scale = std::stof(value);
                   +  - ]
     136   [ +  -  +  +  :         735 :                 else if (key == "spin_freq_scale") current_preset.spin_freq_scale = std::stof(value);
                   +  - ]
     137   [ +  -  +  +  :         718 :                 else if (key == "bottoming_method") current_preset.bottoming_method = std::stoi(value);
                   +  - ]
     138   [ +  -  +  +  :         701 :                 else if (key == "scrub_drag_gain") current_preset.scrub_drag_gain = (std::min)(1.0f, std::stof(value));
                   +  - ]
     139   [ +  -  +  +  :         684 :                 else if (key == "rear_align_effect") current_preset.rear_align_effect = (std::min)(2.0f, std::stof(value));
                   +  - ]
     140   [ +  -  +  +  :         667 :                 else if (key == "sop_yaw_gain") current_preset.sop_yaw_gain = (std::min)(2.0f, std::stof(value));
                   +  - ]
     141   [ +  -  +  +  :         650 :                 else if (key == "steering_shaft_gain") current_preset.steering_shaft_gain = std::stof(value);
                   +  - ]
     142   [ +  -  +  +  :         633 :                 else if (key == "ingame_ffb_gain") current_preset.ingame_ffb_gain = std::stof(value);
                   +  - ]
     143   [ +  -  +  +  :         616 :                 else if (key == "slip_angle_smoothing") current_preset.slip_smoothing = std::stof(value);
                   +  - ]
     144   [ +  -  +  +  :         599 :                 else if (key == "torque_source") current_preset.torque_source = std::stoi(value);
                   +  - ]
     145   [ +  -  +  +  :         582 :                 else if (key == "torque_passthrough") current_preset.torque_passthrough = (value == "1" || value == "true");
          +  -  +  +  +  
                -  -  + ]
     146   [ +  -  +  +  :         565 :                 else if (key == "gyro_gain") current_preset.gyro_gain = (std::min)(1.0f, std::stof(value));
                   +  - ]
     147   [ +  -  +  +  :         548 :                 else if (key == "flatspot_suppression") current_preset.flatspot_suppression = std::stoi(value);
                   +  - ]
     148   [ +  -  +  +  :         531 :                 else if (key == "notch_q") current_preset.notch_q = std::stof(value);
                   +  - ]
     149   [ +  -  +  +  :         514 :                 else if (key == "flatspot_strength") current_preset.flatspot_strength = std::stof(value);
                   +  - ]
     150   [ +  -  +  +  :         497 :                 else if (key == "static_notch_enabled") current_preset.static_notch_enabled = std::stoi(value);
                   +  - ]
     151   [ +  -  +  +  :         480 :                 else if (key == "static_notch_freq") current_preset.static_notch_freq = std::stof(value);
                   +  - ]
     152   [ +  -  +  +  :         463 :                 else if (key == "static_notch_width") current_preset.static_notch_width = std::stof(value);
                   +  - ]
     153   [ +  -  +  +  :         446 :                 else if (key == "yaw_kick_threshold") current_preset.yaw_kick_threshold = std::stof(value);
                   +  - ]
     154   [ +  -  +  +  :         429 :                 else if (key == "optimal_slip_angle") current_preset.optimal_slip_angle = std::stof(value);
                   +  - ]
     155   [ +  -  +  +  :         412 :                 else if (key == "optimal_slip_ratio") current_preset.optimal_slip_ratio = std::stof(value);
                   +  - ]
     156   [ +  -  +  +  :         395 :                 else if (key == "slope_detection_enabled") current_preset.slope_detection_enabled = (value == "1");
                   +  - ]
     157   [ +  -  +  +  :         377 :                 else if (key == "slope_sg_window") current_preset.slope_sg_window = std::stoi(value);
                   +  - ]
     158   [ +  -  +  +  :         360 :                 else if (key == "slope_sensitivity") current_preset.slope_sensitivity = std::stof(value);
                   +  - ]
     159   [ +  -  +  +  :         343 :                 else if (key == "slope_negative_threshold" || key == "slope_min_threshold") current_preset.slope_min_threshold = std::stof(value);
          +  -  +  +  +  
                +  +  - ]
     160   [ +  -  +  +  :         324 :                 else if (key == "slope_smoothing_tau") current_preset.slope_smoothing_tau = std::stof(value);
                   +  - ]
     161   [ +  -  +  +  :         307 :                 else if (key == "slope_max_threshold") current_preset.slope_max_threshold = std::stof(value);
                   +  - ]
     162   [ +  -  +  +  :         290 :                 else if (key == "slope_alpha_threshold") current_preset.slope_alpha_threshold = std::stof(value);
                   +  - ]
     163   [ +  -  +  +  :         273 :                 else if (key == "slope_decay_rate") current_preset.slope_decay_rate = std::stof(value);
                   +  - ]
     164   [ +  -  +  +  :         256 :                 else if (key == "slope_confidence_enabled") current_preset.slope_confidence_enabled = (value == "1");
                   +  - ]
     165   [ +  -  +  +  :         239 :                 else if (key == "steering_shaft_smoothing") current_preset.steering_shaft_smoothing = std::stof(value);
                   +  - ]
     166   [ +  -  +  +  :         222 :                 else if (key == "gyro_smoothing_factor") current_preset.gyro_smoothing = std::stof(value);
                   +  - ]
     167   [ +  -  +  +  :         205 :                 else if (key == "yaw_accel_smoothing") current_preset.yaw_smoothing = std::stof(value);
                   +  - ]
     168   [ +  -  +  +  :         188 :                 else if (key == "chassis_inertia_smoothing") current_preset.chassis_smoothing = std::stof(value);
                   +  - ]
     169   [ +  -  +  +  :         171 :                 else if (key == "speed_gate_lower") current_preset.speed_gate_lower = std::stof(value); // NEW v0.6.25
                   +  - ]
     170   [ +  -  +  +  :         154 :                 else if (key == "speed_gate_upper") current_preset.speed_gate_upper = std::stof(value); // NEW v0.6.25
                   +  - ]
     171   [ +  -  +  +  :         137 :                 else if (key == "road_fallback_scale") current_preset.road_fallback_scale = std::stof(value); // NEW v0.6.25
                   +  - ]
     172   [ +  -  +  +  :         120 :                 else if (key == "understeer_affects_sop") current_preset.understeer_affects_sop = std::stoi(value); // NEW v0.6.25
                   +  - ]
     173   [ +  -  +  +  :         103 :                 else if (key == "slope_g_slew_limit") current_preset.slope_g_slew_limit = std::stof(value); // NEW v0.7.40
                   +  - ]
     174   [ +  -  +  +  :          86 :                 else if (key == "slope_use_torque") current_preset.slope_use_torque = (value == "1"); // NEW v0.7.40
                   +  - ]
     175   [ +  -  +  +  :          69 :                 else if (key == "slope_torque_sensitivity") current_preset.slope_torque_sensitivity = std::stof(value); // NEW v0.7.40
                   +  - ]
     176   [ +  -  +  +  :          52 :                 else if (key == "slope_confidence_max_rate") current_preset.slope_confidence_max_rate = std::stof(value); // NEW v0.7.42
                   +  - ]
     177   [ +  -  +  +  :          35 :                 else if (key == "rest_api_fallback_enabled") current_preset.rest_api_enabled = (value == "1" || value == "true"); // NEW v0.7.113
          +  -  +  -  +  
                -  -  + ]
     178   [ +  -  +  +  :          19 :                 else if (key == "rest_api_port") current_preset.rest_api_port = std::stoi(value); // NEW v0.7.113
                   +  - ]
     179   [ -  -  -  -  :           0 :             } catch (...) { std::cerr << "[Config] ParsePresetLine Error." << std::endl; }
                   -  - ]
     180                 :             :         }
     181                 :        1447 :     }
     182                 :        1447 : }
     183                 :             : 
     184                 :          26 : void Config::LoadPresets() {
     185                 :          26 :     presets.clear();
     186                 :             :     
     187                 :             :     // 1. Default - Uses Preset struct defaults from Config.h (Single Source of Truth)
     188   [ +  -  +  -  :          52 :     presets.push_back(Preset("Default", true));
                   +  - ]
     189                 :             :     
     190                 :             :     // 2. T300 (Custom optimized)
     191                 :             :     {
     192   [ +  -  +  - ]:          26 :         Preset p("T300", true);
     193                 :          26 :         p.gain = 1.0f;
     194                 :          26 :         p.wheelbase_max_nm = 4.0f;
     195                 :          26 :         p.target_rim_nm = 4.0f;
     196                 :          26 :         p.min_force = 0.01f;
     197                 :          26 :         p.steering_shaft_gain = 1.0f;
     198                 :          26 :         p.steering_shaft_smoothing = 0.0f;
     199                 :          26 :         p.understeer = 0.5f;
     200                 :          26 :         p.flatspot_suppression = false;
     201                 :          26 :         p.notch_q = 2.0f;
     202                 :          26 :         p.flatspot_strength = 1.0f;
     203                 :          26 :         p.static_notch_enabled = false;
     204                 :          26 :         p.static_notch_freq = 11.0f;
     205                 :          26 :         p.static_notch_width = 2.0f;
     206                 :          26 :         p.oversteer_boost = 2.40336f;
     207                 :          26 :         p.sop = 0.425003f;
     208                 :          26 :         p.rear_align_effect = 0.966383f;
     209                 :          26 :         p.sop_yaw_gain = 0.386555f;
     210                 :          26 :         p.yaw_kick_threshold = 1.68f;
     211                 :          26 :         p.yaw_smoothing = 0.005f;
     212                 :          26 :         p.gyro_gain = 0.0336134f;
     213                 :          26 :         p.gyro_smoothing = 0.0f;
     214                 :          26 :         p.sop_smoothing = 1.0f;
     215                 :          26 :         p.sop_scale = 1.0f;
     216                 :          26 :         p.understeer_affects_sop = false;
     217                 :          26 :         p.slip_smoothing = 0.0f;
     218                 :          26 :         p.chassis_smoothing = 0.0f;
     219                 :          26 :         p.optimal_slip_angle = 0.10f;   // CHANGED from 0.06f
     220                 :          26 :         p.optimal_slip_ratio = 0.12f;
     221                 :          26 :         p.lockup_enabled = true;
     222                 :          26 :         p.lockup_gain = 2.0f;
     223                 :          26 :         p.brake_load_cap = 10.0f;
     224                 :          26 :         p.lockup_freq_scale = 1.02f;
     225                 :          26 :         p.lockup_gamma = 0.1f;
     226                 :          26 :         p.lockup_start_pct = 1.0f;
     227                 :          26 :         p.lockup_full_pct = 5.0f;
     228                 :          26 :         p.lockup_prediction_sens = 10.0f;
     229                 :          26 :         p.lockup_bump_reject = 0.1f;
     230                 :          26 :         p.lockup_rear_boost = 10.0f;
     231                 :          26 :         p.abs_pulse_enabled = true;
     232                 :          26 :         p.abs_gain = 2.0f;
     233                 :          26 :         p.abs_freq = 20.0f;
     234                 :          26 :         p.texture_load_cap = 1.96f;
     235                 :          26 :         p.slide_enabled = true;
     236                 :          26 :         p.slide_gain = 0.235294f;
     237                 :          26 :         p.slide_freq = 1.0f;
     238                 :          26 :         p.road_enabled = true;
     239                 :          26 :         p.road_gain = 2.0f;
     240                 :          26 :         p.road_fallback_scale = 0.05f;
     241                 :          26 :         p.spin_enabled = true;
     242                 :          26 :         p.spin_gain = 0.5f;
     243                 :          26 :         p.spin_freq_scale = 1.0f;
     244                 :          26 :         p.scrub_drag_gain = 0.0462185f;
     245                 :          26 :         p.bottoming_method = 0;
     246                 :          26 :         p.speed_gate_lower = 0.0f;
     247                 :          26 :         p.speed_gate_upper = 0.277778f;
     248         [ +  - ]:          26 :         presets.push_back(p);
     249                 :          26 :     }
     250                 :             :     
     251                 :             :     // 3. GT3 DD 15 Nm (Simagic Alpha)
     252                 :             :     {
     253   [ +  -  +  - ]:          26 :         Preset p("GT3 DD 15 Nm (Simagic Alpha)", true);
     254                 :          26 :         p.gain = 1.0f;
     255                 :          26 :         p.wheelbase_max_nm = 15.0f;
     256                 :          26 :         p.target_rim_nm = 10.0f;
     257                 :          26 :         p.min_force = 0.0f;
     258                 :          26 :         p.steering_shaft_gain = 1.0f;
     259                 :          26 :         p.steering_shaft_smoothing = 0.0f;
     260                 :          26 :         p.understeer = 1.0f;
     261                 :          26 :         p.flatspot_suppression = false;
     262                 :          26 :         p.notch_q = 2.0f;
     263                 :          26 :         p.flatspot_strength = 1.0f;
     264                 :          26 :         p.static_notch_enabled = false;
     265                 :          26 :         p.static_notch_freq = 11.0f;
     266                 :          26 :         p.static_notch_width = 2.0f;
     267                 :          26 :         p.oversteer_boost = 2.52101f;
     268                 :          26 :         p.sop = 1.666f;
     269                 :          26 :         p.rear_align_effect = 0.666f;
     270                 :          26 :         p.sop_yaw_gain = 0.333f;
     271                 :          26 :         p.yaw_kick_threshold = 0.0f;
     272                 :          26 :         p.yaw_smoothing = 0.001f;
     273                 :          26 :         p.gyro_gain = 0.0f;
     274                 :          26 :         p.gyro_smoothing = 0.0f;
     275                 :          26 :         p.sop_smoothing = 0.99f;
     276                 :          26 :         p.sop_scale = 1.98f;
     277                 :          26 :         p.understeer_affects_sop = false;
     278                 :          26 :         p.slip_smoothing = 0.002f;
     279                 :          26 :         p.chassis_smoothing = 0.012f;
     280                 :          26 :         p.optimal_slip_angle = 0.1f;
     281                 :          26 :         p.optimal_slip_ratio = 0.12f;
     282                 :          26 :         p.lockup_enabled = true;
     283                 :          26 :         p.lockup_gain = 0.37479f;
     284                 :          26 :         p.brake_load_cap = 2.0f;
     285                 :          26 :         p.lockup_freq_scale = 1.0f;
     286                 :          26 :         p.lockup_gamma = 1.0f;
     287                 :          26 :         p.lockup_start_pct = 1.0f;
     288                 :          26 :         p.lockup_full_pct = 7.5f;
     289                 :          26 :         p.lockup_prediction_sens = 10.0f;
     290                 :          26 :         p.lockup_bump_reject = 0.1f;
     291                 :          26 :         p.lockup_rear_boost = 1.0f;
     292                 :          26 :         p.abs_pulse_enabled = false;
     293                 :          26 :         p.abs_gain = 2.1f;
     294                 :          26 :         p.abs_freq = 25.5f;
     295                 :          26 :         p.texture_load_cap = 1.5f;
     296                 :          26 :         p.slide_enabled = false;
     297                 :          26 :         p.slide_gain = 0.226562f;
     298                 :          26 :         p.slide_freq = 1.47f;
     299                 :          26 :         p.road_enabled = true;
     300                 :          26 :         p.road_gain = 0.0f;
     301                 :          26 :         p.road_fallback_scale = 0.05f;
     302                 :          26 :         p.spin_enabled = true;
     303                 :          26 :         p.spin_gain = 0.462185f;
     304                 :          26 :         p.spin_freq_scale = 1.8f;
     305                 :          26 :         p.scrub_drag_gain = 0.333f;
     306                 :          26 :         p.bottoming_method = 1;
     307                 :          26 :         p.speed_gate_lower = 1.0f;
     308                 :          26 :         p.speed_gate_upper = 5.0f;
     309         [ +  - ]:          26 :         presets.push_back(p);
     310                 :          26 :     }
     311                 :             :     
     312                 :             :     // 4. LMPx/HY DD 15 Nm (Simagic Alpha)
     313                 :             :     {
     314   [ +  -  +  - ]:          26 :         Preset p("LMPx/HY DD 15 Nm (Simagic Alpha)", true);
     315                 :          26 :         p.gain = 1.0f;
     316                 :          26 :         p.wheelbase_max_nm = 15.0f;
     317                 :          26 :         p.target_rim_nm = 10.0f;
     318                 :          26 :         p.min_force = 0.0f;
     319                 :          26 :         p.steering_shaft_gain = 1.0f;
     320                 :          26 :         p.steering_shaft_smoothing = 0.0f;
     321                 :          26 :         p.understeer = 1.0f;
     322                 :          26 :         p.flatspot_suppression = false;
     323                 :          26 :         p.notch_q = 2.0f;
     324                 :          26 :         p.flatspot_strength = 1.0f;
     325                 :          26 :         p.static_notch_enabled = false;
     326                 :          26 :         p.static_notch_freq = 11.0f;
     327                 :          26 :         p.static_notch_width = 2.0f;
     328                 :          26 :         p.oversteer_boost = 2.52101f;
     329                 :          26 :         p.sop = 1.666f;
     330                 :          26 :         p.rear_align_effect = 0.666f;
     331                 :          26 :         p.sop_yaw_gain = 0.333f;
     332                 :          26 :         p.yaw_kick_threshold = 0.0f;
     333                 :          26 :         p.yaw_smoothing = 0.003f;
     334                 :          26 :         p.gyro_gain = 0.0f;
     335                 :          26 :         p.gyro_smoothing = 0.003f;
     336                 :          26 :         p.sop_smoothing = 0.97f;
     337                 :          26 :         p.sop_scale = 1.59f;
     338                 :          26 :         p.understeer_affects_sop = false;
     339                 :          26 :         p.slip_smoothing = 0.003f;
     340                 :          26 :         p.chassis_smoothing = 0.019f;
     341                 :          26 :         p.optimal_slip_angle = 0.12f;
     342                 :          26 :         p.optimal_slip_ratio = 0.12f;
     343                 :          26 :         p.lockup_enabled = true;
     344                 :          26 :         p.lockup_gain = 0.37479f;
     345                 :          26 :         p.brake_load_cap = 2.0f;
     346                 :          26 :         p.lockup_freq_scale = 1.0f;
     347                 :          26 :         p.lockup_gamma = 1.0f;
     348                 :          26 :         p.lockup_start_pct = 1.0f;
     349                 :          26 :         p.lockup_full_pct = 7.5f;
     350                 :          26 :         p.lockup_prediction_sens = 10.0f;
     351                 :          26 :         p.lockup_bump_reject = 0.1f;
     352                 :          26 :         p.lockup_rear_boost = 1.0f;
     353                 :          26 :         p.abs_pulse_enabled = false;
     354                 :          26 :         p.abs_gain = 2.1f;
     355                 :          26 :         p.abs_freq = 25.5f;
     356                 :          26 :         p.texture_load_cap = 1.5f;
     357                 :          26 :         p.slide_enabled = false;
     358                 :          26 :         p.slide_gain = 0.226562f;
     359                 :          26 :         p.slide_freq = 1.47f;
     360                 :          26 :         p.road_enabled = true;
     361                 :          26 :         p.road_gain = 0.0f;
     362                 :          26 :         p.road_fallback_scale = 0.05f;
     363                 :          26 :         p.spin_enabled = true;
     364                 :          26 :         p.spin_gain = 0.462185f;
     365                 :          26 :         p.spin_freq_scale = 1.8f;
     366                 :          26 :         p.scrub_drag_gain = 0.333f;
     367                 :          26 :         p.bottoming_method = 1;
     368                 :          26 :         p.speed_gate_lower = 1.0f;
     369                 :          26 :         p.speed_gate_upper = 5.0f;
     370         [ +  - ]:          26 :         presets.push_back(p);
     371                 :          26 :     }
     372                 :             :     
     373                 :             :     // 5. GM DD 21 Nm (Moza R21 Ultra)
     374                 :             :     {
     375   [ +  -  +  - ]:          26 :         Preset p("GM DD 21 Nm (Moza R21 Ultra)", true);
     376                 :          26 :         p.gain = 1.454f;
     377                 :          26 :         p.wheelbase_max_nm = 21.0f;
     378                 :          26 :         p.target_rim_nm = 12.0f;
     379                 :          26 :         p.min_force = 0.0f;
     380                 :          26 :         p.steering_shaft_gain = 1.989f;
     381                 :          26 :         p.steering_shaft_smoothing = 0.0f;
     382                 :          26 :         p.understeer = 0.638f;
     383                 :          26 :         p.flatspot_suppression = true;
     384                 :          26 :         p.notch_q = 0.57f;
     385                 :          26 :         p.flatspot_strength = 1.0f;
     386                 :          26 :         p.static_notch_enabled = false;
     387                 :          26 :         p.static_notch_freq = 11.0f;
     388                 :          26 :         p.static_notch_width = 2.0f;
     389                 :          26 :         p.oversteer_boost = 0.0f;
     390                 :          26 :         p.sop = 0.0f;
     391                 :          26 :         p.rear_align_effect = 0.29f;
     392                 :          26 :         p.sop_yaw_gain = 0.0f;
     393                 :          26 :         p.yaw_kick_threshold = 0.0f;
     394                 :          26 :         p.yaw_smoothing = 0.015f;
     395                 :          26 :         p.gyro_gain = 0.0f;
     396                 :          26 :         p.gyro_smoothing = 0.0f;
     397                 :          26 :         p.sop_smoothing = 0.0f;
     398                 :          26 :         p.sop_scale = 0.89f;
     399                 :          26 :         p.understeer_affects_sop = false;
     400                 :          26 :         p.slip_smoothing = 0.002f;
     401                 :          26 :         p.chassis_smoothing = 0.0f;
     402                 :          26 :         p.optimal_slip_angle = 0.1f;
     403                 :          26 :         p.optimal_slip_ratio = 0.12f;
     404                 :          26 :         p.lockup_enabled = true;
     405                 :          26 :         p.lockup_gain = 0.977f;
     406                 :          26 :         p.brake_load_cap = 81.0f;
     407                 :          26 :         p.lockup_freq_scale = 1.0f;
     408                 :          26 :         p.lockup_gamma = 1.0f;
     409                 :          26 :         p.lockup_start_pct = 1.0f;
     410                 :          26 :         p.lockup_full_pct = 7.5f;
     411                 :          26 :         p.lockup_prediction_sens = 10.0f;
     412                 :          26 :         p.lockup_bump_reject = 0.1f;
     413                 :          26 :         p.lockup_rear_boost = 1.0f;
     414                 :          26 :         p.abs_pulse_enabled = false;
     415                 :          26 :         p.abs_gain = 2.1f;
     416                 :          26 :         p.abs_freq = 25.5f;
     417                 :          26 :         p.texture_load_cap = 1.5f;
     418                 :          26 :         p.slide_enabled = false;
     419                 :          26 :         p.slide_gain = 0.0f;
     420                 :          26 :         p.slide_freq = 1.47f;
     421                 :          26 :         p.road_enabled = true;
     422                 :          26 :         p.road_gain = 0.0f;
     423                 :          26 :         p.road_fallback_scale = 0.05f;
     424                 :          26 :         p.spin_enabled = true;
     425                 :          26 :         p.spin_gain = 0.462185f;
     426                 :          26 :         p.spin_freq_scale = 1.8f;
     427                 :          26 :         p.scrub_drag_gain = 0.333f;
     428                 :          26 :         p.bottoming_method = 1;
     429                 :          26 :         p.speed_gate_lower = 1.0f;
     430                 :          26 :         p.speed_gate_upper = 5.0f;
     431         [ +  - ]:          26 :         presets.push_back(p);
     432                 :          26 :     }
     433                 :             :     
     434                 :             :     // 6. GM + Yaw Kick DD 21 Nm (Moza R21 Ultra)
     435                 :             :     {
     436                 :             :         // Copy GM preset and add yaw kick
     437   [ +  -  +  - ]:          26 :         Preset p("GM + Yaw Kick DD 21 Nm (Moza R21 Ultra)", true);
     438                 :          26 :         p.gain = 1.454f;
     439                 :          26 :         p.wheelbase_max_nm = 21.0f;
     440                 :          26 :         p.target_rim_nm = 12.0f;
     441                 :          26 :         p.min_force = 0.0f;
     442                 :          26 :         p.steering_shaft_gain = 1.989f;
     443                 :          26 :         p.steering_shaft_smoothing = 0.0f;
     444                 :          26 :         p.understeer = 0.638f;
     445                 :          26 :         p.flatspot_suppression = true;
     446                 :          26 :         p.notch_q = 0.57f;
     447                 :          26 :         p.flatspot_strength = 1.0f;
     448                 :          26 :         p.static_notch_enabled = false;
     449                 :          26 :         p.static_notch_freq = 11.0f;
     450                 :          26 :         p.static_notch_width = 2.0f;
     451                 :          26 :         p.oversteer_boost = 0.0f;
     452                 :          26 :         p.sop = 0.0f;
     453                 :          26 :         p.rear_align_effect = 0.29f;
     454                 :          26 :         p.sop_yaw_gain = 0.333f;  // ONLY DIFFERENCE: Added yaw kick
     455                 :          26 :         p.yaw_kick_threshold = 0.0f;
     456                 :          26 :         p.yaw_smoothing = 0.003f;
     457                 :          26 :         p.gyro_gain = 0.0f;
     458                 :          26 :         p.gyro_smoothing = 0.0f;
     459                 :          26 :         p.sop_smoothing = 0.0f;
     460                 :          26 :         p.sop_scale = 0.89f;
     461                 :          26 :         p.understeer_affects_sop = false;
     462                 :          26 :         p.slip_smoothing = 0.002f;
     463                 :          26 :         p.chassis_smoothing = 0.0f;
     464                 :          26 :         p.optimal_slip_angle = 0.1f;
     465                 :          26 :         p.optimal_slip_ratio = 0.12f;
     466                 :          26 :         p.lockup_enabled = true;
     467                 :          26 :         p.lockup_gain = 0.977f;
     468                 :          26 :         p.brake_load_cap = 81.0f;
     469                 :          26 :         p.lockup_freq_scale = 1.0f;
     470                 :          26 :         p.lockup_gamma = 1.0f;
     471                 :          26 :         p.lockup_start_pct = 1.0f;
     472                 :          26 :         p.lockup_full_pct = 7.5f;
     473                 :          26 :         p.lockup_prediction_sens = 10.0f;
     474                 :          26 :         p.lockup_bump_reject = 0.1f;
     475                 :          26 :         p.lockup_rear_boost = 1.0f;
     476                 :          26 :         p.abs_pulse_enabled = false;
     477                 :          26 :         p.abs_gain = 2.1f;
     478                 :          26 :         p.abs_freq = 25.5f;
     479                 :          26 :         p.texture_load_cap = 1.5f;
     480                 :          26 :         p.slide_enabled = false;
     481                 :          26 :         p.slide_gain = 0.0f;
     482                 :          26 :         p.slide_freq = 1.47f;
     483                 :          26 :         p.road_enabled = true;
     484                 :          26 :         p.road_gain = 0.0f;
     485                 :          26 :         p.road_fallback_scale = 0.05f;
     486                 :          26 :         p.spin_enabled = true;
     487                 :          26 :         p.spin_gain = 0.462185f;
     488                 :          26 :         p.spin_freq_scale = 1.8f;
     489                 :          26 :         p.scrub_drag_gain = 0.333f;
     490                 :          26 :         p.bottoming_method = 1;
     491                 :          26 :         p.speed_gate_lower = 1.0f;
     492                 :          26 :         p.speed_gate_upper = 5.0f;
     493         [ +  - ]:          26 :         presets.push_back(p);
     494                 :          26 :     }
     495                 :             :     
     496                 :             :     // 8. Test: Game Base FFB Only
     497   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: Game Base FFB Only", true)
                   +  - ]
     498                 :          26 :         .SetUndersteer(0.0f)
     499                 :          26 :         .SetSoP(0.0f)
     500                 :          26 :         .SetSoPScale(1.0f)
     501                 :          26 :         .SetSmoothing(0.85f)
     502                 :          26 :         .SetSlipSmoothing(0.015f)
     503                 :          26 :         .SetSlide(false, 0.0f)
     504                 :          26 :         .SetRearAlign(0.0f)
     505                 :             :     );
     506                 :             : 
     507                 :             :     // 9. Test: SoP Only
     508   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: SoP Only", true)
                   +  - ]
     509                 :          26 :         .SetUndersteer(0.0f)
     510                 :          26 :         .SetSoP(0.08f)
     511                 :          26 :         .SetSoPScale(1.0f)
     512                 :          26 :         .SetSmoothing(0.85f)
     513                 :          26 :         .SetSlipSmoothing(0.015f)
     514                 :          26 :         .SetSlide(false, 0.0f)
     515                 :          26 :         .SetRearAlign(0.0f)
     516                 :          26 :         .SetSoPYaw(0.0f)
     517                 :             :     );
     518                 :             : 
     519                 :             :     // 10. Test: Understeer Only (Updated v0.6.31 for proper effect isolation)
     520   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: Understeer Only", true)
                   +  - ]
     521                 :             :         // PRIMARY EFFECT
     522                 :          26 :         .SetUndersteer(0.61f)
     523                 :             :         
     524                 :             :         // DISABLE ALL OTHER EFFECTS
     525                 :          26 :         .SetSoP(0.0f)
     526                 :          26 :         .SetSoPScale(1.0f)
     527                 :          26 :         .SetOversteer(0.0f)          // Disable oversteer boost
     528                 :          26 :         .SetRearAlign(0.0f)
     529                 :          26 :         .SetSoPYaw(0.0f)             // Disable yaw kick
     530                 :          26 :         .SetGyro(0.0f)               // Disable gyro damping
     531                 :             :         
     532                 :             :         // DISABLE ALL TEXTURES
     533                 :          26 :         .SetSlide(false, 0.0f)
     534                 :          26 :         .SetRoad(false, 0.0f)        // Disable road texture
     535                 :          26 :         .SetSpin(false, 0.0f)        // Disable spin
     536                 :          26 :         .SetLockup(false, 0.0f)      // Disable lockup vibration
     537                 :          26 :         .SetAdvancedBraking(0.5f, 20.0f, 0.1f, false, 0.0f)  // Disable ABS pulse
     538                 :          26 :         .SetScrub(0.0f)
     539                 :             :         
     540                 :             :         // SMOOTHING
     541                 :          26 :         .SetSmoothing(0.85f)         // SoP smoothing (doesn't affect test since SoP=0)
     542                 :          26 :         .SetSlipSmoothing(0.015f)    // Slip angle smoothing (important for grip calculation)
     543                 :             :         
     544                 :             :         // PHYSICS PARAMETERS (Explicit for clarity and future-proofing)
     545                 :          26 :         .SetOptimalSlip(0.10f, 0.12f)  // Explicit optimal slip thresholds
     546                 :          26 :         .SetSpeedGate(0.0f, 0.0f)      // Disable speed gate (0 = no gating)
     547                 :             :     );
     548                 :             : 
     549                 :             :     // 11. Test: Yaw Kick Only
     550   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: Yaw Kick Only", true)
                   +  - ]
     551                 :             :         // PRIMARY EFFECT
     552                 :          26 :         .SetSoPYaw(0.386555f)        // Yaw kick at T300 level
     553                 :          26 :         .SetYawKickThreshold(1.68f)  // T300 threshold
     554                 :          26 :         .SetYawSmoothing(0.005f)     // T300 smoothing
     555                 :             :         
     556                 :             :         // DISABLE ALL OTHER EFFECTS
     557                 :          26 :         .SetUndersteer(0.0f)
     558                 :          26 :         .SetSoP(0.0f)
     559                 :          26 :         .SetSoPScale(1.0f)
     560                 :          26 :         .SetOversteer(0.0f)
     561                 :          26 :         .SetRearAlign(0.0f)
     562                 :          26 :         .SetGyro(0.0f)
     563                 :             :         
     564                 :             :         // DISABLE ALL TEXTURES
     565                 :          26 :         .SetSlide(false, 0.0f)
     566                 :          26 :         .SetRoad(false, 0.0f)
     567                 :          26 :         .SetSpin(false, 0.0f)
     568                 :          26 :         .SetLockup(false, 0.0f)
     569                 :          26 :         .SetAdvancedBraking(0.5f, 20.0f, 0.1f, false, 0.0f)
     570                 :          26 :         .SetScrub(0.0f)
     571                 :             :         
     572                 :             :         // SMOOTHING
     573                 :          26 :         .SetSmoothing(0.85f)
     574                 :          26 :         .SetSlipSmoothing(0.015f)
     575                 :             :     );
     576                 :             : 
     577                 :             :     // 12. Test: Textures Only
     578   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: Textures Only", true)
                   +  - ]
     579                 :          26 :         .SetUndersteer(0.0f)
     580                 :          26 :         .SetSoP(0.0f)
     581                 :          26 :         .SetSoPScale(0.0f)
     582                 :          26 :         .SetSmoothing(0.85f)
     583                 :          26 :         .SetSlipSmoothing(0.015f)
     584                 :          26 :         .SetLockup(true, 1.0f)
     585                 :          26 :         .SetSpin(true, 1.0f)
     586                 :          26 :         .SetSlide(true, 0.39f)
     587                 :          26 :         .SetRoad(true, 1.0f)
     588                 :          26 :         .SetRearAlign(0.0f)
     589                 :             :     );
     590                 :             : 
     591                 :             :     // 13. Test: Rear Align Torque Only
     592   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: Rear Align Torque Only", true)
                   +  - ]
     593                 :          26 :         .SetGain(1.0f)
     594                 :          26 :         .SetUndersteer(0.0f)
     595                 :          26 :         .SetSoP(0.0f)
     596                 :          26 :         .SetSmoothing(0.85f)
     597                 :          26 :         .SetSlipSmoothing(0.015f)
     598                 :          26 :         .SetSlide(false, 0.0f)
     599                 :          26 :         .SetRearAlign(0.90f)
     600                 :          26 :         .SetSoPYaw(0.0f)
     601                 :             :     );
     602                 :             : 
     603                 :             :     // 14. Test: SoP Base Only
     604   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: SoP Base Only", true)
                   +  - ]
     605                 :          26 :         .SetGain(1.0f)
     606                 :          26 :         .SetUndersteer(0.0f)
     607                 :          26 :         .SetSoP(0.08f)
     608                 :          26 :         .SetSmoothing(0.85f)
     609                 :          26 :         .SetSlipSmoothing(0.015f)
     610                 :          26 :         .SetSlide(false, 0.0f)
     611                 :          26 :         .SetRearAlign(0.0f)
     612                 :          26 :         .SetSoPYaw(0.0f)
     613                 :             :     );
     614                 :             : 
     615                 :             :     // 15. Test: Slide Texture Only
     616   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: Slide Texture Only", true)
                   +  - ]
     617                 :          26 :         .SetGain(1.0f)
     618                 :          26 :         .SetUndersteer(0.0f)
     619                 :          26 :         .SetSoP(0.0f)
     620                 :          26 :         .SetSmoothing(0.85f)
     621                 :          26 :         .SetSlipSmoothing(0.015f)
     622                 :          26 :         .SetSlide(true, 0.39f, 1.0f)
     623                 :          26 :         .SetRearAlign(0.0f)
     624                 :             :     );
     625                 :             : 
     626                 :             :     // 16. Test: No Effects
     627   [ +  -  +  -  :          78 :     presets.push_back(Preset("Test: No Effects", true)
                   +  - ]
     628                 :          26 :         .SetGain(1.0f)
     629                 :          26 :         .SetUndersteer(0.0f)
     630                 :          26 :         .SetSoP(0.0f)
     631                 :          26 :         .SetSmoothing(0.85f)
     632                 :          26 :         .SetSlipSmoothing(0.015f)
     633                 :          26 :         .SetSlide(false, 0.0f)
     634                 :          26 :         .SetRearAlign(0.0f)
     635                 :             :     );
     636                 :             : 
     637                 :             :     // --- NEW GUIDE PRESETS (v0.4.24) ---
     638                 :             : 
     639                 :             :     // 17. Guide: Understeer (Front Grip Loss)
     640   [ +  -  +  -  :          78 :     presets.push_back(Preset("Guide: Understeer (Front Grip)", true)
                   +  - ]
     641                 :          26 :         .SetGain(1.0f)
     642                 :          26 :         .SetUndersteer(0.61f)
     643                 :          26 :         .SetSoP(0.0f)
     644                 :          26 :         .SetOversteer(0.0f)
     645                 :          26 :         .SetRearAlign(0.0f)
     646                 :          26 :         .SetSoPYaw(0.0f)
     647                 :          26 :         .SetGyro(0.0f)
     648                 :          26 :         .SetLockup(false, 0.0f)
     649                 :          26 :         .SetSpin(false, 0.0f)
     650                 :          26 :         .SetSlide(false, 0.0f)
     651                 :          26 :         .SetRoad(false, 0.0f)
     652                 :          26 :         .SetScrub(0.0f)
     653                 :          26 :         .SetSmoothing(0.85f)
     654                 :          26 :         .SetSlipSmoothing(0.015f)
     655                 :             :     );
     656                 :             : 
     657                 :             :     // 18. Guide: Oversteer (Rear Grip Loss)
     658   [ +  -  +  -  :          78 :     presets.push_back(Preset("Guide: Oversteer (Rear Grip)", true)
                   +  - ]
     659                 :          26 :         .SetGain(1.0f)
     660                 :          26 :         .SetUndersteer(0.0f)
     661                 :          26 :         .SetSoP(0.08f)
     662                 :          26 :         .SetSoPScale(1.0f)
     663                 :          26 :         .SetRearAlign(0.90f)
     664                 :          26 :         .SetOversteer(0.65f)
     665                 :          26 :         .SetSoPYaw(0.0f)
     666                 :          26 :         .SetGyro(0.0f)
     667                 :          26 :         .SetLockup(false, 0.0f)
     668                 :          26 :         .SetSpin(false, 0.0f)
     669                 :          26 :         .SetSlide(false, 0.0f)
     670                 :          26 :         .SetRoad(false, 0.0f)
     671                 :          26 :         .SetScrub(0.0f)
     672                 :          26 :         .SetSmoothing(0.85f)
     673                 :          26 :         .SetSlipSmoothing(0.015f)
     674                 :             :     );
     675                 :             : 
     676                 :             :     // 19. Guide: Slide Texture (Scrubbing)
     677   [ +  -  +  -  :          78 :     presets.push_back(Preset("Guide: Slide Texture (Scrub)", true)
                   +  - ]
     678                 :          26 :         .SetGain(1.0f)
     679                 :          26 :         .SetUndersteer(0.0f)
     680                 :          26 :         .SetSoP(0.0f)
     681                 :          26 :         .SetOversteer(0.0f)
     682                 :          26 :         .SetRearAlign(0.0f)
     683                 :          26 :         .SetSlide(true, 0.39f, 1.0f) // Gain 0.39, Freq 1.0 (Rumble)
     684                 :          26 :         .SetScrub(1.0f)
     685                 :          26 :         .SetLockup(false, 0.0f)
     686                 :          26 :         .SetSpin(false, 0.0f)
     687                 :          26 :         .SetRoad(false, 0.0f)
     688                 :          26 :         .SetSmoothing(0.85f)
     689                 :          26 :         .SetSlipSmoothing(0.015f)
     690                 :             :     );
     691                 :             : 
     692                 :             :     // 20. Guide: Braking Lockup
     693   [ +  -  +  -  :          78 :     presets.push_back(Preset("Guide: Braking Lockup", true)
                   +  - ]
     694                 :          26 :         .SetGain(1.0f)
     695                 :          26 :         .SetUndersteer(0.0f)
     696                 :          26 :         .SetSoP(0.0f)
     697                 :          26 :         .SetOversteer(0.0f)
     698                 :          26 :         .SetRearAlign(0.0f)
     699                 :          26 :         .SetLockup(true, 1.0f)
     700                 :          26 :         .SetSpin(false, 0.0f)
     701                 :          26 :         .SetSlide(false, 0.0f)
     702                 :          26 :         .SetRoad(false, 0.0f)
     703                 :          26 :         .SetScrub(0.0f)
     704                 :          26 :         .SetSmoothing(0.85f)
     705                 :          26 :         .SetSlipSmoothing(0.015f)
     706                 :             :     );
     707                 :             : 
     708                 :             :     // 21. Guide: Traction Loss (Wheel Spin)
     709   [ +  -  +  -  :          78 :     presets.push_back(Preset("Guide: Traction Loss (Spin)", true)
                   +  - ]
     710                 :          26 :         .SetGain(1.0f)
     711                 :          26 :         .SetUndersteer(0.0f)
     712                 :          26 :         .SetSoP(0.0f)
     713                 :          26 :         .SetOversteer(0.0f)
     714                 :          26 :         .SetRearAlign(0.0f)
     715                 :          26 :         .SetSpin(true, 1.0f)
     716                 :          26 :         .SetLockup(false, 0.0f)
     717                 :          26 :         .SetSlide(false, 0.0f)
     718                 :          26 :         .SetRoad(false, 0.0f)
     719                 :          26 :         .SetScrub(0.0f)
     720                 :          26 :         .SetSmoothing(0.85f)
     721                 :          26 :         .SetSlipSmoothing(0.015f)
     722                 :             :     );
     723                 :             : 
     724                 :             :      // 22. Guide: SoP Yaw (Kick)
     725   [ +  -  +  -  :          78 :     presets.push_back(Preset("Guide: SoP Yaw (Kick)", true)
                   +  - ]
     726                 :          26 :         .SetGain(1.0f)
     727                 :          26 :         .SetUndersteer(0.0f)
     728                 :          26 :         .SetSoP(0.0f)
     729                 :          26 :         .SetOversteer(0.0f)
     730                 :          26 :         .SetRearAlign(0.0f)
     731                 :          26 :         .SetSoPYaw(5.0f) // Standard T300 level
     732                 :          26 :         .SetGyro(0.0f)
     733                 :          26 :         .SetLockup(false, 0.0f)
     734                 :          26 :         .SetSpin(false, 0.0f)
     735                 :          26 :         .SetSlide(false, 0.0f)
     736                 :          26 :         .SetRoad(false, 0.0f)
     737                 :          26 :         .SetScrub(0.0f)
     738                 :          26 :         .SetSmoothing(0.85f)
     739                 :          26 :         .SetSlipSmoothing(0.015f)
     740                 :             :     );
     741                 :             : 
     742                 :             :     // 23. Guide: Gyroscopic Damping
     743   [ +  -  +  -  :          52 :     presets.push_back(Preset("Guide: Gyroscopic Damping", true)
                   +  - ]
     744                 :          26 :         .SetGain(1.0f)
     745                 :          26 :         .SetUndersteer(0.0f)
     746                 :          26 :         .SetSoP(0.0f)
     747                 :          26 :         .SetOversteer(0.0f)
     748                 :          26 :         .SetRearAlign(0.0f)
     749                 :          26 :         .SetSoPYaw(0.0f)
     750                 :          26 :         .SetGyro(1.0f) // Max damping
     751                 :          26 :         .SetLockup(false, 0.0f)
     752                 :          26 :         .SetSpin(false, 0.0f)
     753                 :          26 :         .SetSlide(false, 0.0f)
     754                 :          26 :         .SetRoad(false, 0.0f)
     755                 :          26 :         .SetScrub(0.0f)
     756                 :          26 :         .SetSmoothing(0.85f)
     757                 :          26 :         .SetSlipSmoothing(0.015f)
     758                 :             :     );
     759                 :             : 
     760                 :             :     // --- Parse User Presets from config.ini ---
     761                 :             :     // (Keep the existing parsing logic below, it works fine for file I/O)
     762         [ +  - ]:          26 :     std::ifstream file(m_config_path);
     763         [ +  + ]:          26 :     if (!file.is_open()) return;
     764                 :             : 
     765                 :          21 :     std::string line;
     766                 :          21 :     bool in_presets = false;
     767                 :          21 :     bool needs_save = false;
     768                 :             :     
     769         [ +  - ]:          21 :     std::string current_preset_name = "";
     770         [ +  - ]:          21 :     Preset current_preset; // Uses default constructor with default values
     771         [ +  - ]:          21 :     std::string current_preset_version = "";
     772                 :          21 :     bool preset_pending = false;
     773                 :          21 :     bool legacy_torque_hack = false;
     774                 :          21 :     float legacy_torque_val = 100.0f;
     775                 :             : 
     776   [ +  -  +  -  :        3219 :     while (std::getline(file, line)) {
                   +  + ]
     777                 :             :         // Strip whitespace
     778         [ +  - ]:        3198 :         line.erase(0, line.find_first_not_of(" \t\r\n"));
     779         [ +  - ]:        3198 :         line.erase(line.find_last_not_of(" \t\r\n") + 1);
     780                 :             :         
     781   [ +  +  +  -  :        3198 :         if (line.empty() || line[0] == ';') continue;
             +  +  +  + ]
     782                 :             : 
     783   [ +  -  +  + ]:        2927 :         if (line[0] == '[') {
     784   [ +  +  +  -  :          55 :             if (preset_pending && !current_preset_name.empty()) {
                   +  + ]
     785         [ +  - ]:           5 :                 current_preset.name = current_preset_name;
     786                 :           5 :                 current_preset.is_builtin = false; // User preset
     787                 :             :                 
     788                 :             :                 // Issue #211: Legacy 100Nm hack scaling
     789   [ -  +  -  -  :           5 :                 if (legacy_torque_hack && IsVersionLessEqual(current_preset_version, "0.7.66")) {
          -  -  -  -  -  
          +  -  +  -  +  
             -  -  -  - ]
     790                 :           0 :                     current_preset.gain *= (15.0f / legacy_torque_val);
     791   [ #  #  #  #  :           0 :                     std::cout << "[Config] Migrated legacy 100Nm hack for preset '" << current_preset_name << "'. Scaling gain." << std::endl;
             #  #  #  # ]
     792                 :           0 :                     needs_save = true;
     793                 :             :                 }
     794                 :             : 
     795                 :             :                 // MIGRATION: If version is missing or old, update it
     796         [ -  + ]:           5 :                 if (current_preset_version.empty()) {
     797         [ #  # ]:           0 :                     current_preset.app_version = LMUFFB_VERSION;
     798                 :           0 :                     needs_save = true;
     799   [ #  #  #  #  :           0 :                     std::cout << "[Config] Migrated legacy preset '" << current_preset_name << "' to version " << LMUFFB_VERSION << std::endl;
          #  #  #  #  #  
                      # ]
     800                 :             :                 } else {
     801         [ +  - ]:           5 :                     current_preset.app_version = current_preset_version;
     802                 :             :                 }
     803                 :             :                 
     804         [ +  - ]:           5 :                 current_preset.Validate(); // v0.7.15: Validate before adding
     805         [ +  - ]:           5 :                 presets.push_back(current_preset);
     806                 :           5 :                 preset_pending = false;
     807                 :             :             }
     808                 :             :             
     809   [ +  -  +  + ]:          55 :             if (line == "[Presets]") {
     810                 :          18 :                 in_presets = true;
     811   [ +  -  +  + ]:          37 :             } else if (line.rfind("[Preset:", 0) == 0) { 
     812                 :          22 :                 in_presets = false; 
     813                 :          22 :                 size_t end_pos = line.find(']');
     814         [ +  - ]:          22 :                 if (end_pos != std::string::npos) {
     815         [ +  - ]:          22 :                     current_preset_name = line.substr(8, end_pos - 8);
     816   [ +  -  +  - ]:          22 :                     current_preset = Preset(current_preset_name, false); // Reset to defaults, not builtin
     817                 :          22 :                     preset_pending = true;
     818         [ +  - ]:          22 :                     current_preset_version = "";
     819                 :          22 :                     legacy_torque_hack = false;
     820                 :          22 :                     legacy_torque_val = 100.0f;
     821                 :             :                 }
     822                 :             :             } else {
     823                 :          15 :                 in_presets = false;
     824                 :             :             }
     825                 :          55 :             continue;
     826                 :          55 :         }
     827                 :             : 
     828         [ +  + ]:        2872 :         if (preset_pending) {
     829         [ +  - ]:        1370 :             ParsePresetLine(line, current_preset, current_preset_version, needs_save, legacy_torque_hack, legacy_torque_val);
     830                 :             :         }
     831                 :             :     }
     832                 :             :     
     833   [ +  +  +  -  :          21 :     if (preset_pending && !current_preset_name.empty()) {
                   +  + ]
     834         [ +  - ]:          17 :         current_preset.name = current_preset_name;
     835                 :          17 :         current_preset.is_builtin = false;
     836                 :             :         
     837                 :             :         // Issue #211: Legacy 100Nm hack scaling
     838   [ +  +  +  -  :          19 :         if (legacy_torque_hack && IsVersionLessEqual(current_preset_version, "0.7.66")) {
          +  -  +  -  +  
          +  +  +  +  +  
             -  -  -  - ]
     839                 :           1 :             current_preset.gain *= (15.0f / legacy_torque_val);
     840   [ +  -  +  -  :           1 :             std::cout << "[Config] Migrated legacy 100Nm hack for preset '" << current_preset_name << "'. Scaling gain." << std::endl;
             +  -  +  - ]
     841                 :           1 :             needs_save = true;
     842                 :             :         }
     843                 :             : 
     844                 :             :         // MIGRATION: If version is missing or old, update it
     845         [ +  + ]:          17 :         if (current_preset_version.empty()) {
     846         [ +  - ]:           4 :             current_preset.app_version = LMUFFB_VERSION;
     847                 :           4 :             needs_save = true;
     848   [ +  -  +  -  :           4 :             std::cout << "[Config] Migrated legacy preset '" << current_preset_name << "' to version " << LMUFFB_VERSION << std::endl;
          +  -  +  -  +  
                      - ]
     849                 :             :         } else {
     850         [ +  - ]:          13 :             current_preset.app_version = current_preset_version;
     851                 :             :         }
     852                 :             :         
     853         [ +  - ]:          17 :         current_preset.Validate(); // v0.7.15: Validate before adding
     854         [ +  - ]:          17 :         presets.push_back(current_preset);
     855                 :             :     }
     856                 :             : 
     857                 :             :     // Auto-save if migration occurred
     858         [ +  + ]:          21 :     if (needs_save) {
     859                 :           5 :         m_needs_save = true;
     860                 :             :     }
     861         [ +  + ]:          26 : }
     862                 :             : 
     863                 :          18 : void Config::ApplyPreset(int index, FFBEngine& engine) {
     864   [ +  +  +  +  :          18 :     if (index >= 0 && index < presets.size()) {
                   +  + ]
     865         [ +  - ]:          14 :         std::lock_guard<std::recursive_mutex> lock(g_engine_mutex);
     866         [ +  - ]:          14 :         presets[index].Apply(engine);
     867         [ +  - ]:          14 :         m_last_preset_name = presets[index].name;
     868   [ +  -  +  -  :          14 :         std::cout << "[Config] Applied preset: " << presets[index].name << std::endl;
                   +  - ]
     869   [ +  -  +  - ]:          14 :         Save(engine); // Integrated Auto-Save (v0.6.27)
     870                 :          14 :     }
     871                 :          18 : }
     872                 :             : 
     873                 :          52 : void Config::WritePresetFields(std::ofstream& file, const Preset& p) {
     874                 :          52 :     file << "app_version=" << p.app_version << "\n";
     875                 :          52 :     file << "gain=" << p.gain << "\n";
     876                 :          52 :     file << "wheelbase_max_nm=" << p.wheelbase_max_nm << "\n";
     877                 :          52 :     file << "target_rim_nm=" << p.target_rim_nm << "\n";
     878                 :          52 :     file << "min_force=" << p.min_force << "\n";
     879                 :             : 
     880                 :          52 :     file << "steering_shaft_gain=" << p.steering_shaft_gain << "\n";
     881                 :          52 :     file << "ingame_ffb_gain=" << p.ingame_ffb_gain << "\n";
     882                 :          52 :     file << "steering_shaft_smoothing=" << p.steering_shaft_smoothing << "\n";
     883                 :          52 :     file << "understeer=" << p.understeer << "\n";
     884                 :          52 :     file << "torque_source=" << p.torque_source << "\n";
     885                 :          52 :     file << "torque_passthrough=" << p.torque_passthrough << "\n";
     886                 :          52 :     file << "flatspot_suppression=" << p.flatspot_suppression << "\n";
     887                 :          52 :     file << "notch_q=" << p.notch_q << "\n";
     888                 :          52 :     file << "flatspot_strength=" << p.flatspot_strength << "\n";
     889                 :          52 :     file << "static_notch_enabled=" << p.static_notch_enabled << "\n";
     890                 :          52 :     file << "static_notch_freq=" << p.static_notch_freq << "\n";
     891                 :          52 :     file << "static_notch_width=" << p.static_notch_width << "\n";
     892                 :             : 
     893                 :          52 :     file << "oversteer_boost=" << p.oversteer_boost << "\n";
     894                 :          52 :     file << "dynamic_weight_gain=" << p.dynamic_weight_gain << "\n";
     895                 :          52 :     file << "dynamic_weight_smoothing=" << p.dynamic_weight_smoothing << "\n";
     896                 :          52 :     file << "grip_smoothing_steady=" << p.grip_smoothing_steady << "\n";
     897                 :          52 :     file << "grip_smoothing_fast=" << p.grip_smoothing_fast << "\n";
     898                 :          52 :     file << "grip_smoothing_sensitivity=" << p.grip_smoothing_sensitivity << "\n";
     899                 :          52 :     file << "sop=" << p.sop << "\n";
     900                 :          52 :     file << "rear_align_effect=" << p.rear_align_effect << "\n";
     901                 :          52 :     file << "sop_yaw_gain=" << p.sop_yaw_gain << "\n";
     902                 :          52 :     file << "yaw_kick_threshold=" << p.yaw_kick_threshold << "\n";
     903                 :          52 :     file << "yaw_accel_smoothing=" << p.yaw_smoothing << "\n";
     904                 :          52 :     file << "gyro_gain=" << p.gyro_gain << "\n";
     905                 :          52 :     file << "gyro_smoothing_factor=" << p.gyro_smoothing << "\n";
     906                 :          52 :     file << "sop_smoothing_factor=" << p.sop_smoothing << "\n";
     907                 :          52 :     file << "sop_scale=" << p.sop_scale << "\n";
     908                 :          52 :     file << "understeer_affects_sop=" << p.understeer_affects_sop << "\n";
     909                 :          52 :     file << "slope_detection_enabled=" << p.slope_detection_enabled << "\n";
     910                 :          52 :     file << "slope_sg_window=" << p.slope_sg_window << "\n";
     911                 :          52 :     file << "slope_sensitivity=" << p.slope_sensitivity << "\n";
     912                 :             : 
     913                 :          52 :     file << "slope_smoothing_tau=" << p.slope_smoothing_tau << "\n";
     914                 :          52 :     file << "slope_min_threshold=" << p.slope_min_threshold << "\n";
     915                 :          52 :     file << "slope_max_threshold=" << p.slope_max_threshold << "\n";
     916                 :          52 :     file << "slope_alpha_threshold=" << p.slope_alpha_threshold << "\n";
     917                 :          52 :     file << "slope_decay_rate=" << p.slope_decay_rate << "\n";
     918                 :          52 :     file << "slope_confidence_enabled=" << p.slope_confidence_enabled << "\n";
     919                 :          52 :     file << "slope_g_slew_limit=" << p.slope_g_slew_limit << "\n";
     920         [ +  - ]:          52 :     file << "slope_use_torque=" << (p.slope_use_torque ? "1" : "0") << "\n";
     921                 :          52 :     file << "slope_torque_sensitivity=" << p.slope_torque_sensitivity << "\n";
     922                 :          52 :     file << "slope_confidence_max_rate=" << p.slope_confidence_max_rate << "\n";
     923                 :             : 
     924                 :          52 :     file << "slip_angle_smoothing=" << p.slip_smoothing << "\n";
     925                 :          52 :     file << "chassis_inertia_smoothing=" << p.chassis_smoothing << "\n";
     926                 :          52 :     file << "optimal_slip_angle=" << p.optimal_slip_angle << "\n";
     927                 :          52 :     file << "optimal_slip_ratio=" << p.optimal_slip_ratio << "\n";
     928                 :             : 
     929         [ +  - ]:          52 :     file << "lockup_enabled=" << (p.lockup_enabled ? "1" : "0") << "\n";
     930                 :          52 :     file << "lockup_gain=" << p.lockup_gain << "\n";
     931                 :          52 :     file << "brake_load_cap=" << p.brake_load_cap << "\n";
     932                 :          52 :     file << "lockup_freq_scale=" << p.lockup_freq_scale << "\n";
     933                 :          52 :     file << "lockup_gamma=" << p.lockup_gamma << "\n";
     934                 :          52 :     file << "lockup_start_pct=" << p.lockup_start_pct << "\n";
     935                 :          52 :     file << "lockup_full_pct=" << p.lockup_full_pct << "\n";
     936                 :          52 :     file << "lockup_prediction_sens=" << p.lockup_prediction_sens << "\n";
     937                 :          52 :     file << "lockup_bump_reject=" << p.lockup_bump_reject << "\n";
     938                 :          52 :     file << "lockup_rear_boost=" << p.lockup_rear_boost << "\n";
     939         [ +  + ]:          52 :     file << "abs_pulse_enabled=" << (p.abs_pulse_enabled ? "1" : "0") << "\n";
     940                 :          52 :     file << "abs_gain=" << p.abs_gain << "\n";
     941                 :          52 :     file << "abs_freq=" << p.abs_freq << "\n";
     942                 :             : 
     943                 :          52 :     file << "texture_load_cap=" << p.texture_load_cap << "\n";
     944         [ +  + ]:          52 :     file << "slide_enabled=" << (p.slide_enabled ? "1" : "0") << "\n";
     945                 :          52 :     file << "slide_gain=" << p.slide_gain << "\n";
     946                 :          52 :     file << "slide_freq=" << p.slide_freq << "\n";
     947         [ +  - ]:          52 :     file << "road_enabled=" << (p.road_enabled ? "1" : "0") << "\n";
     948                 :          52 :     file << "road_gain=" << p.road_gain << "\n";
     949                 :          52 :     file << "vibration_gain=" << p.vibration_gain << "\n";
     950                 :          52 :     file << "road_fallback_scale=" << p.road_fallback_scale << "\n";
     951         [ -  + ]:          52 :     file << "dynamic_normalization_enabled=" << (p.dynamic_normalization_enabled ? "1" : "0") << "\n";
     952         [ -  + ]:          52 :     file << "auto_load_normalization_enabled=" << (p.auto_load_normalization_enabled ? "1" : "0") << "\n";
     953         [ +  - ]:          52 :     file << "soft_lock_enabled=" << (p.soft_lock_enabled ? "1" : "0") << "\n";
     954                 :          52 :     file << "soft_lock_stiffness=" << p.soft_lock_stiffness << "\n";
     955                 :          52 :     file << "soft_lock_damping=" << p.soft_lock_damping << "\n";
     956         [ +  - ]:          52 :     file << "spin_enabled=" << (p.spin_enabled ? "1" : "0") << "\n";
     957                 :          52 :     file << "spin_gain=" << p.spin_gain << "\n";
     958                 :          52 :     file << "spin_freq_scale=" << p.spin_freq_scale << "\n";
     959                 :          52 :     file << "scrub_drag_gain=" << p.scrub_drag_gain << "\n";
     960                 :          52 :     file << "bottoming_method=" << p.bottoming_method << "\n";
     961         [ -  + ]:          52 :     file << "rest_api_fallback_enabled=" << (p.rest_api_enabled ? "1" : "0") << "\n";
     962                 :          52 :     file << "rest_api_port=" << p.rest_api_port << "\n";
     963                 :             : 
     964                 :          52 :     file << "speed_gate_lower=" << p.speed_gate_lower << "\n";
     965                 :          52 :     file << "speed_gate_upper=" << p.speed_gate_upper << "\n";
     966                 :          52 : }
     967                 :             : 
     968                 :           2 : void Config::ExportPreset(int index, const std::string& filename) {
     969   [ +  +  +  -  :           2 :     if (index < 0 || index >= presets.size()) return;
                   +  - ]
     970                 :             : 
     971                 :           0 :     const Preset& p = presets[index];
     972         [ #  # ]:           0 :     std::ofstream file(filename);
     973         [ #  # ]:           0 :     if (file.is_open()) {
     974   [ #  #  #  #  :           0 :         file << "[Preset:" << p.name << "]\n";
                   #  # ]
     975         [ #  # ]:           0 :         WritePresetFields(file, p);
     976         [ #  # ]:           0 :         file.close();
     977   [ #  #  #  #  :           0 :         std::cout << "[Config] Exported preset '" << p.name << "' to " << filename << std::endl;
          #  #  #  #  #  
                      # ]
     978                 :             :     } else {
     979   [ #  #  #  #  :           0 :         std::cerr << "[Config] Failed to export preset to " << filename << std::endl;
                   #  # ]
     980                 :             :     }
     981                 :           0 : }
     982                 :             : 
     983                 :           3 : bool Config::ImportPreset(const std::string& filename, const FFBEngine& engine) {
     984         [ +  - ]:           3 :     std::ifstream file(filename);
     985         [ +  + ]:           3 :     if (!file.is_open()) return false;
     986                 :             : 
     987                 :           1 :     std::string line;
     988         [ +  - ]:           1 :     std::string current_preset_name = "";
     989         [ +  - ]:           1 :     Preset current_preset;
     990         [ +  - ]:           1 :     std::string current_preset_version = "";
     991                 :           1 :     bool preset_pending = false;
     992                 :           1 :     bool imported = false;
     993                 :           1 :     bool legacy_torque_hack = false;
     994                 :           1 :     float legacy_torque_val = 100.0f;
     995                 :             : 
     996   [ +  -  +  -  :          79 :     while (std::getline(file, line)) {
                   +  + ]
     997                 :             :         // Strip whitespace
     998         [ +  - ]:          78 :         line.erase(0, line.find_first_not_of(" \t\r\n"));
     999         [ +  - ]:          78 :         line.erase(line.find_last_not_of(" \t\r\n") + 1);
    1000                 :             : 
    1001   [ +  -  +  -  :          78 :         if (line.empty() || line[0] == ';') continue;
             -  +  -  + ]
    1002                 :             : 
    1003   [ +  -  +  + ]:          78 :         if (line[0] == '[') {
    1004   [ +  -  +  - ]:           1 :             if (line.rfind("[Preset:", 0) == 0) {
    1005                 :           1 :                 size_t end_pos = line.find(']');
    1006         [ +  - ]:           1 :                 if (end_pos != std::string::npos) {
    1007         [ +  - ]:           1 :                     current_preset_name = line.substr(8, end_pos - 8);
    1008   [ +  -  +  - ]:           1 :                     current_preset = Preset(current_preset_name, false);
    1009                 :           1 :                     preset_pending = true;
    1010         [ +  - ]:           1 :                     current_preset_version = "";
    1011                 :           1 :                     legacy_torque_hack = false;
    1012                 :           1 :                     legacy_torque_val = 100.0f;
    1013                 :             :                 }
    1014                 :             :             }
    1015                 :           1 :             continue;
    1016                 :           1 :         }
    1017                 :             : 
    1018         [ +  - ]:          77 :         if (preset_pending) {
    1019                 :          77 :             bool dummy_needs_save = false;
    1020         [ +  - ]:          77 :             ParsePresetLine(line, current_preset, current_preset_version, dummy_needs_save, legacy_torque_hack, legacy_torque_val);
    1021                 :             :         }
    1022                 :             :     }
    1023                 :             : 
    1024   [ +  -  +  -  :           1 :     if (preset_pending && !current_preset_name.empty()) {
                   +  - ]
    1025         [ +  - ]:           1 :         current_preset.name = current_preset_name;
    1026                 :           1 :         current_preset.is_builtin = false;
    1027                 :             : 
    1028                 :             :         // Issue #211: Legacy 100Nm hack scaling
    1029   [ -  +  -  -  :           1 :         if (legacy_torque_hack && IsVersionLessEqual(current_preset_version, "0.7.66")) {
          -  -  -  -  -  
          +  -  +  -  +  
             -  -  -  - ]
    1030                 :           0 :             current_preset.gain *= (15.0f / legacy_torque_val);
    1031   [ #  #  #  #  :           0 :             std::cout << "[Config] Migrated legacy 100Nm hack for imported preset '" << current_preset_name << "'. Scaling gain." << std::endl;
             #  #  #  # ]
    1032                 :             :         }
    1033                 :             : 
    1034   [ +  -  +  -  :           2 :         current_preset.app_version = current_preset_version.empty() ? LMUFFB_VERSION : current_preset_version;
          -  -  +  -  -  
                      - ]
    1035                 :             : 
    1036                 :             :         // Handle name collision
    1037         [ +  - ]:           1 :         std::string base_name = current_preset.name;
    1038                 :           1 :         int counter = 1;
    1039                 :           1 :         bool exists = true;
    1040         [ +  + ]:           2 :         while (exists) {
    1041                 :           1 :             exists = false;
    1042         [ +  + ]:          27 :             for (const auto& p : presets) {
    1043         [ -  + ]:          26 :                 if (p.name == current_preset.name) {
    1044   [ #  #  #  #  :           0 :                     current_preset.name = base_name + " (" + std::to_string(counter++) + ")";
                   #  # ]
    1045                 :           0 :                     exists = true;
    1046                 :           0 :                     break;
    1047                 :             :                 }
    1048                 :             :             }
    1049                 :             :         }
    1050                 :             : 
    1051         [ +  - ]:           1 :         current_preset.Validate(); // v0.7.15: Validate before adding
    1052         [ +  - ]:           1 :         presets.push_back(current_preset);
    1053                 :           1 :         imported = true;
    1054                 :           1 :     }
    1055                 :             : 
    1056         [ +  - ]:           1 :     if (imported) {
    1057   [ +  -  +  - ]:           1 :         Save(engine);
    1058   [ +  -  +  -  :           1 :         std::cout << "[Config] Imported preset '" << current_preset.name << "' from " << filename << std::endl;
          +  -  +  -  +  
                      - ]
    1059                 :           1 :         return true;
    1060                 :             :     }
    1061                 :             : 
    1062                 :           0 :     return false;
    1063                 :           3 : }
    1064                 :             : 
    1065                 :           9 : void Config::AddUserPreset(const std::string& name, const FFBEngine& engine) {
    1066                 :             :     // Check if name exists and overwrite, or add new
    1067                 :           9 :     bool found = false;
    1068         [ +  + ]:         123 :     for (auto& p : presets) {
    1069   [ +  +  +  -  :         116 :         if (p.name == name && !p.is_builtin) {
                   +  + ]
    1070         [ +  - ]:           2 :             p.UpdateFromEngine(engine);
    1071                 :           2 :             found = true;
    1072                 :           2 :             break;
    1073                 :             :         }
    1074                 :             :     }
    1075                 :             :     
    1076         [ +  + ]:           9 :     if (!found) {
    1077   [ +  -  +  - ]:           7 :         Preset p(name, false);
    1078         [ +  - ]:           7 :         p.UpdateFromEngine(engine);
    1079         [ +  - ]:           7 :         presets.push_back(p);
    1080                 :           7 :     }
    1081                 :             :     
    1082                 :           9 :     m_last_preset_name = name;
    1083                 :             : 
    1084                 :             :     // Save immediately to persist
    1085   [ +  -  +  - ]:           9 :     Save(engine);
    1086                 :           9 : }
    1087                 :             : 
    1088                 :           9 : void Config::DeletePreset(int index, const FFBEngine& engine) {
    1089   [ +  +  +  +  :          12 :     if (index < 0 || index >= (int)presets.size()) return;
                   +  + ]
    1090         [ +  + ]:           6 :     if (presets[index].is_builtin) return; // Cannot delete builtin presets
    1091                 :             : 
    1092         [ +  - ]:           3 :     std::string name = presets[index].name;
    1093         [ +  - ]:           3 :     presets.erase(presets.begin() + index);
    1094   [ +  -  +  -  :           3 :     std::cout << "[Config] Deleted preset: " << name << std::endl;
                   +  - ]
    1095                 :             : 
    1096                 :             :     // If the deleted preset was the last used one, reset it
    1097         [ +  + ]:           3 :     if (m_last_preset_name == name) {
    1098         [ +  - ]:           2 :         m_last_preset_name = "Default";
    1099                 :             :     }
    1100                 :             : 
    1101   [ +  -  +  - ]:           3 :     Save(engine);
    1102                 :           3 : }
    1103                 :             : 
    1104                 :           6 : void Config::DuplicatePreset(int index, const FFBEngine& engine) {
    1105   [ +  +  +  +  :           6 :     if (index < 0 || index >= (int)presets.size()) return;
                   +  + ]
    1106                 :             : 
    1107         [ +  - ]:           4 :     Preset p = presets[index];
    1108         [ +  - ]:           4 :     p.name = p.name + " (Copy)";
    1109                 :           4 :     p.is_builtin = false;
    1110         [ +  - ]:           4 :     p.app_version = LMUFFB_VERSION;
    1111                 :             : 
    1112                 :             :     // Ensure unique name
    1113         [ +  - ]:           4 :     std::string base_name = p.name;
    1114                 :           4 :     int counter = 1;
    1115                 :           4 :     bool exists = true;
    1116         [ +  + ]:          11 :     while (exists) {
    1117                 :           7 :         exists = false;
    1118         [ +  + ]:          43 :         for (const auto& existing : presets) {
    1119         [ +  + ]:          39 :             if (existing.name == p.name) {
    1120   [ +  -  +  - ]:           3 :                 p.name = base_name + " " + std::to_string(counter++);
    1121                 :           3 :                 exists = true;
    1122                 :           3 :                 break;
    1123                 :             :             }
    1124                 :             :         }
    1125                 :             :     }
    1126                 :             : 
    1127         [ +  - ]:           4 :     presets.push_back(p);
    1128         [ +  - ]:           4 :     m_last_preset_name = p.name;
    1129   [ +  -  +  -  :           4 :     std::cout << "[Config] Duplicated preset to: " << p.name << std::endl;
                   +  - ]
    1130   [ +  -  +  - ]:           4 :     Save(engine);
    1131                 :           4 : }
    1132                 :             : 
    1133                 :         383 : bool Config::IsEngineDirtyRelativeToPreset(int index, const FFBEngine& engine) {
    1134   [ +  +  +  +  :         383 :     if (index < 0 || index >= (int)presets.size()) return false;
                   +  + ]
    1135                 :             : 
    1136         [ +  - ]:         380 :     Preset current_state;
    1137         [ +  - ]:         380 :     current_state.UpdateFromEngine(engine);
    1138                 :             : 
    1139                 :         380 :     return !presets[index].Equals(current_state);
    1140                 :         380 : }
    1141                 :             : 
    1142                 :          43 : void Config::SetSavedStaticLoad(const std::string& vehicleName, double value) {
    1143         [ +  - ]:          43 :     std::lock_guard<std::recursive_mutex> lock(m_static_loads_mutex);
    1144         [ +  - ]:          43 :     m_saved_static_loads[vehicleName] = value;
    1145                 :          43 : }
    1146                 :             : 
    1147                 :        1336 : bool Config::GetSavedStaticLoad(const std::string& vehicleName, double& value) {
    1148         [ +  - ]:        1336 :     std::lock_guard<std::recursive_mutex> lock(m_static_loads_mutex);
    1149         [ +  - ]:        1336 :     auto it = m_saved_static_loads.find(vehicleName);
    1150         [ +  + ]:        1336 :     if (it != m_saved_static_loads.end()) {
    1151                 :           7 :         value = it->second;
    1152                 :           7 :         return true;
    1153                 :             :     }
    1154                 :        1329 :     return false;
    1155                 :        1336 : }
    1156                 :             : 
    1157                 :          67 : void Config::Save(const FFBEngine& engine, const std::string& filename) {
    1158         [ +  - ]:          67 :     std::lock_guard<std::recursive_mutex> lock(g_engine_mutex);
    1159   [ +  +  +  - ]:          67 :     std::string final_path = filename.empty() ? m_config_path : filename;
    1160         [ +  - ]:          67 :     std::ofstream file(final_path);
    1161         [ +  + ]:          67 :     if (file.is_open()) {
    1162         [ +  - ]:          65 :         file << "; --- System & Window ---\n";
    1163                 :             :         // Config Version Tracking: The ini_version field serves dual purposes:
    1164                 :             :         // 1. Records the app version that last saved this config
    1165                 :             :         // 2. Acts as an implicit config format version for migration logic
    1166                 :             :         // NOTE: Currently migration is threshold-based (e.g., understeer > 2.0 = legacy).
    1167                 :             :         //       For more complex migrations, consider adding explicit config_format_version field.
    1168   [ +  -  +  -  :          65 :         file << "ini_version=" << LMUFFB_VERSION << "\n";
                   +  - ]
    1169   [ +  -  +  -  :          65 :         file << "always_on_top=" << m_always_on_top << "\n";
                   +  - ]
    1170   [ +  -  +  -  :          65 :         file << "last_device_guid=" << m_last_device_guid << "\n";
                   +  - ]
    1171   [ +  -  +  -  :          65 :         file << "last_preset_name=" << m_last_preset_name << "\n";
                   +  - ]
    1172   [ +  -  +  -  :          65 :         file << "win_pos_x=" << win_pos_x << "\n";
                   +  - ]
    1173   [ +  -  +  -  :          65 :         file << "win_pos_y=" << win_pos_y << "\n";
                   +  - ]
    1174   [ +  -  +  -  :          65 :         file << "win_w_small=" << win_w_small << "\n";
                   +  - ]
    1175   [ +  -  +  -  :          65 :         file << "win_h_small=" << win_h_small << "\n";
                   +  - ]
    1176   [ +  -  +  -  :          65 :         file << "win_w_large=" << win_w_large << "\n";
                   +  - ]
    1177   [ +  -  +  -  :          65 :         file << "win_h_large=" << win_h_large << "\n";
                   +  - ]
    1178   [ +  -  +  -  :          65 :         file << "show_graphs=" << show_graphs << "\n";
                   +  - ]
    1179   [ +  -  +  -  :          65 :         file << "auto_start_logging=" << m_auto_start_logging << "\n";
                   +  - ]
    1180   [ +  -  +  -  :          65 :         file << "log_path=" << m_log_path << "\n";
                   +  - ]
    1181                 :             : 
    1182         [ +  - ]:          65 :         file << "\n; --- General FFB ---\n";
    1183   [ +  -  +  -  :          65 :         file << "invert_force=" << engine.m_invert_force << "\n";
                   +  - ]
    1184   [ +  -  +  -  :          65 :         file << "gain=" << engine.m_gain << "\n";
                   +  - ]
    1185   [ +  -  +  -  :          65 :         file << "dynamic_normalization_enabled=" << engine.m_dynamic_normalization_enabled << "\n";
                   +  - ]
    1186   [ +  -  +  -  :          65 :         file << "auto_load_normalization_enabled=" << engine.m_auto_load_normalization_enabled << "\n";
                   +  - ]
    1187   [ +  -  +  -  :          65 :         file << "soft_lock_enabled=" << engine.m_soft_lock_enabled << "\n";
                   +  - ]
    1188   [ +  -  +  -  :          65 :         file << "soft_lock_stiffness=" << engine.m_soft_lock_stiffness << "\n";
                   +  - ]
    1189   [ +  -  +  -  :          65 :         file << "soft_lock_damping=" << engine.m_soft_lock_damping << "\n";
                   +  - ]
    1190   [ +  -  +  -  :          65 :         file << "wheelbase_max_nm=" << engine.m_wheelbase_max_nm << "\n";
                   +  - ]
    1191   [ +  -  +  -  :          65 :         file << "target_rim_nm=" << engine.m_target_rim_nm << "\n";
                   +  - ]
    1192   [ +  -  +  -  :          65 :         file << "min_force=" << engine.m_min_force << "\n";
                   +  - ]
    1193                 :             : 
    1194         [ +  - ]:          65 :         file << "\n; --- Front Axle (Understeer) ---\n";
    1195   [ +  -  +  -  :          65 :         file << "steering_shaft_gain=" << engine.m_steering_shaft_gain << "\n";
                   +  - ]
    1196   [ +  -  +  -  :          65 :         file << "ingame_ffb_gain=" << engine.m_ingame_ffb_gain << "\n";
                   +  - ]
    1197   [ +  -  +  -  :          65 :         file << "steering_shaft_smoothing=" << engine.m_steering_shaft_smoothing << "\n";
                   +  - ]
    1198   [ +  -  +  -  :          65 :         file << "understeer=" << engine.m_understeer_effect << "\n";
                   +  - ]
    1199   [ +  -  +  -  :          65 :         file << "torque_source=" << engine.m_torque_source << "\n";
                   +  - ]
    1200   [ +  -  +  -  :          65 :         file << "torque_passthrough=" << engine.m_torque_passthrough << "\n";
                   +  - ]
    1201   [ +  -  +  -  :          65 :         file << "flatspot_suppression=" << engine.m_flatspot_suppression << "\n";
                   +  - ]
    1202   [ +  -  +  -  :          65 :         file << "notch_q=" << engine.m_notch_q << "\n";
                   +  - ]
    1203   [ +  -  +  -  :          65 :         file << "flatspot_strength=" << engine.m_flatspot_strength << "\n";
                   +  - ]
    1204   [ +  -  +  -  :          65 :         file << "static_notch_enabled=" << engine.m_static_notch_enabled << "\n";
                   +  - ]
    1205   [ +  -  +  -  :          65 :         file << "static_notch_freq=" << engine.m_static_notch_freq << "\n";
                   +  - ]
    1206   [ +  -  +  -  :          65 :         file << "static_notch_width=" << engine.m_static_notch_width << "\n";
                   +  - ]
    1207                 :             : 
    1208         [ +  - ]:          65 :         file << "\n; --- Rear Axle (Oversteer) ---\n";
    1209   [ +  -  +  -  :          65 :         file << "oversteer_boost=" << engine.m_oversteer_boost << "\n";
                   +  - ]
    1210   [ +  -  +  -  :          65 :         file << "dynamic_weight_gain=" << engine.m_dynamic_weight_gain << "\n";
                   +  - ]
    1211   [ +  -  +  -  :          65 :         file << "dynamic_weight_smoothing=" << engine.m_dynamic_weight_smoothing << "\n";
                   +  - ]
    1212   [ +  -  +  -  :          65 :         file << "grip_smoothing_steady=" << engine.m_grip_smoothing_steady << "\n";
                   +  - ]
    1213   [ +  -  +  -  :          65 :         file << "grip_smoothing_fast=" << engine.m_grip_smoothing_fast << "\n";
                   +  - ]
    1214   [ +  -  +  -  :          65 :         file << "grip_smoothing_sensitivity=" << engine.m_grip_smoothing_sensitivity << "\n";
                   +  - ]
    1215   [ +  -  +  -  :          65 :         file << "sop=" << engine.m_sop_effect << "\n";
                   +  - ]
    1216   [ +  -  +  -  :          65 :         file << "rear_align_effect=" << engine.m_rear_align_effect << "\n";
                   +  - ]
    1217   [ +  -  +  -  :          65 :         file << "sop_yaw_gain=" << engine.m_sop_yaw_gain << "\n";
                   +  - ]
    1218   [ +  -  +  -  :          65 :         file << "yaw_kick_threshold=" << engine.m_yaw_kick_threshold << "\n";
                   +  - ]
    1219   [ +  -  +  -  :          65 :         file << "yaw_accel_smoothing=" << engine.m_yaw_accel_smoothing << "\n";
                   +  - ]
    1220   [ +  -  +  -  :          65 :         file << "gyro_gain=" << engine.m_gyro_gain << "\n";
                   +  - ]
    1221   [ +  -  +  -  :          65 :         file << "gyro_smoothing_factor=" << engine.m_gyro_smoothing << "\n";
                   +  - ]
    1222   [ +  -  +  -  :          65 :         file << "sop_smoothing_factor=" << engine.m_sop_smoothing_factor << "\n";
                   +  - ]
    1223   [ +  -  +  -  :          65 :         file << "sop_scale=" << engine.m_sop_scale << "\n";
                   +  - ]
    1224   [ +  -  +  -  :          65 :         file << "understeer_affects_sop=" << engine.m_understeer_affects_sop << "\n";
                   +  - ]
    1225                 :             : 
    1226         [ +  - ]:          65 :         file << "\n; --- Physics (Grip & Slip Angle) ---\n";
    1227   [ +  -  +  -  :          65 :         file << "slip_angle_smoothing=" << engine.m_slip_angle_smoothing << "\n";
                   +  - ]
    1228   [ +  -  +  -  :          65 :         file << "chassis_inertia_smoothing=" << engine.m_chassis_inertia_smoothing << "\n";
                   +  - ]
    1229   [ +  -  +  -  :          65 :         file << "optimal_slip_angle=" << engine.m_optimal_slip_angle << "\n";
                   +  - ]
    1230   [ +  -  +  -  :          65 :         file << "optimal_slip_ratio=" << engine.m_optimal_slip_ratio << "\n";
                   +  - ]
    1231   [ +  -  +  -  :          65 :         file << "slope_detection_enabled=" << engine.m_slope_detection_enabled << "\n";
                   +  - ]
    1232   [ +  -  +  -  :          65 :         file << "slope_sg_window=" << engine.m_slope_sg_window << "\n";
                   +  - ]
    1233   [ +  -  +  -  :          65 :         file << "slope_sensitivity=" << engine.m_slope_sensitivity << "\n";
                   +  - ]
    1234                 :             : 
    1235   [ +  -  +  -  :          65 :         file << "slope_smoothing_tau=" << engine.m_slope_smoothing_tau << "\n";
                   +  - ]
    1236   [ +  -  +  -  :          65 :         file << "slope_min_threshold=" << engine.m_slope_min_threshold << "\n";
                   +  - ]
    1237   [ +  -  +  -  :          65 :         file << "slope_max_threshold=" << engine.m_slope_max_threshold << "\n";
                   +  - ]
    1238   [ +  -  +  -  :          65 :         file << "slope_alpha_threshold=" << engine.m_slope_alpha_threshold << "\n";
                   +  - ]
    1239   [ +  -  +  -  :          65 :         file << "slope_decay_rate=" << engine.m_slope_decay_rate << "\n";
                   +  - ]
    1240   [ +  -  +  -  :          65 :         file << "slope_confidence_enabled=" << engine.m_slope_confidence_enabled << "\n";
                   +  - ]
    1241   [ +  -  +  -  :          65 :         file << "slope_g_slew_limit=" << engine.m_slope_g_slew_limit << "\n";
                   +  - ]
    1242   [ +  -  +  -  :          65 :         file << "slope_use_torque=" << (engine.m_slope_use_torque ? "1" : "0") << "\n";
             +  -  +  - ]
    1243   [ +  -  +  -  :          65 :         file << "slope_torque_sensitivity=" << engine.m_slope_torque_sensitivity << "\n";
                   +  - ]
    1244   [ +  -  +  -  :          65 :         file << "slope_confidence_max_rate=" << engine.m_slope_confidence_max_rate << "\n";
                   +  - ]
    1245                 :             : 
    1246         [ +  - ]:          65 :         file << "\n; --- Braking & Lockup ---\n";
    1247   [ +  -  +  -  :          65 :         file << "lockup_enabled=" << engine.m_lockup_enabled << "\n";
                   +  - ]
    1248   [ +  -  +  -  :          65 :         file << "lockup_gain=" << engine.m_lockup_gain << "\n";
                   +  - ]
    1249   [ +  -  +  -  :          65 :         file << "brake_load_cap=" << engine.m_brake_load_cap << "\n";
                   +  - ]
    1250   [ +  -  +  -  :          65 :         file << "lockup_freq_scale=" << engine.m_lockup_freq_scale << "\n";
                   +  - ]
    1251   [ +  -  +  -  :          65 :         file << "lockup_gamma=" << engine.m_lockup_gamma << "\n";
                   +  - ]
    1252   [ +  -  +  -  :          65 :         file << "lockup_start_pct=" << engine.m_lockup_start_pct << "\n";
                   +  - ]
    1253   [ +  -  +  -  :          65 :         file << "lockup_full_pct=" << engine.m_lockup_full_pct << "\n";
                   +  - ]
    1254   [ +  -  +  -  :          65 :         file << "lockup_prediction_sens=" << engine.m_lockup_prediction_sens << "\n";
                   +  - ]
    1255   [ +  -  +  -  :          65 :         file << "lockup_bump_reject=" << engine.m_lockup_bump_reject << "\n";
                   +  - ]
    1256   [ +  -  +  -  :          65 :         file << "lockup_rear_boost=" << engine.m_lockup_rear_boost << "\n";
                   +  - ]
    1257   [ +  -  +  -  :          65 :         file << "abs_pulse_enabled=" << engine.m_abs_pulse_enabled << "\n";
                   +  - ]
    1258   [ +  -  +  -  :          65 :         file << "abs_gain=" << engine.m_abs_gain << "\n";
                   +  - ]
    1259   [ +  -  +  -  :          65 :         file << "abs_freq=" << engine.m_abs_freq_hz << "\n";
                   +  - ]
    1260                 :             : 
    1261         [ +  - ]:          65 :         file << "\n; --- Vibration Effects ---\n";
    1262   [ +  -  +  -  :          65 :         file << "texture_load_cap=" << engine.m_texture_load_cap << "\n";
                   +  - ]
    1263   [ +  -  +  -  :          65 :         file << "slide_enabled=" << engine.m_slide_texture_enabled << "\n";
                   +  - ]
    1264   [ +  -  +  -  :          65 :         file << "slide_gain=" << engine.m_slide_texture_gain << "\n";
                   +  - ]
    1265   [ +  -  +  -  :          65 :         file << "slide_freq=" << engine.m_slide_freq_scale << "\n";
                   +  - ]
    1266   [ +  -  +  -  :          65 :         file << "road_enabled=" << engine.m_road_texture_enabled << "\n";
                   +  - ]
    1267   [ +  -  +  -  :          65 :         file << "road_gain=" << engine.m_road_texture_gain << "\n";
                   +  - ]
    1268   [ +  -  +  -  :          65 :         file << "vibration_gain=" << engine.m_vibration_gain << "\n";
                   +  - ]
    1269   [ +  -  +  -  :          65 :         file << "road_fallback_scale=" << engine.m_road_fallback_scale << "\n";
                   +  - ]
    1270   [ +  -  +  -  :          65 :         file << "spin_enabled=" << engine.m_spin_enabled << "\n";
                   +  - ]
    1271   [ +  -  +  -  :          65 :         file << "spin_gain=" << engine.m_spin_gain << "\n";
                   +  - ]
    1272   [ +  -  +  -  :          65 :         file << "spin_freq_scale=" << engine.m_spin_freq_scale << "\n";
                   +  - ]
    1273   [ +  -  +  -  :          65 :         file << "scrub_drag_gain=" << engine.m_scrub_drag_gain << "\n";
                   +  - ]
    1274   [ +  -  +  -  :          65 :         file << "bottoming_method=" << engine.m_bottoming_method << "\n";
                   +  - ]
    1275   [ +  -  +  -  :          65 :         file << "rest_api_fallback_enabled=" << engine.m_rest_api_enabled << "\n";
                   +  - ]
    1276   [ +  -  +  -  :          65 :         file << "rest_api_port=" << engine.m_rest_api_port << "\n";
                   +  - ]
    1277                 :             : 
    1278         [ +  - ]:          65 :         file << "\n; --- Advanced Settings ---\n";
    1279   [ +  -  +  -  :          65 :         file << "speed_gate_lower=" << engine.m_speed_gate_lower << "\n";
                   +  - ]
    1280   [ +  -  +  -  :          65 :         file << "speed_gate_upper=" << engine.m_speed_gate_upper << "\n";
                   +  - ]
    1281                 :             : 
    1282         [ +  - ]:          65 :         file << "\n[StaticLoads]\n";
    1283                 :             :         {
    1284         [ +  - ]:          65 :             std::lock_guard<std::recursive_mutex> static_lock(m_static_loads_mutex);
    1285         [ +  + ]:         185 :             for (const auto& pair : m_saved_static_loads) {
    1286   [ +  -  +  -  :         120 :                 file << pair.first << "=" << pair.second << "\n";
             +  -  +  - ]
    1287                 :             :             }
    1288                 :          65 :         }
    1289                 :             : 
    1290         [ +  - ]:          65 :         file << "\n[Presets]\n";
    1291         [ +  + ]:         982 :         for (const auto& p : presets) {
    1292         [ +  + ]:         917 :             if (!p.is_builtin) {
    1293   [ +  -  +  -  :          52 :                 file << "[Preset:" << p.name << "]\n";
                   +  - ]
    1294         [ +  - ]:          52 :                 WritePresetFields(file, p);
    1295         [ +  - ]:          52 :                 file << "\n";
    1296                 :             :             }
    1297                 :             :         }
    1298                 :             :         
    1299         [ +  - ]:          65 :         file.close();
    1300                 :             : 
    1301                 :             :     } else {
    1302   [ +  -  +  -  :           2 :         std::cerr << "[Config] Failed to save to " << final_path << std::endl;
                   +  - ]
    1303                 :             :     }
    1304                 :          67 : }
    1305                 :             : 
    1306                 :          64 : void Config::Load(FFBEngine& engine, const std::string& filename) {
    1307         [ +  - ]:          64 :     std::lock_guard<std::recursive_mutex> lock(g_engine_mutex);
    1308   [ +  +  +  - ]:          64 :     std::string final_path = filename.empty() ? m_config_path : filename;
    1309         [ +  - ]:          64 :     std::ifstream file(final_path);
    1310         [ +  + ]:          64 :     if (!file.is_open()) {
    1311   [ +  -  +  - ]:           3 :         std::cout << "[Config] No config found, using defaults." << std::endl;
    1312                 :           3 :         return;
    1313                 :             :     }
    1314                 :             : 
    1315                 :          61 :     std::string line;
    1316                 :          61 :     bool in_static_loads = false;
    1317                 :          61 :     bool in_presets = false;
    1318         [ +  - ]:          61 :     std::string config_version = "";
    1319                 :          61 :     bool legacy_torque_hack = false;
    1320                 :          61 :     float legacy_torque_val = 100.0f;
    1321                 :             : 
    1322   [ +  -  +  -  :        4296 :     while (std::getline(file, line)) {
                   +  + ]
    1323                 :             :         // Strip whitespace and check for section headers
    1324         [ +  - ]:        4235 :         line.erase(0, line.find_first_not_of(" \t\r\n"));
    1325         [ +  - ]:        4235 :         line.erase(line.find_last_not_of(" \t\r\n") + 1);
    1326   [ +  +  +  -  :        4946 :         if (line.empty() || line[0] == ';') continue;
             +  +  +  + ]
    1327                 :             : 
    1328   [ +  -  +  + ]:        3766 :         if (line[0] == '[') {
    1329   [ +  -  +  + ]:          74 :             if (line == "[StaticLoads]") {
    1330                 :          31 :                 in_static_loads = true;
    1331                 :          31 :                 in_presets = false;
    1332   [ +  -  +  +  :          43 :             } else if (line == "[Presets]" || line.rfind("[Preset:", 0) == 0) {
          +  -  +  +  +  
                      + ]
    1333                 :          37 :                 in_presets = true;
    1334                 :          37 :                 in_static_loads = false;
    1335                 :             :             } else {
    1336                 :           6 :                 in_static_loads = false;
    1337                 :           6 :                 in_presets = false;
    1338                 :             :             }
    1339                 :          74 :             continue;
    1340                 :             :         }
    1341                 :             : 
    1342         [ +  + ]:        3692 :         if (in_presets) continue;
    1343                 :             : 
    1344         [ +  - ]:        3095 :         std::istringstream is_line(line);
    1345                 :        3095 :         std::string key;
    1346   [ +  -  +  -  :        3095 :         if (std::getline(is_line, key, '=')) {
                   +  - ]
    1347                 :        3095 :             std::string value;
    1348   [ +  -  +  -  :        3095 :             if (std::getline(is_line, value)) {
                   +  + ]
    1349                 :             :                 try {
    1350         [ +  + ]:        3083 :                     if (in_static_loads) {
    1351   [ +  -  +  - ]:          40 :                         SetSavedStaticLoad(key, std::stod(value));
    1352                 :          40 :                         continue;
    1353                 :             :                     }
    1354                 :             : 
    1355   [ +  -  +  + ]:        3043 :                     if (key == "ini_version") {
    1356                 :             :                         // Config Version Tracking: This field records the app version that last saved the config.
    1357                 :             :                         // It serves as an implicit config format version for migration decisions.
    1358                 :             :                         // Current approach: Threshold-based detection (e.g., understeer > 2.0 = legacy format).
    1359                 :             :                         // Future improvement: Add explicit config_format_version field if migrations become
    1360                 :             :                         // more complex (e.g., structural changes, removed fields, renamed keys).
    1361         [ +  - ]:          32 :                         config_version = value;
    1362   [ +  -  +  -  :          32 :                         std::cout << "[Config] Loading config version: " << config_version << std::endl;
                   +  - ]
    1363                 :             :                     }
    1364   [ +  -  +  +  :        3011 :                     else if (key == "always_on_top") m_always_on_top = std::stoi(value);
                   +  - ]
    1365   [ +  -  +  +  :        2980 :                     else if (key == "last_device_guid") m_last_device_guid = value;
                   +  - ]
    1366   [ +  -  +  +  :        2962 :                     else if (key == "last_preset_name") m_last_preset_name = value;
                   +  - ]
    1367                 :             :                     // Window Geometry (v0.5.5)
    1368   [ +  -  +  +  :        2931 :                     else if (key == "win_pos_x") win_pos_x = std::stoi(value);
                   +  - ]
    1369   [ +  -  +  +  :        2902 :                     else if (key == "win_pos_y") win_pos_y = std::stoi(value);
                   +  - ]
    1370   [ +  -  +  +  :        2873 :                     else if (key == "win_w_small") win_w_small = std::stoi(value);
                   +  - ]
    1371   [ +  -  +  +  :        2844 :                     else if (key == "win_h_small") win_h_small = std::stoi(value);
                   +  - ]
    1372   [ +  -  +  +  :        2815 :                     else if (key == "win_w_large") win_w_large = std::stoi(value);
                   +  - ]
    1373   [ +  -  +  +  :        2786 :                     else if (key == "win_h_large") win_h_large = std::stoi(value);
                   +  - ]
    1374   [ +  -  +  +  :        2757 :                     else if (key == "show_graphs") show_graphs = std::stoi(value);
                   +  - ]
    1375   [ +  -  +  +  :        2727 :                     else if (key == "auto_start_logging") m_auto_start_logging = std::stoi(value);
                   +  - ]
    1376   [ +  -  +  +  :        2697 :                     else if (key == "log_path") m_log_path = value;
                   +  - ]
    1377   [ +  -  +  +  :        2667 :                     else if (key == "invert_force") engine.m_invert_force = std::stoi(value);
                   +  - ]
    1378   [ +  -  +  +  :        2636 :                     else if (key == "gain") engine.m_gain = std::stof(value);
                   +  + ]
    1379   [ +  -  +  +  :        2601 :                     else if (key == "dynamic_normalization_enabled") engine.m_dynamic_normalization_enabled = (value == "1" || value == "true");
          +  -  +  +  +  
                -  -  + ]
    1380   [ +  -  +  +  :        2574 :                     else if (key == "auto_load_normalization_enabled") engine.m_auto_load_normalization_enabled = (value == "1" || value == "true");
          +  -  +  -  +  
                -  -  + ]
    1381   [ +  -  +  +  :        2547 :                     else if (key == "sop_smoothing_factor" || key == "smoothing") engine.m_sop_smoothing_factor = std::stof(value); // "smoothing" is a legacy alias
          +  -  +  +  +  
                +  +  - ]
    1382   [ +  -  +  +  :        2513 :                     else if (key == "sop_scale") engine.m_sop_scale = std::stof(value);
                   +  - ]
    1383   [ +  -  +  +  :        2482 :                     else if (key == "slip_angle_smoothing") engine.m_slip_angle_smoothing = std::stof(value);
                   +  - ]
    1384   [ +  -  +  +  :        2453 :                     else if (key == "texture_load_cap" || key == "max_load_factor") engine.m_texture_load_cap = std::stof(value);
          +  -  +  +  +  
                +  +  - ]
    1385   [ +  -  +  +  :        2418 :                     else if (key == "brake_load_cap") engine.m_brake_load_cap = std::stof(value);
                   +  - ]
    1386   [ +  -  +  +  :        2385 :                     else if (key == "understeer") engine.m_understeer_effect = std::stof(value);
                   +  + ]
    1387   [ +  -  +  +  :        2349 :                     else if (key == "torque_source") engine.m_torque_source = std::stoi(value);
                   +  - ]
    1388   [ +  -  +  +  :        2318 :                     else if (key == "torque_passthrough") engine.m_torque_passthrough = (value == "1" || value == "true");
          +  -  +  +  +  
                -  +  + ]
    1389   [ +  -  +  +  :        2287 :                     else if (key == "sop") engine.m_sop_effect = std::stof(value);
                   +  - ]
    1390   [ +  -  +  +  :        2255 :                     else if (key == "min_force") engine.m_min_force = std::stof(value);
                   +  - ]
    1391   [ +  -  +  +  :        2224 :                     else if (key == "oversteer_boost") engine.m_oversteer_boost = std::stof(value);
                   +  - ]
    1392   [ +  -  +  +  :        2193 :                     else if (key == "dynamic_weight_gain") engine.m_dynamic_weight_gain = std::stof(value);
                   +  - ]
    1393   [ +  -  +  +  :        2163 :                     else if (key == "dynamic_weight_smoothing") engine.m_dynamic_weight_smoothing = std::stof(value);
                   +  - ]
    1394   [ +  -  +  +  :        2133 :                     else if (key == "grip_smoothing_steady") engine.m_grip_smoothing_steady = std::stof(value);
                   +  - ]
    1395   [ +  -  +  +  :        2103 :                     else if (key == "grip_smoothing_fast") engine.m_grip_smoothing_fast = std::stof(value);
                   +  - ]
    1396   [ +  -  +  +  :        2073 :                     else if (key == "grip_smoothing_sensitivity") engine.m_grip_smoothing_sensitivity = std::stof(value);
                   +  - ]
    1397                 :             :                     // v0.4.50: SAFETY CLAMPING for Generator Effects (Gain Compensation Migration)
    1398                 :             :                     // Legacy configs may have high gains (e.g., 5.0) to compensate for lack of auto-scaling.
    1399                 :             :                     // With new decoupling, these would cause 25x force explosions. Clamp to safe maximums.
    1400   [ +  -  +  +  :        2043 :                     else if (key == "lockup_enabled") engine.m_lockup_enabled = std::stoi(value);
                   +  - ]
    1401   [ +  -  +  +  :        2012 :                     else if (key == "lockup_gain") engine.m_lockup_gain = std::stof(value);
                   +  - ]
    1402   [ +  -  +  +  :        1978 :                     else if (key == "lockup_start_pct") engine.m_lockup_start_pct = std::stof(value);
                   +  - ]
    1403   [ +  -  +  +  :        1947 :                     else if (key == "lockup_full_pct") engine.m_lockup_full_pct = std::stof(value);
                   +  - ]
    1404   [ +  -  +  +  :        1916 :                     else if (key == "lockup_rear_boost") engine.m_lockup_rear_boost = std::stof(value);
                   +  - ]
    1405   [ +  -  +  +  :        1885 :                     else if (key == "lockup_gamma") engine.m_lockup_gamma = std::stof(value);
                   +  - ]
    1406   [ +  -  +  +  :        1853 :                     else if (key == "lockup_prediction_sens") engine.m_lockup_prediction_sens = std::stof(value);
                   +  - ]
    1407   [ +  -  +  +  :        1822 :                     else if (key == "lockup_bump_reject") engine.m_lockup_bump_reject = std::stof(value);
                   +  - ]
    1408   [ +  -  +  +  :        1791 :                     else if (key == "abs_pulse_enabled") engine.m_abs_pulse_enabled = std::stoi(value);
                   +  - ]
    1409   [ +  -  +  +  :        1760 :                     else if (key == "abs_gain") engine.m_abs_gain = std::stof(value);
                   +  - ]
    1410   [ +  -  +  +  :        1729 :                     else if (key == "spin_enabled") engine.m_spin_enabled = std::stoi(value);
                   +  - ]
    1411   [ +  -  +  +  :        1698 :                     else if (key == "spin_gain") engine.m_spin_gain = std::stof(value);
                   +  - ]
    1412   [ +  -  +  +  :        1666 :                     else if (key == "slide_enabled") engine.m_slide_texture_enabled = std::stoi(value);
                   +  - ]
    1413   [ +  -  +  +  :        1635 :                     else if (key == "slide_gain") engine.m_slide_texture_gain = std::stof(value);
                   +  - ]
    1414   [ +  -  +  +  :        1603 :                     else if (key == "slide_freq") engine.m_slide_freq_scale = std::stof(value);
                   +  - ]
    1415   [ +  -  +  +  :        1572 :                     else if (key == "road_enabled") engine.m_road_texture_enabled = std::stoi(value);
                   +  - ]
    1416   [ +  -  +  +  :        1541 :                     else if (key == "road_gain") engine.m_road_texture_gain = std::stof(value);
                   +  - ]
    1417   [ +  -  +  +  :        1509 :                     else if (key == "vibration_gain" || key == "tactile_gain") engine.m_vibration_gain = std::stof(value);
          +  -  -  +  +  
                +  +  - ]
    1418   [ +  -  +  +  :        1482 :                     else if (key == "soft_lock_enabled") engine.m_soft_lock_enabled = std::stoi(value);
                   +  - ]
    1419   [ +  -  +  +  :        1451 :                     else if (key == "soft_lock_stiffness") engine.m_soft_lock_stiffness = std::stof(value);
                   +  - ]
    1420   [ +  -  +  +  :        1420 :                     else if (key == "soft_lock_damping") engine.m_soft_lock_damping = std::stof(value);
                   +  - ]
    1421   [ +  -  +  +  :        1389 :                     else if (key == "wheelbase_max_nm") engine.m_wheelbase_max_nm = std::stof(value);
                   +  - ]
    1422   [ +  -  +  +  :        1358 :                     else if (key == "target_rim_nm") engine.m_target_rim_nm = std::stof(value);
                   +  - ]
    1423   [ +  -  +  + ]:        1327 :                     else if (key == "max_torque_ref") {
    1424                 :             :                         // MIGRATION LOGIC (Issue #153)
    1425         [ +  - ]:          12 :                         float old_val = std::stof(value);
    1426         [ +  + ]:          12 :                         if (old_val > 40.0f) {
    1427                 :           6 :                             engine.m_wheelbase_max_nm = 15.0f;
    1428                 :           6 :                             engine.m_target_rim_nm = 10.0f;
    1429                 :           6 :                             legacy_torque_hack = true;
    1430                 :           6 :                             legacy_torque_val = old_val;
    1431                 :             :                         } else {
    1432                 :           6 :                             engine.m_wheelbase_max_nm = old_val;
    1433                 :           6 :                             engine.m_target_rim_nm = old_val;
    1434                 :             :                         }
    1435                 :             :                     }
    1436   [ +  -  +  +  :        1315 :                     else if (key == "abs_freq") engine.m_abs_freq_hz = std::stof(value);
                   +  - ]
    1437   [ +  -  +  +  :        1284 :                     else if (key == "lockup_freq_scale") engine.m_lockup_freq_scale = std::stof(value);
                   +  - ]
    1438   [ +  -  +  +  :        1254 :                     else if (key == "spin_freq_scale") engine.m_spin_freq_scale = std::stof(value);
                   +  - ]
    1439   [ +  -  +  +  :        1223 :                     else if (key == "bottoming_method") engine.m_bottoming_method = std::stoi(value);
                   +  - ]
    1440   [ +  -  +  +  :        1192 :                     else if (key == "scrub_drag_gain") engine.m_scrub_drag_gain = (std::min)(1.0f, std::stof(value));
                   +  - ]
    1441   [ +  -  +  +  :        1160 :                     else if (key == "rear_align_effect") engine.m_rear_align_effect = std::stof(value);
                   +  - ]
    1442   [ +  -  +  +  :        1128 :                     else if (key == "sop_yaw_gain") engine.m_sop_yaw_gain = std::stof(value);
                   +  - ]
    1443   [ +  -  +  +  :        1096 :                     else if (key == "steering_shaft_gain") engine.m_steering_shaft_gain = std::stof(value);
                   +  - ]
    1444   [ +  -  +  +  :        1065 :                     else if (key == "ingame_ffb_gain") engine.m_ingame_ffb_gain = std::stof(value);
                   +  - ]
    1445   [ +  -  +  +  :        1034 :                     else if (key == "gyro_gain") engine.m_gyro_gain = (std::min)(1.0f, std::stof(value));
                   +  - ]
    1446   [ +  -  +  +  :        1002 :                     else if (key == "flatspot_suppression") engine.m_flatspot_suppression = std::stoi(value);
                   +  - ]
    1447   [ +  -  +  +  :         971 :                     else if (key == "notch_q") engine.m_notch_q = std::stof(value);
                   +  - ]
    1448   [ +  -  +  +  :         940 :                     else if (key == "flatspot_strength") engine.m_flatspot_strength = std::stof(value);
                   +  - ]
    1449   [ +  -  +  +  :         909 :                     else if (key == "static_notch_enabled") engine.m_static_notch_enabled = std::stoi(value);
                   +  - ]
    1450   [ +  -  +  +  :         878 :                     else if (key == "static_notch_freq") engine.m_static_notch_freq = std::stof(value);
                   +  - ]
    1451   [ +  -  +  +  :         847 :                     else if (key == "static_notch_width") engine.m_static_notch_width = std::stof(value);
                   +  - ]
    1452   [ +  -  +  +  :         816 :                     else if (key == "yaw_kick_threshold") engine.m_yaw_kick_threshold = std::stof(value);
                   +  - ]
    1453   [ +  -  +  +  :         785 :                     else if (key == "optimal_slip_angle") engine.m_optimal_slip_angle = std::stof(value);
                   +  - ]
    1454   [ +  -  +  +  :         753 :                     else if (key == "optimal_slip_ratio") engine.m_optimal_slip_ratio = std::stof(value);
                   +  - ]
    1455   [ +  -  +  +  :         723 :                     else if (key == "slope_detection_enabled") engine.m_slope_detection_enabled = (value == "1");
                   +  - ]
    1456   [ +  -  +  +  :         691 :                     else if (key == "slope_sg_window") engine.m_slope_sg_window = std::stoi(value);
                   +  - ]
    1457   [ +  -  +  +  :         657 :                     else if (key == "slope_sensitivity") engine.m_slope_sensitivity = std::stof(value);
                   +  - ]
    1458   [ +  -  +  +  :         624 :                     else if (key == "slope_negative_threshold" || key == "slope_min_threshold") engine.m_slope_min_threshold = std::stof(value);
          +  -  +  +  +  
                +  +  - ]
    1459   [ +  -  +  +  :         588 :                     else if (key == "slope_smoothing_tau") engine.m_slope_smoothing_tau = std::stof(value);
                   +  - ]
    1460   [ +  -  +  +  :         558 :                     else if (key == "slope_max_threshold") engine.m_slope_max_threshold = std::stof(value);
                   +  - ]
    1461   [ +  -  +  +  :         525 :                     else if (key == "slope_alpha_threshold") engine.m_slope_alpha_threshold = std::stof(value);
                   +  - ]
    1462   [ +  -  +  +  :         494 :                     else if (key == "slope_decay_rate") engine.m_slope_decay_rate = std::stof(value);
                   +  - ]
    1463   [ +  -  +  +  :         463 :                     else if (key == "slope_confidence_enabled") engine.m_slope_confidence_enabled = (value == "1");
                   +  - ]
    1464   [ +  -  +  +  :         432 :                     else if (key == "steering_shaft_smoothing") engine.m_steering_shaft_smoothing = std::stof(value);
                   +  - ]
    1465   [ +  -  +  +  :         401 :                     else if (key == "gyro_smoothing_factor") engine.m_gyro_smoothing = std::stof(value);
                   +  - ]
    1466   [ +  -  +  +  :         370 :                     else if (key == "yaw_accel_smoothing") engine.m_yaw_accel_smoothing = std::stof(value);
                   +  - ]
    1467   [ +  -  +  +  :         339 :                     else if (key == "chassis_inertia_smoothing") engine.m_chassis_inertia_smoothing = std::stof(value);
                   +  - ]
    1468   [ +  -  +  +  :         308 :                     else if (key == "speed_gate_lower") engine.m_speed_gate_lower = std::stof(value); // NEW v0.6.25
                   +  - ]
    1469   [ +  -  +  +  :         277 :                     else if (key == "speed_gate_upper") engine.m_speed_gate_upper = std::stof(value); // NEW v0.6.25
                   +  - ]
    1470   [ +  -  +  +  :         246 :                     else if (key == "road_fallback_scale") engine.m_road_fallback_scale = std::stof(value); // NEW v0.6.25
                   +  - ]
    1471   [ +  -  +  +  :         215 :                     else if (key == "understeer_affects_sop") engine.m_understeer_affects_sop = std::stoi(value); // NEW v0.6.25
                   +  - ]
    1472   [ +  -  +  +  :         184 :                     else if (key == "slope_g_slew_limit") engine.m_slope_g_slew_limit = std::stof(value); // NEW v0.7.40
                   +  - ]
    1473   [ +  -  +  +  :         153 :                     else if (key == "slope_use_torque") engine.m_slope_use_torque = (value == "1"); // NEW v0.7.40
                   +  - ]
    1474   [ +  -  +  +  :         122 :                     else if (key == "slope_torque_sensitivity") engine.m_slope_torque_sensitivity = std::stof(value); // NEW v0.7.40
                   +  - ]
    1475   [ +  -  +  +  :          91 :                     else if (key == "slope_confidence_max_rate") engine.m_slope_confidence_max_rate = std::stof(value); // NEW v0.7.42
                   +  - ]
    1476   [ +  -  +  +  :          60 :                     else if (key == "rest_api_fallback_enabled") engine.m_rest_api_enabled = (value == "1" || value == "true");
          +  -  +  -  +  
                -  -  + ]
    1477   [ +  -  +  +  :          33 :                     else if (key == "rest_api_port") engine.m_rest_api_port = std::stoi(value);
                   +  - ]
    1478                 :           2 :                 } catch (...) {
    1479   [ +  -  +  -  :           2 :                     std::cerr << "[Config] Error parsing line: " << line << std::endl;
                   +  - ]
    1480         [ +  - ]:           2 :                 }
    1481                 :             :             }
    1482         [ +  + ]:        3095 :         }
    1483   [ +  +  +  + ]:        3135 :     }
    1484                 :             :     
    1485                 :             :     // v0.7.16: Comprehensive Safety Validation & Clamping
    1486                 :             :     // These checks ensure that even if config.ini is manually edited with invalid values,
    1487                 :             :     // the engine remains stable and doesn't crash or produce NaN.
    1488                 :             : 
    1489                 :          61 :     engine.m_gain = (std::max)(0.0f, engine.m_gain);
    1490                 :          61 :     engine.m_wheelbase_max_nm = (std::max)(1.0f, engine.m_wheelbase_max_nm);
    1491                 :          61 :     engine.m_target_rim_nm = (std::max)(1.0f, engine.m_target_rim_nm);
    1492                 :          61 :     engine.m_min_force = (std::max)(0.0f, engine.m_min_force);
    1493                 :          61 :     engine.m_sop_scale = (std::max)(0.01f, engine.m_sop_scale);
    1494                 :          61 :     engine.m_slip_angle_smoothing = (std::max)(0.0001f, engine.m_slip_angle_smoothing);
    1495                 :          61 :     engine.m_notch_q = (std::max)(0.1f, engine.m_notch_q);
    1496                 :          61 :     engine.m_static_notch_width = (std::max)(0.1f, engine.m_static_notch_width);
    1497                 :          61 :     engine.m_speed_gate_upper = (std::max)(0.1f, engine.m_speed_gate_upper);
    1498                 :             : 
    1499                 :          61 :     engine.m_torque_source = (std::max)(0, (std::min)(1, engine.m_torque_source));
    1500                 :             : 
    1501         [ +  + ]:          61 :     if (engine.m_optimal_slip_angle < 0.01f) {
    1502   [ +  -  +  - ]:           4 :         std::cerr << "[Config] Invalid optimal_slip_angle (" << engine.m_optimal_slip_angle 
    1503   [ +  -  +  - ]:           4 :                   << "), resetting to default 0.10" << std::endl;
    1504                 :           4 :         engine.m_optimal_slip_angle = 0.10f;
    1505                 :             :     }
    1506         [ +  + ]:          61 :     if (engine.m_optimal_slip_ratio < 0.01f) {
    1507   [ +  -  +  - ]:           2 :         std::cerr << "[Config] Invalid optimal_slip_ratio (" << engine.m_optimal_slip_ratio 
    1508   [ +  -  +  - ]:           2 :                   << "), resetting to default 0.12" << std::endl;
    1509                 :           2 :         engine.m_optimal_slip_ratio = 0.12f;
    1510                 :             :     }
    1511                 :             :     
    1512                 :             :     // Slope Detection Validation
    1513         [ +  + ]:          61 :     if (engine.m_slope_sg_window < 5) engine.m_slope_sg_window = 5;
    1514         [ -  + ]:          61 :     if (engine.m_slope_sg_window > 41) engine.m_slope_sg_window = 41;
    1515         [ +  + ]:          61 :     if (engine.m_slope_sg_window % 2 == 0) engine.m_slope_sg_window++; // Must be odd
    1516         [ -  + ]:          61 :     if (engine.m_slope_sensitivity < 0.1f) engine.m_slope_sensitivity = 0.1f;
    1517         [ -  + ]:          61 :     if (engine.m_slope_sensitivity > 10.0f) engine.m_slope_sensitivity = 10.0f;
    1518         [ -  + ]:          61 :     if (engine.m_slope_smoothing_tau < 0.001f) engine.m_slope_smoothing_tau = 0.04f;
    1519                 :             :     
    1520   [ +  +  +  + ]:          61 :     if (engine.m_slope_alpha_threshold < 0.001f || engine.m_slope_alpha_threshold > 0.1f) {
    1521   [ +  -  +  - ]:           2 :         std::cerr << "[Config] Invalid slope_alpha_threshold (" << engine.m_slope_alpha_threshold 
    1522   [ +  -  +  - ]:           2 :                   << "), resetting to 0.02f" << std::endl;
    1523                 :           2 :         engine.m_slope_alpha_threshold = 0.02f;
    1524                 :             :     }
    1525   [ +  -  -  + ]:          61 :     if (engine.m_slope_decay_rate < 0.1f || engine.m_slope_decay_rate > 20.0f) {
    1526   [ #  #  #  # ]:           0 :         std::cerr << "[Config] Invalid slope_decay_rate (" << engine.m_slope_decay_rate 
    1527   [ #  #  #  # ]:           0 :                   << "), resetting to 5.0f" << std::endl;
    1528                 :           0 :         engine.m_slope_decay_rate = 5.0f;
    1529                 :             :     }
    1530                 :             : 
    1531                 :             :     // Advanced Slope Validation (v0.7.40)
    1532                 :          61 :     engine.m_slope_g_slew_limit = (std::max)(1.0f, (std::min)(1000.0f, engine.m_slope_g_slew_limit));
    1533                 :          61 :     engine.m_slope_torque_sensitivity = (std::max)(0.01f, (std::min)(10.0f, engine.m_slope_torque_sensitivity));
    1534                 :          61 :     engine.m_slope_confidence_max_rate = (std::max)(engine.m_slope_alpha_threshold + 0.01f, (std::min)(1.0f, engine.m_slope_confidence_max_rate));
    1535                 :             : 
    1536                 :             :     // Migration: v0.7.x sensitivity → v0.7.11 thresholds
    1537                 :             :     // If loading old config with sensitivity but at default thresholds
    1538         [ +  + ]:          61 :     if (engine.m_slope_min_threshold == -0.3f && 
    1539         [ +  - ]:          55 :         engine.m_slope_max_threshold == -2.0f &&
    1540         [ +  + ]:          55 :         engine.m_slope_sensitivity != 0.5f) {
    1541                 :             :         
    1542                 :             :         // Old formula: factor = 1 - (excess * 0.1 * sens)
    1543                 :             :         // At factor=0.2 (floor): excess * 0.1 * sens = 0.8
    1544                 :             :         // excess = 0.8 / (0.1 * sens) = 8 / sens
    1545                 :             :         // max = min - excess = -0.3 - (8/sens)
    1546                 :           3 :         double sens = (double)engine.m_slope_sensitivity;
    1547         [ +  - ]:           3 :         if (sens > 0.01) {
    1548                 :           3 :             engine.m_slope_max_threshold = (float)(engine.m_slope_min_threshold - (8.0 / sens));
    1549   [ +  -  +  - ]:           3 :             std::cout << "[Config] Migrated slope_sensitivity " << sens 
    1550   [ +  -  +  -  :           3 :                       << " to max_threshold " << engine.m_slope_max_threshold << std::endl;
                   +  - ]
    1551                 :             :         }
    1552                 :             :     }
    1553                 :             : 
    1554                 :             :     // Validation: max should be more negative than min
    1555         [ +  + ]:          61 :     if (engine.m_slope_max_threshold > engine.m_slope_min_threshold) {
    1556                 :           2 :         std::swap(engine.m_slope_min_threshold, engine.m_slope_max_threshold);
    1557   [ +  -  +  - ]:           2 :         std::cout << "[Config] Swapped slope thresholds (min should be > max)" << std::endl;
    1558                 :             :     }
    1559                 :             :     
    1560                 :             :     // v0.6.20: Safety Validation - Clamp Advanced Braking Parameters to Valid Ranges
    1561   [ +  +  -  + ]:          61 :     if (engine.m_lockup_gamma < 0.1f || engine.m_lockup_gamma > 4.0f) {
    1562   [ +  -  +  - ]:           1 :         std::cerr << "[Config] Invalid lockup_gamma (" << engine.m_lockup_gamma 
    1563   [ +  -  +  - ]:           1 :                   << "), clamping to range [0.1, 4.0]" << std::endl;
    1564                 :           1 :         engine.m_lockup_gamma = (std::max)(0.1f, (std::min)(4.0f, engine.m_lockup_gamma));
    1565                 :             :     }
    1566   [ +  -  -  + ]:          61 :     if (engine.m_lockup_prediction_sens < 10.0f || engine.m_lockup_prediction_sens > 100.0f) {
    1567   [ #  #  #  # ]:           0 :         std::cerr << "[Config] Invalid lockup_prediction_sens (" << engine.m_lockup_prediction_sens 
    1568   [ #  #  #  # ]:           0 :                   << "), clamping to range [10.0, 100.0]" << std::endl;
    1569                 :           0 :         engine.m_lockup_prediction_sens = (std::max)(10.0f, (std::min)(100.0f, engine.m_lockup_prediction_sens));
    1570                 :             :     }
    1571   [ +  +  -  + ]:          61 :     if (engine.m_lockup_bump_reject < 0.1f || engine.m_lockup_bump_reject > 5.0f) {
    1572   [ +  -  +  - ]:           1 :         std::cerr << "[Config] Invalid lockup_bump_reject (" << engine.m_lockup_bump_reject 
    1573   [ +  -  +  - ]:           1 :                   << "), clamping to range [0.1, 5.0]" << std::endl;
    1574                 :           1 :         engine.m_lockup_bump_reject = (std::max)(0.1f, (std::min)(5.0f, engine.m_lockup_bump_reject));
    1575                 :             :     }
    1576   [ +  -  -  + ]:          61 :     if (engine.m_abs_gain < 0.0f || engine.m_abs_gain > 10.0f) {
    1577   [ #  #  #  # ]:           0 :         std::cerr << "[Config] Invalid abs_gain (" << engine.m_abs_gain 
    1578   [ #  #  #  # ]:           0 :                   << "), clamping to range [0.0, 10.0]" << std::endl;
    1579                 :           0 :         engine.m_abs_gain = (std::max)(0.0f, (std::min)(10.0f, engine.m_abs_gain));
    1580                 :             :     }
    1581                 :             :     // Issue #211: Legacy 100Nm hack scaling
    1582   [ +  +  +  -  :          73 :     if (legacy_torque_hack && IsVersionLessEqual(config_version, "0.7.66")) {
          +  -  +  -  +  
          +  +  +  +  +  
             -  -  -  - ]
    1583                 :           6 :         engine.m_gain *= (15.0f / legacy_torque_val);
    1584   [ +  -  +  - ]:           6 :         std::cout << "[Config] Migrated legacy 100Nm hack for main config. Scaling gain." << std::endl;
    1585                 :           6 :         m_needs_save = true;
    1586                 :             :     }
    1587                 :             : 
    1588                 :             :     // Legacy Migration: Convert 0-200 range to 0-2.0 range
    1589         [ +  + ]:          61 :     if (engine.m_understeer_effect > 2.0f) {
    1590                 :           5 :         float old_val = engine.m_understeer_effect;
    1591                 :           5 :         engine.m_understeer_effect = engine.m_understeer_effect / 100.0f;
    1592   [ +  -  +  - ]:           5 :         std::cout << "[Config] Migrated legacy understeer_effect: " << old_val 
    1593   [ +  -  +  -  :           5 :                   << " -> " << engine.m_understeer_effect << std::endl;
                   +  - ]
    1594                 :             :     }
    1595                 :             :     // Clamp to new valid range [0.0, 2.0]
    1596   [ +  -  -  + ]:          61 :     if (engine.m_understeer_effect < 0.0f || engine.m_understeer_effect > 2.0f) {
    1597                 :           0 :         engine.m_understeer_effect = (std::max)(0.0f, (std::min)(2.0f, engine.m_understeer_effect));
    1598                 :             :     }
    1599   [ +  -  -  + ]:          61 :     if (engine.m_steering_shaft_gain < 0.0f || engine.m_steering_shaft_gain > 2.0f) {
    1600                 :           0 :         engine.m_steering_shaft_gain = (std::max)(0.0f, (std::min)(2.0f, engine.m_steering_shaft_gain));
    1601                 :             :     }
    1602   [ +  -  -  + ]:          61 :     if (engine.m_ingame_ffb_gain < 0.0f || engine.m_ingame_ffb_gain > 2.0f) {
    1603                 :           0 :         engine.m_ingame_ffb_gain = (std::max)(0.0f, (std::min)(2.0f, engine.m_ingame_ffb_gain));
    1604                 :             :     }
    1605   [ +  -  +  + ]:          61 :     if (engine.m_lockup_gain < 0.0f || engine.m_lockup_gain > 3.0f) {
    1606                 :           2 :         engine.m_lockup_gain = (std::max)(0.0f, (std::min)(3.0f, engine.m_lockup_gain));
    1607                 :             :     }
    1608   [ +  +  +  + ]:          61 :     if (engine.m_brake_load_cap < 1.0f || engine.m_brake_load_cap > 10.0f) {
    1609                 :           2 :         engine.m_brake_load_cap = (std::max)(1.0f, (std::min)(10.0f, engine.m_brake_load_cap));
    1610                 :             :     }
    1611   [ +  +  -  + ]:          61 :     if (engine.m_lockup_rear_boost < 1.0f || engine.m_lockup_rear_boost > 10.0f) {
    1612                 :           1 :         engine.m_lockup_rear_boost = (std::max)(1.0f, (std::min)(10.0f, engine.m_lockup_rear_boost));
    1613                 :             :     }
    1614   [ +  -  -  + ]:          61 :     if (engine.m_oversteer_boost < 0.0f || engine.m_oversteer_boost > 4.0f) {
    1615                 :           0 :         engine.m_oversteer_boost = (std::max)(0.0f, (std::min)(4.0f, engine.m_oversteer_boost));
    1616                 :             :     }
    1617   [ +  -  +  + ]:          61 :     if (engine.m_sop_yaw_gain < 0.0f || engine.m_sop_yaw_gain > 1.0f) {
    1618                 :           1 :          engine.m_sop_yaw_gain = (std::max)(0.0f, (std::min)(1.0f, engine.m_sop_yaw_gain));
    1619                 :             :     }
    1620   [ +  -  +  + ]:          61 :     if (engine.m_slide_texture_gain < 0.0f || engine.m_slide_texture_gain > 2.0f) {
    1621                 :           1 :         engine.m_slide_texture_gain = (std::max)(0.0f, (std::min)(2.0f, engine.m_slide_texture_gain));
    1622                 :             :     }
    1623   [ +  -  +  + ]:          61 :     if (engine.m_road_texture_gain < 0.0f || engine.m_road_texture_gain > 2.0f) {
    1624                 :           1 :         engine.m_road_texture_gain = (std::max)(0.0f, (std::min)(2.0f, engine.m_road_texture_gain));
    1625                 :             :     }
    1626   [ +  -  +  + ]:          61 :     if (engine.m_spin_gain < 0.0f || engine.m_spin_gain > 2.0f) {
    1627                 :           1 :         engine.m_spin_gain = (std::max)(0.0f, (std::min)(2.0f, engine.m_spin_gain));
    1628                 :             :     }
    1629   [ +  -  +  + ]:          61 :     if (engine.m_rear_align_effect < 0.0f || engine.m_rear_align_effect > 2.0f) {
    1630                 :           1 :         engine.m_rear_align_effect = (std::max)(0.0f, (std::min)(2.0f, engine.m_rear_align_effect));
    1631                 :             :     }
    1632   [ +  -  +  + ]:          61 :     if (engine.m_sop_effect < 0.0f || engine.m_sop_effect > 2.0f) {
    1633                 :           1 :         engine.m_sop_effect = (std::max)(0.0f, (std::min)(2.0f, engine.m_sop_effect));
    1634                 :             :     }
    1635                 :          61 :     engine.m_soft_lock_stiffness = (std::max)(0.0f, engine.m_soft_lock_stiffness);
    1636                 :          61 :     engine.m_soft_lock_damping = (std::max)(0.0f, engine.m_soft_lock_damping);
    1637                 :          61 :     engine.m_rest_api_port = (std::max)(1, engine.m_rest_api_port);
    1638   [ +  -  +  -  :          61 :     std::cout << "[Config] Loaded from " << filename << std::endl;
                   +  - ]
    1639   [ +  +  +  +  :          70 : }
                   +  + ]
    1640                 :             : 
    1641                 :             : 
    1642                 :             : 
    1643                 :             : 
    1644                 :             : 
    1645                 :             : 
    1646                 :             : 
    1647                 :             : 
    1648                 :             : 
        

Generated by: LCOV version 2.0-1