LCOV - code coverage report
Current view: top level - ffb - FFBSafetyMonitor.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.8 % 90 88
Test Date: 2026-03-18 19:01:10 Functions: 100.0 % 6 6
Branches: 78.0 % 82 64

             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                 :             : 
        

Generated by: LCOV version 2.0-1