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