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