LCOV - code coverage report
Current view: top level - physics - VehicleUtils.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 131 131
Test Date: 2026-03-18 19:01:10 Functions: 100.0 % 7 7
Branches: 77.9 % 321 250

             Branch data     Line data    Source code
       1                 :             : #include "VehicleUtils.h"
       2                 :             : #include <algorithm>
       3                 :             : #include <string>
       4                 :             : #include <cctype>
       5                 :             : 
       6                 :             : // Helper: Trim whitespace from a string
       7                 :         921 : static std::string Trim(const std::string& s) {
       8                 :         921 :     auto start = s.find_first_not_of(" \t\n\r");
       9   [ +  +  +  - ]:        1059 :     if (start == std::string::npos) return "";
      10                 :         852 :     auto end = s.find_last_not_of(" \t\n\r");
      11                 :         852 :     return s.substr(start, end - start + 1);
      12                 :             : }
      13                 :             : 
      14                 :             : // Helper: Parse car class from strings (v0.7.44 Refactor)
      15                 :             : // Returns a ParsedVehicleClass enum for internal logic and categorization
      16                 :         449 : ParsedVehicleClass ParseVehicleClass(const char* className, const char* vehicleName) {
      17   [ +  +  +  - ]:         898 :     std::string cls = className ? className : "";
      18   [ +  +  +  - ]:         449 :     std::string name = vehicleName ? vehicleName : "";
      19                 :             : 
      20                 :             :     // Normalize for case-insensitive matching
      21                 :         449 :     std::transform(cls.begin(), cls.end(), cls.begin(), ::toupper);
      22                 :         449 :     std::transform(name.begin(), name.end(), name.begin(), ::toupper);
      23                 :             : 
      24                 :             :     // Issue #368: Trim hidden whitespace that might cause matching failures in some environments
      25         [ +  - ]:         449 :     cls = Trim(cls);
      26         [ +  - ]:         449 :     name = Trim(name);
      27                 :             : 
      28                 :             :     // 1. Primary Identification via Class Name (Hierarchical)
      29   [ +  +  +  + ]:        1266 :     if (cls.find("HYPERCAR") != std::string::npos || cls.find("LMH") != std::string::npos ||
      30   [ +  +  +  -  :        1673 :         cls.find("LMDH") != std::string::npos || cls.find("HYPER") != std::string::npos ||
             -  +  +  + ]
      31                 :         407 :         cls.find("CADILLAC") != std::string::npos) {
      32                 :          42 :         return ParsedVehicleClass::HYPERCAR;
      33                 :             :     }
      34                 :             :     
      35         [ +  + ]:         407 :     if (cls.find("LMP2") != std::string::npos) {
      36   [ +  +  +  +  :          33 :         if (cls.find("ELMS") != std::string::npos || name.find("DERESTRICTED") != std::string::npos) { return ParsedVehicleClass::LMP2_UNRESTRICTED; }
                   +  + ]
      37                 :             :         // Issue #225: "LMP2" in LMU refers to the restricted WEC spec.
      38                 :          21 :         return ParsedVehicleClass::LMP2_RESTRICTED;
      39                 :             :     }
      40                 :             : 
      41         [ +  + ]:         374 :     if (cls.find("LMP3") != std::string::npos) return ParsedVehicleClass::LMP3;
      42         [ +  + ]:         373 :     if (cls.find("GTE") != std::string::npos) return ParsedVehicleClass::GTE;
      43                 :             : 
      44                 :             :     // Issue #368: In LMU GT3-spec cars are in LMGT3 class
      45   [ +  +  +  +  :         352 :     if (cls.find("LMGT3") != std::string::npos || cls.find("GT3") != std::string::npos) return ParsedVehicleClass::LMGT3;
                   +  + ]
      46                 :             : 
      47                 :             :     // 2. Secondary Identification via Vehicle Name Keywords (Fallback)
      48         [ +  + ]:          74 :     if (!name.empty()) {
      49                 :             :         // Hypercars
      50   [ +  +  +  + ]:         207 :         if (name.find("499P") != std::string::npos || name.find("GR010") != std::string::npos ||
      51   [ +  +  +  + ]:         198 :             name.find("963") != std::string::npos || name.find("9X8") != std::string::npos ||
      52   [ +  +  +  + ]:         188 :             name.find("V-SERIES.R") != std::string::npos || name.find("SCG 007") != std::string::npos ||
      53   [ +  +  +  + ]:         180 :             name.find("GLICKENHAUS") != std::string::npos || name.find("VANWALL") != std::string::npos ||
      54   [ +  +  +  + ]:         174 :             name.find("A424") != std::string::npos || name.find("SC63") != std::string::npos ||
      55   [ +  +  +  + ]:         168 :             name.find("VALKYRIE") != std::string::npos || name.find("M HYBRID") != std::string::npos ||
      56   [ +  +  +  +  :         248 :             name.find("TIPO 6") != std::string::npos || name.find("680") != std::string::npos ||
             -  +  +  + ]
      57                 :          53 :             name.find("CADILLAC") != std::string::npos) {
      58                 :          19 :             return ParsedVehicleClass::HYPERCAR;
      59                 :             :         }
      60                 :             :         
      61                 :             :         // LMP2
      62   [ +  +  +  +  :          53 :         if (name.find("ORECA") != std::string::npos || name.find("07") != std::string::npos) {
                   +  + ]
      63                 :           3 :             return ParsedVehicleClass::LMP2_UNSPECIFIED;
      64                 :             :         }
      65                 :             : 
      66                 :             :         // LMP3
      67   [ +  +  +  + ]:         141 :         if (name.find("LIGIER") != std::string::npos || name.find("GINETTA") != std::string::npos ||
      68   [ +  +  +  + ]:         132 :             name.find("DUQUEINE") != std::string::npos || name.find("P320") != std::string::npos ||
      69   [ +  +  +  +  :         180 :             name.find("P325") != std::string::npos || name.find("G61") != std::string::npos ||
             +  +  +  + ]
      70                 :          41 :             name.find("D09") != std::string::npos) {
      71                 :          10 :             return ParsedVehicleClass::LMP3;
      72                 :             :         }
      73                 :             : 
      74                 :             :         // GTE
      75   [ +  +  +  + ]:         117 :         if (name.find("RSR-19") != std::string::npos || name.find("488 GTE") != std::string::npos ||
      76   [ +  +  +  +  :         117 :             name.find("C8.R") != std::string::npos || name.find("VANTAGE AMR") != std::string::npos) {
                   +  + ]
      77                 :           4 :             return ParsedVehicleClass::GTE;
      78                 :             :         }
      79                 :             : 
      80                 :             :         // GT3 / LMGT3 (In LMU all GT3-spec cars are in LMGT3 class)
      81   [ +  +  +  + ]:         103 :         if (name.find("LMGT3") != std::string::npos || name.find("GT3") != std::string::npos ||
      82   [ +  +  +  + ]:          93 :             name.find("HURACAN") != std::string::npos || name.find("RC F") != std::string::npos ||
      83   [ +  +  +  +  :         101 :             name.find("720S") != std::string::npos || name.find("MUSTANG") != std::string::npos) {
                   +  + ]
      84                 :           8 :             return ParsedVehicleClass::LMGT3;
      85                 :             :         }
      86                 :             :     }
      87                 :             : 
      88                 :          30 :     return ParsedVehicleClass::UNKNOWN;
      89                 :         449 : }
      90                 :             : 
      91                 :             : // Lookup table: Map ParsedVehicleClass to Seed Load (Newtons)
      92                 :         272 : double GetDefaultLoadForClass(ParsedVehicleClass vclass) {
      93   [ +  +  +  +  :         272 :     switch (vclass) {
             +  +  +  + ]
      94                 :          28 :         case ParsedVehicleClass::HYPERCAR:         return 9500.0;
      95                 :           7 :         case ParsedVehicleClass::LMP2_UNRESTRICTED: return 8500.0;
      96                 :          12 :         case ParsedVehicleClass::LMP2_RESTRICTED:   return 7500.0;
      97                 :           1 :         case ParsedVehicleClass::LMP2_UNSPECIFIED:  return 8000.0;
      98                 :           3 :         case ParsedVehicleClass::LMP3:             return 5800.0;
      99                 :          13 :         case ParsedVehicleClass::GTE:              return 5500.0;
     100                 :         183 :         case ParsedVehicleClass::LMGT3:            return 5000.0;
     101                 :          25 :         default:                                   return 4500.0;
     102                 :             :     }
     103                 :             : }
     104                 :             : 
     105                 :             : // Helper: String representation of parsed class for logging and UI
     106                 :         139 : const char* VehicleClassToString(ParsedVehicleClass vclass) {
     107   [ +  +  +  +  :         139 :     switch (vclass) {
             +  +  +  + ]
     108                 :          15 :         case ParsedVehicleClass::HYPERCAR:         return "Hypercar";
     109                 :           4 :         case ParsedVehicleClass::LMP2_UNRESTRICTED: return "LMP2 Unrestricted";
     110                 :           7 :         case ParsedVehicleClass::LMP2_RESTRICTED:   return "LMP2 Restricted";
     111                 :           1 :         case ParsedVehicleClass::LMP2_UNSPECIFIED:  return "LMP2 Unspecified";
     112                 :           2 :         case ParsedVehicleClass::LMP3:             return "LMP3";
     113                 :           9 :         case ParsedVehicleClass::GTE:              return "GTE";
     114                 :          94 :         case ParsedVehicleClass::LMGT3:            return "LMGT3";
     115                 :           7 :         default:                                   return "Unknown";
     116                 :             :     }
     117                 :             : }
     118                 :             : 
     119                 :             : // Helper: Parse vehicle brand from strings (v0.7.132)
     120                 :          23 : const char* ParseVehicleBrand(const char* className, const char* vehicleName) {
     121         [ -  + ]:          23 :     if (!vehicleName) return "Unknown";
     122                 :             :     
     123         [ +  - ]:          23 :     std::string name = vehicleName;
     124                 :             :     // Normalize for case-insensitive matching
     125                 :          23 :     std::transform(name.begin(), name.end(), name.begin(), ::toupper);
     126                 :             : 
     127                 :             :     // Issue #368: Trim hidden whitespace
     128         [ +  - ]:          23 :     name = Trim(name);
     129                 :             : 
     130   [ +  +  +  -  :          23 :     if (name.find("FERRARI") != std::string::npos || name.find("499P") != std::string::npos || name.find("488") != std::string::npos || name.find("296") != std::string::npos) return "Ferrari";
          +  -  -  +  +  
                      + ]
     131   [ +  -  -  +  :          22 :     if (name.find("TOYOTA") != std::string::npos || name.find("GR010") != std::string::npos) return "Toyota";
                   -  + ]
     132   [ +  -  +  +  :          56 :     if (name.find("PORSCHE") != std::string::npos || name.find("963") != std::string::npos || name.find("911") != std::string::npos ||
                   +  - ]
     133   [ +  +  +  +  :          68 :         name.find("RSR") != std::string::npos || name.find("992") != std::string::npos || name.find("PROTON") != std::string::npos ||
          +  +  +  +  +  
                      + ]
     134                 :          24 :         name.find("MANTHEY") != std::string::npos) return "Porsche";
     135   [ +  -  -  +  :          10 :     if (name.find("PEUGEOT") != std::string::npos || name.find("9X8") != std::string::npos) return "Peugeot";
                   -  + ]
     136   [ +  -  -  +  :          10 :     if (name.find("CADILLAC") != std::string::npos || name.find("V-SERIES") != std::string::npos) return "Cadillac";
                   -  + ]
     137   [ +  -  +  -  :          10 :     if (name.find("LAMBORGHINI") != std::string::npos || name.find("SC63") != std::string::npos || name.find("HURACAN") != std::string::npos) return "Lamborghini";
             -  +  -  + ]
     138   [ +  -  +  -  :          10 :     if (name.find("BMW") != std::string::npos || name.find("M HYBRID") != std::string::npos || name.find("M4") != std::string::npos) return "BMW";
             -  +  -  + ]
     139   [ +  -  -  +  :          10 :     if (name.find("ALPINE") != std::string::npos || name.find("A424") != std::string::npos) return "Alpine";
                   -  + ]
     140   [ +  -  -  +  :          10 :     if (name.find("ISOTTA") != std::string::npos || name.find("TIPO 6") != std::string::npos) return "Isotta Fraschini";
                   -  + ]
     141   [ +  -  -  +  :          10 :     if (name.find("GLICKENHAUS") != std::string::npos || name.find("SCG") != std::string::npos) return "Glickenhaus";
                   -  + ]
     142   [ +  -  -  +  :          10 :     if (name.find("VANWALL") != std::string::npos || name.find("680") != std::string::npos) return "Vanwall";
                   -  + ]
     143   [ +  +  -  +  :          10 :     if (name.find("ORECA") != std::string::npos || name.find("07") != std::string::npos) return "Oreca";
                   +  + ]
     144   [ +  -  -  +  :           9 :     if (name.find("ASTON") != std::string::npos || name.find("VANTAGE") != std::string::npos) return "Aston Martin";
                   -  + ]
     145   [ +  -  +  -  :           9 :     if (name.find("CORVETTE") != std::string::npos || name.find("C8.R") != std::string::npos || name.find("Z06") != std::string::npos) return "Corvette";
             -  +  -  + ]
     146   [ +  -  -  +  :           9 :     if (name.find("FORD") != std::string::npos || name.find("MUSTANG") != std::string::npos) return "Ford";
                   -  + ]
     147   [ +  -  -  +  :           9 :     if (name.find("MCLAREN") != std::string::npos || name.find("720S") != std::string::npos) return "McLaren";
                   -  + ]
     148   [ +  -  -  +  :           9 :     if (name.find("LEXUS") != std::string::npos || name.find("RC F") != std::string::npos) return "Lexus";
                   -  + ]
     149   [ +  +  +  -  :           9 :     if (name.find("LIGIER") != std::string::npos || name.find("JS P320") != std::string::npos || name.find("P325") != std::string::npos) return "Ligier";
             -  +  +  + ]
     150   [ +  +  +  -  :           8 :     if (name.find("DUQUEINE") != std::string::npos || name.find("D08") != std::string::npos || name.find("D09") != std::string::npos) return "Duqueine";
             -  +  +  + ]
     151   [ +  +  -  +  :           7 :     if (name.find("GINETTA") != std::string::npos || name.find("G61") != std::string::npos) return "Ginetta";
                   +  + ]
     152                 :             : 
     153                 :             :     // Backup: Check Class Name if vehicle name is ambiguous
     154         [ +  - ]:           6 :     if (className) {
     155         [ +  - ]:           6 :         std::string cls = className;
     156                 :           6 :         std::transform(cls.begin(), cls.end(), cls.begin(), ::toupper);
     157         [ -  + ]:           6 :         if (cls.find("FERRARI") != std::string::npos) return "Ferrari";
     158         [ -  + ]:           6 :         if (cls.find("PORSCHE") != std::string::npos) return "Porsche";
     159                 :             :         
     160                 :             :         // Issue #270: LMP2 fallback (almost always Oreca)
     161         [ +  + ]:           6 :         if (cls.find("LMP2") != std::string::npos) return "Oreca";
     162                 :             :         
     163                 :             :         // LMP3 fallbacks
     164         [ +  + ]:           4 :         if (cls.find("GINETTA") != std::string::npos) return "Ginetta";
     165         [ +  + ]:           3 :         if (cls.find("LIGIER") != std::string::npos) return "Ligier";
     166         [ +  + ]:           2 :         if (cls.find("DUQUEINE") != std::string::npos) return "Duqueine";
     167         [ +  + ]:           6 :     }
     168                 :             : 
     169                 :           1 :     return "Unknown";
     170                 :          23 : }
     171                 :             : 
     172                 :             : // Lookup table: Map ParsedVehicleClass to Motion Ratio (Wheel vs Pushrod)
     173                 :       63473 : double GetMotionRatioForClass(ParsedVehicleClass vclass) {
     174      [ +  +  + ]:       63473 :     switch (vclass) {
     175                 :          74 :         case ParsedVehicleClass::HYPERCAR:
     176                 :             :         case ParsedVehicleClass::LMP2_UNRESTRICTED:
     177                 :             :         case ParsedVehicleClass::LMP2_RESTRICTED:
     178                 :             :         case ParsedVehicleClass::LMP2_UNSPECIFIED:
     179                 :             :         case ParsedVehicleClass::LMP3:
     180                 :          74 :             return 0.50; // Prototypes have high motion ratios (pushrod sees ~2x wheel load)
     181                 :       16984 :         case ParsedVehicleClass::GTE:
     182                 :             :         case ParsedVehicleClass::LMGT3:
     183                 :       16984 :             return 0.65; // GT cars have lower motion ratios
     184                 :       46415 :         default:
     185                 :       46415 :             return 0.55; // Default fallback
     186                 :             :     }
     187                 :             : }
     188                 :             : 
     189                 :             : // Lookup table: Map ParsedVehicleClass to Unsprung Weight (Newtons)
     190                 :       63451 : double GetUnsprungWeightForClass(ParsedVehicleClass vclass, bool is_rear) {
     191         [ +  + ]:       63451 :     if (is_rear) {
     192      [ +  +  + ]:        6113 :         switch (vclass) {
     193                 :          24 :             case ParsedVehicleClass::HYPERCAR:
     194                 :             :             case ParsedVehicleClass::LMP2_UNRESTRICTED:
     195                 :             :             case ParsedVehicleClass::LMP2_RESTRICTED:
     196                 :             :             case ParsedVehicleClass::LMP2_UNSPECIFIED:
     197                 :             :             case ParsedVehicleClass::LMP3:
     198                 :          24 :                 return 450.0;
     199                 :        1926 :             case ParsedVehicleClass::GTE:
     200                 :             :             case ParsedVehicleClass::LMGT3:
     201                 :        1926 :                 return 550.0;
     202                 :        4163 :             default:
     203                 :        4163 :                 return 500.0;
     204                 :             :         }
     205                 :             :     } else {
     206      [ +  +  + ]:       57338 :         switch (vclass) {
     207                 :          44 :             case ParsedVehicleClass::HYPERCAR:
     208                 :             :             case ParsedVehicleClass::LMP2_UNRESTRICTED:
     209                 :             :             case ParsedVehicleClass::LMP2_RESTRICTED:
     210                 :             :             case ParsedVehicleClass::LMP2_UNSPECIFIED:
     211                 :             :             case ParsedVehicleClass::LMP3:
     212                 :          44 :                 return 400.0;
     213                 :       15049 :             case ParsedVehicleClass::GTE:
     214                 :             :             case ParsedVehicleClass::LMGT3:
     215                 :       15049 :                 return 500.0;
     216                 :       42245 :             default:
     217                 :       42245 :                 return 450.0;
     218                 :             :         }
     219                 :             :     }
     220                 :             : }
        

Generated by: LCOV version 2.0-1