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