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