Branch data Line data Source code
1 : : #include "FFBSafetyMonitor.h"
2 : :
3 : 21 : bool FFBSafetyMonitor::IsFFBAllowed(const VehicleScoringInfoV01& scoring, unsigned char gamePhase) const {
4 : : // 1. Mute if not player vehicle
5 [ + + ]: 21 : if (!scoring.mIsPlayer) return false;
6 : :
7 : : // 2. Mute if not in player control (AI or Garage)
8 [ + + ]: 16 : if (scoring.mControl != 0) return false;
9 : :
10 : : // 3. Mute if in Garage Stall
11 [ + + ]: 13 : if (scoring.mInGarageStall) return false;
12 : :
13 : : // 4. Mute if Disqualified
14 [ + + ]: 9 : if (scoring.mFinishStatus == 3) return false;
15 : :
16 : 6 : return true;
17 : : }
18 : :
19 : 40 : void FFBSafetyMonitor::TriggerSafetyWindow(const char* reason, double now) {
20 [ + + ]: 40 : if (now >= 0.0) {
21 : 23 : m_last_now = now;
22 [ + - ]: 17 : } else if (m_time_ptr) {
23 : 17 : m_last_now = *m_time_ptr;
24 : : }
25 : 40 : double current_now = m_last_now;
26 : :
27 : 40 : bool different_reason = std::strcmp(reason, last_reset_reason) != 0;
28 : 40 : bool timer_expired = safety_timer <= 0.0;
29 : :
30 [ + + + + ]: 40 : if (timer_expired || different_reason) {
31 : : // Always log if the window was inactive or the reason changed
32 : 26 : Logger::Get().LogFile("[Safety] Triggered Safety Window (%.1fs). Reason: %s",
33 : 26 : (double)m_safety_window_duration, reason);
34 : 26 : last_reset_log_time = current_now;
35 : 26 : StringUtils::SafeCopy(last_reset_reason, 64, reason);
36 : : } else {
37 : : // Same reason, apply 1-second throttling to avoid spam
38 [ + + ]: 14 : if (current_now > (last_reset_log_time + 1.0)) {
39 : 2 : Logger::Get().LogFile("[Safety] Triggered Safety Window (%.1fs). Reason: %s (Subsequent)",
40 : 2 : (double)m_safety_window_duration, reason);
41 : 2 : last_reset_log_time = current_now;
42 : : }
43 : : }
44 : :
45 : 40 : safety_timer = (double)m_safety_window_duration;
46 [ + + ]: 40 : if (m_safety_window_duration <= 0.001f) {
47 : 5 : safety_timer = 0.0; // Ensure it's exactly 0 if disabled
48 : : }
49 : :
50 : 40 : safety_is_seeded = false;
51 : 40 : spike_counter = 0;
52 : 40 : }
53 : :
54 : 3297 : double FFBSafetyMonitor::ApplySafetySlew(double target_force, double current_output_force, double dt, bool restricted, double now) {
55 [ + - ]: 3297 : if (now >= 0.0) {
56 : 3297 : m_last_now = now;
57 [ # # ]: 0 : } else if (m_time_ptr) {
58 : 0 : m_last_now = *m_time_ptr;
59 : : }
60 : 3297 : double current_now = m_last_now;
61 : :
62 : : // Check for non-finite inputs
63 [ + + ]: 3297 : if (!std::isfinite(target_force)) return 0.0;
64 [ - + ]: 3292 : if (!std::isfinite(current_output_force)) return 0.0;
65 : :
66 : : // 1. Determine slew limit
67 [ + + ]: 3292 : double slew_limit = restricted ? (double)SAFETY_SLEW_RESTRICTED : (double)SAFETY_SLEW_NORMAL;
68 : :
69 : : // If safety window is active, we apply the user-defined slew limit (Issue #316)
70 [ + + + - ]: 3292 : if (safety_timer > 0.0 && m_safety_slew_full_scale_time_s > 0.01f) {
71 : 11 : slew_limit = 1.0 / (double)m_safety_slew_full_scale_time_s;
72 : : }
73 : :
74 : 3292 : double max_delta = slew_limit * dt;
75 : 3292 : double delta = target_force - current_output_force;
76 : 3292 : double requested_rate = std::abs(delta) / (dt + 1e-9);
77 : :
78 : : // 2. Spike Detection (independent of slew limit)
79 [ + + ]: 3292 : if (requested_rate > (double)m_immediate_spike_threshold) {
80 [ + + ]: 12 : if (current_now > (last_massive_spike_log_time + 5.0)) {
81 [ + - ]: 3 : Logger::Get().LogFile("[Safety] Massive Spike! Rate: %.0f u/s (Limit: %.0f). Triggering Safety.",
82 [ + - ]: 3 : requested_rate, (double)m_immediate_spike_threshold);
83 : 3 : last_massive_spike_log_time = current_now;
84 : : }
85 [ + - ]: 12 : TriggerSafetyWindow("Massive Spike", current_now);
86 [ + + ]: 3280 : } else if (requested_rate > (double)m_spike_detection_threshold) {
87 : 8 : spike_counter++;
88 [ + + ]: 8 : if (spike_counter >= 5) { // 5 consecutive frames of high slew
89 [ + - ]: 1 : if (current_now > (last_high_spike_log_time + 5.0)) {
90 [ + - + - ]: 1 : Logger::Get().LogFile("[Safety] Sustained High Slew (%.0f u/s). Triggering Safety.", requested_rate);
91 : 1 : last_high_spike_log_time = current_now;
92 : : }
93 [ + - ]: 1 : TriggerSafetyWindow("High Spike", current_now);
94 : : }
95 : : } else {
96 : 3272 : spike_counter = (std::max)(0, spike_counter - 1);
97 : : }
98 : :
99 : : // 3. Apply Slew
100 [ + + ]: 3292 : if (std::abs(delta) > max_delta) {
101 [ + - ]: 23 : delta = std::clamp(delta, -max_delta, max_delta);
102 : : }
103 : :
104 : 3292 : return current_output_force + delta;
105 : : }
106 : :
107 : 15836 : double FFBSafetyMonitor::ProcessSafetyMitigation(double norm_force, double dt) {
108 [ + + ]: 15836 : if (safety_timer > 0.0) {
109 : : // Apply extra gain reduction
110 : 817 : norm_force *= (double)m_safety_gain_reduction;
111 : :
112 : : // Apply extra smoothing to the final output to blunt any jitter
113 : : // Using a 200ms EMA (Issue #314)
114 : : // On first frame of safety window, seed the smoothed force
115 [ + + ]: 817 : if (!safety_is_seeded) {
116 : 12 : safety_smoothed_force = norm_force;
117 : 12 : safety_is_seeded = true;
118 : : } else {
119 : 805 : double safety_alpha = dt / ((double)m_safety_smoothing_tau + dt);
120 : 805 : safety_smoothed_force += safety_alpha * (norm_force - safety_smoothed_force);
121 : : }
122 : 817 : norm_force = safety_smoothed_force;
123 : :
124 : 817 : safety_timer -= dt;
125 [ + + ]: 817 : if (safety_timer <= 0.0) {
126 : 1 : safety_timer = 0.0;
127 : 1 : Logger::Get().LogFile("[Safety] Exited Safety Mode");
128 : : }
129 : : }
130 : 15836 : return norm_force;
131 : : }
132 : :
133 : 15836 : void FFBSafetyMonitor::UpdateTockDetection(double steering, double norm_force, double dt) {
134 [ + + + + : 15836 : if (std::abs(steering) > 0.95 && std::abs(norm_force) > 0.8) {
+ + ]
135 : 682 : tock_timer += dt;
136 [ + + ]: 682 : if (tock_timer > 1.0) { // Pinned for 1 second
137 [ + - ]: 2 : double current_now = (m_time_ptr) ? *m_time_ptr : m_last_now;
138 [ + - ]: 2 : if (current_now > (last_tock_log_time + 5.0)) {
139 : 2 : Logger::Get().LogFile("[Safety] Full Tock Detected: Force %.2f at %.1f%% lock",
140 : : norm_force, steering * 100.0);
141 : 2 : last_tock_log_time = current_now;
142 : : }
143 : 2 : tock_timer = 0.0;
144 : : }
145 : : } else {
146 : 15154 : tock_timer = (std::max)(0.0, tock_timer - dt);
147 : : }
148 : 15836 : }
149 : :
150 : 3288 : double FFBSafetyMonitor::ApplySafetySlew(double target_force, double dt, bool restricted) {
151 [ + - ]: 3288 : double now = (m_time_ptr) ? *m_time_ptr : m_last_now;
152 [ - + ]: 3288 : if (!m_time_ptr) m_last_now += dt;
153 : 3288 : safety_smoothed_force = ApplySafetySlew(target_force, safety_smoothed_force, dt, restricted, now);
154 : 3288 : return safety_smoothed_force;
155 : : }
156 : :
|