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