@@ -164,6 +164,9 @@ void PlayerTrace::AddPoint(std::string trace_name, void *player, int slot, bool
164164 camera->GetEyePos <true >(slot, eyepos, angles);
165165 }
166166
167+ this ->EmitLog (Utils::ssprintf (" ProcessMovement(%d)" , session->GetTick ()).c_str ());
168+ this ->EmitLog (Utils::ssprintf (" player @ (%.6f,%.6f,%.6f)" , pos.x , pos.y , pos.z ).c_str ());
169+
167170 HitboxList hitboxes = ConstructHitboxList (pos);
168171
169172 trace.positions [slot].push_back (pos);
@@ -178,6 +181,9 @@ void PlayerTrace::AddPoint(std::string trace_name, void *player, int slot, bool
178181 if (slot == 0 ) {
179182 PortalLocations portals = ConstructPortalLocations ();
180183 trace.portals .push_back (portals);
184+
185+ VphysLocationList vphysLocations = ConstructVphysLocationList ();
186+ trace.vphysLocations .push_back (vphysLocations);
181187 }
182188}
183189Trace *PlayerTrace::GetTrace (std::string trace_name) {
@@ -649,6 +655,49 @@ HitboxList PlayerTrace::ConstructHitboxList(Vector center) const {
649655 return list;
650656}
651657
658+ VphysLocationList PlayerTrace::ConstructVphysLocationList () const {
659+ VphysLocationList locationList;
660+
661+ for (int i = 0 ; i < Offsets::NUM_ENT_ENTRIES; ++i) {
662+ void *ent = server->m_EntPtrArray [i].m_pEntity ;
663+ if (!ent) continue ;
664+ auto className = server->GetEntityClassName (ent);
665+
666+ const char *allowedClassNames[] = {
667+ " player" ,
668+ " prop_physics" ,
669+ " func_physbox" ,
670+ " prop_weighted_cube" ,
671+ " prop_monster_box" ,
672+ " npc_security_camera" ,
673+ " npc_portal_turret_floor" ,
674+ " func_brush" ,
675+ " prop_dynamic" ,
676+ };
677+
678+ int size = sizeof (allowedClassNames) / sizeof (allowedClassNames[0 ]);
679+ bool isAllowedEntity = false ;
680+
681+ for (int i = 0 ; i < size; i++) {
682+ if (strcmp (className, allowedClassNames[i]) == 0 ) {
683+ isAllowedEntity = true ;
684+ break ;
685+ }
686+ }
687+ if (!isAllowedEntity) continue ;
688+
689+ ICollideable *coll = &SE (ent)->collision ();
690+
691+ locationList.locations [i] = {
692+ std::string (className),
693+ coll->GetCollisionOrigin (),
694+ coll->GetCollisionAngles ()
695+ };
696+ }
697+
698+ return locationList;
699+ }
700+
652701PortalLocations PlayerTrace::ConstructPortalLocations () const {
653702 if (!sar_trace_portal_record.GetBool ()) return PortalLocations{};
654703
@@ -1020,3 +1069,161 @@ CON_COMMAND(sar_trace_export, "sar_trace_export <filename> [trace name] - Export
10201069
10211070 console->Print (" Trace successfully exported to '%s'!\n " , filename.c_str ());
10221071}
1072+
1073+ CON_COMMAND (sar_trace_compare, " sar_trace_compare <trace 1> <trace 2> - compares two given recorded traces and shows where differences occurred.\n " ) {
1074+ if (args.ArgC () != 3 )
1075+ return console->Print (sar_trace_compare.ThisPtr ()->m_pszHelpString );
1076+
1077+ auto trace1Name = args[1 ];
1078+ auto trace2Name = args[2 ];
1079+
1080+ auto trace1 = playerTrace->GetTrace (trace1Name);
1081+ auto trace2 = playerTrace->GetTrace (trace2Name);
1082+ if (trace1 == nullptr || trace2 == nullptr ) {
1083+ return console->Print (" Trace with name \" %s\" does not exist.\n " , trace1 == nullptr ? trace1Name : trace2Name);
1084+ }
1085+
1086+ console->Print (" Comparing traces \" %s\" and \" %s\" :\n " , trace1Name, trace2Name);
1087+
1088+ const auto goodColor = Color (110 , 247 , 76 );
1089+ const auto badColor = Color (255 , 100 , 100 );
1090+
1091+ auto startTick1 = tickInternalToUser (0 , *trace1);
1092+ auto startTick2 = tickInternalToUser (0 , *trace2);
1093+ if (startTick1 != startTick2) {
1094+ console->ColorMsg (badColor, " Mismatch in starting tick: %d <-> %d\n " , startTick1, startTick2);
1095+ console->ColorMsg (badColor, " Comparison cancelled.\n " );
1096+ return ;
1097+ }
1098+
1099+ auto vphysList1 = trace1->vphysLocations ;
1100+ auto vphysList2 = trace2->vphysLocations ;
1101+ if (vphysList1.size () != vphysList2.size ()) {
1102+ console->ColorMsg (badColor, " Mismatch in trace length: %d <-> %d\n " , vphysList1.size (), vphysList2.size ());
1103+ return ;
1104+ }
1105+
1106+ int mismatchCount = 0 ;
1107+
1108+ auto maxLength = std::min (vphysList1.size (), vphysList2.size ());
1109+ for (size_t i = 0 ; i < maxLength; ++i) {
1110+
1111+ auto userTick = tickInternalToUser (i, *trace1);
1112+
1113+ auto locationsList1 = vphysList1[i].locations ;
1114+ auto locationsList2 = vphysList2[i].locations ;
1115+
1116+ for (int ent_index = 0 ; ent_index < Offsets::NUM_ENT_ENTRIES; ++ent_index) {
1117+ auto location1Entry = locationsList1.find (ent_index);
1118+ auto location2Entry = locationsList2.find (ent_index);
1119+
1120+ auto location1Valid = (location1Entry != locationsList1.end ());
1121+ auto location2Valid = (location2Entry != locationsList2.end ());
1122+
1123+ if (!location1Valid && !location2Valid) {
1124+ continue ;
1125+ } else if (!location1Valid || !location2Valid) {
1126+ auto entityName = location1Valid ? (*location1Entry).second .className .c_str () : (*location2Entry).second .className .c_str ();
1127+ console->ColorMsg (badColor, " Tick %d Slot %d: entity %s exists in trace \" %s\" , but not in trace \" %s\"\n " ,
1128+ userTick, ent_index, entityName, location1Valid ? trace1Name : trace2Name, location2Valid ? trace1Name : trace2Name);
1129+ mismatchCount++;
1130+ continue ;
1131+ }
1132+
1133+ bool mismatch = false ;
1134+
1135+ auto location1 = (*location1Entry).second ;
1136+ auto location2 = (*location2Entry).second ;
1137+
1138+ if (location1.className != location2.className ) {
1139+ console->ColorMsg (badColor, " Tick %d Slot %d: mismatch in entity types:\n %s <-> %s\n " ,
1140+ userTick, ent_index, location1.className .c_str (), location2.className .c_str ());
1141+ mismatch = true ;
1142+ }
1143+
1144+ if (location1.pos != location2.pos ) {
1145+ console->ColorMsg (badColor, " Tick %d Slot %d: mismatch in position of entity %s:\n (%.9f %.9f %.9f) <-> (%.9f %.9f %.9f)\n " ,
1146+ userTick, ent_index, location1.className .c_str (),
1147+ location1.pos .x , location1.pos .y , location1.pos .z ,
1148+ location2.pos .x , location2.pos .y , location2.pos .z
1149+ );
1150+ mismatch = true ;
1151+ }
1152+
1153+ if (QAngleToVector (location1.ang ) != QAngleToVector (location2.ang )) {
1154+ console->ColorMsg (badColor, " Tick %d Slot %d: mismatch in rotation of entity %s:\n (%.9f %.9f %.9f) <-> (%.9f %.9f %.9f)\n " ,
1155+ userTick, ent_index, location1.className .c_str (),
1156+ location1.ang .x , location1.ang .y , location1.ang .z ,
1157+ location2.ang .x , location2.ang .y , location2.ang .z
1158+ );
1159+ mismatch = true ;
1160+ }
1161+
1162+ if (mismatch) {
1163+ mismatchCount++;
1164+ }
1165+ }
1166+ }
1167+ console->Print (" Checked %d ticks, found mismatches: %d.\n " , maxLength, mismatchCount);
1168+
1169+ if (mismatchCount > 0 ) {
1170+ console->Print (" Mismatch detected: dumping trace logs.\n " );
1171+ std::string dump_1_name = Utils::ssprintf (" %s_dump.txt" , trace1Name);
1172+ FILE *f1 = fopen (dump_1_name.c_str (), " w" );
1173+ if (f1) {
1174+ for (auto line : trace1->log_lines ) {
1175+ fprintf (f1, " %s\n " , line.c_str ());
1176+ }
1177+ fclose (f1);
1178+ console->Print (" Trace '%s' log dumped to '%s'\n " , trace1Name, dump_1_name.c_str ());
1179+ }
1180+ std::string dump_2_name = Utils::ssprintf (" %s_dump.txt" , trace2Name);
1181+ FILE *f2 = fopen (dump_2_name.c_str (), " w" );
1182+ if (f2) {
1183+ for (auto line : trace2->log_lines ) {
1184+ fprintf (f2, " %s\n " , line.c_str ());
1185+ }
1186+ fclose (f2);
1187+ console->Print (" Trace '%s' log dumped to '%s'\n " , trace2Name, dump_2_name.c_str ());
1188+ }
1189+ }
1190+ }
1191+
1192+ void PlayerTrace::EnterLogScope (const char *name) {
1193+ if (!playerTrace->ShouldRecord ()) return ;
1194+ auto trace = this ->GetTrace (sar_trace_record.GetString ());
1195+ if (!trace) return ;
1196+ std::string line = " " ;
1197+ for (unsigned i = 0 ; i < trace->log_scope_stack .size (); ++i) {
1198+ line += " " ;
1199+ }
1200+ line += Utils::ssprintf (" [ENTER %s]" , name);
1201+ trace->log_lines .push_back (line);
1202+ trace->log_scope_stack .push_back ({name});
1203+ }
1204+
1205+ void PlayerTrace::ExitLogScope () {
1206+ if (!playerTrace->ShouldRecord ()) return ;
1207+ auto trace = this ->GetTrace (sar_trace_record.GetString ());
1208+ if (!trace) return ;
1209+ std::string line = " " ;
1210+ if (trace->log_scope_stack .size () == 0 ) return ; // trace probably stopped/started halfway through a log scope
1211+ for (unsigned i = 0 ; i < trace->log_scope_stack .size () - 1 ; ++i) {
1212+ line += " " ;
1213+ }
1214+ line += Utils::ssprintf (" [EXIT %s]" , trace->log_scope_stack .back ().c_str ());
1215+ trace->log_lines .push_back (line);
1216+ trace->log_scope_stack .pop_back ();
1217+ }
1218+
1219+ void PlayerTrace::EmitLog (const char *msg) {
1220+ if (!playerTrace->ShouldRecord ()) return ;
1221+ auto trace = this ->GetTrace (sar_trace_record.GetString ());
1222+ if (!trace) return ;
1223+ std::string line = " " ;
1224+ for (unsigned i = 0 ; i < trace->log_scope_stack .size (); ++i) {
1225+ line += " " ;
1226+ }
1227+ line += msg;
1228+ trace->log_lines .push_back (line);
1229+ }
0 commit comments