From aa5c599c21b7be23e26d25c9e46fdb94ec489196 Mon Sep 17 00:00:00 2001 From: rudkoLA Date: Wed, 17 Jun 2026 16:52:46 +0300 Subject: [PATCH 1/6] add sar_trace_sync --- docs/cvars.md | 1 + src/Features/PlayerTrace.cpp | 39 ++++++++++++++++++++++++++++++------ src/Features/PlayerTrace.hpp | 1 + 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/docs/cvars.md b/docs/cvars.md index ba91db5ac..4e318157c 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -692,6 +692,7 @@ |sar_trace_record|0|Record the trace to a slot. Set to 0 for not recording| |sar_trace_reveal|0|Only draw traces until the specified tick. Set to bbox to draw until the bbox tick.| |sar_trace_show|cmd|sar_trace_show [trace name] - show the trace with the given name| +|sar_trace_sync|cmd|sar_trace_sync - syncs all the hovered traces to the fastest trace.| |sar_trace_teleport_at|cmd|sar_trace_teleport_at \ [player slot] [trace name] - teleports the player at the given trace tick on the given trace ID (defaults to hovered one or the first one ever made) in the given slot (defaults to 0).| |sar_trace_teleport_eye|cmd|sar_trace_teleport_eye \ [player slot] [trace name] - teleports the player to the eye position at the given trace tick on the given trace (defaults to hovered one or the first one ever made) in the given slot (defaults to 0).| |sar_trace_use_shot_eyeoffset|1|Uses eye offset and angles accurate for portal shooting.| diff --git a/src/Features/PlayerTrace.cpp b/src/Features/PlayerTrace.cpp index a1e108219..7f1355f91 100644 --- a/src/Features/PlayerTrace.cpp +++ b/src/Features/PlayerTrace.cpp @@ -16,6 +16,7 @@ #include "Modules/Surface.hpp" #include +#include PlayerTrace *playerTrace; @@ -60,11 +61,11 @@ static int tickInternalToUser(int tick, const Trace &trace) { if (tick == -1) return -1; switch (sar_trace_draw_time.GetInt()) { case 2: - return tick + trace.startSessionTick; + return tick + trace.startSessionTick + trace.tasTickOffset; case 3: - if (trace.startTasTick > 0) return tick + trace.startTasTick; + if (trace.startTasTick > 0) return tick + trace.startTasTick + trace.tasTickOffset; default: // FALLTHROUGH - return tick; + return tick + trace.tasTickOffset; } } @@ -72,11 +73,11 @@ static int tickUserToInternal(int tick, const Trace &trace) { if (tick == -1) return -1; switch (sar_trace_draw_time.GetInt()) { case 2: - return tick - trace.startSessionTick; + return tick - trace.startSessionTick - trace.tasTickOffset; case 3: - if (trace.startTasTick > 0) return tick - trace.startTasTick; + if (trace.startTasTick > 0) return tick - trace.startTasTick - trace.tasTickOffset; default: // FALLTHROUGH - return tick; + return tick - trace.tasTickOffset; } } @@ -1239,6 +1240,32 @@ CON_COMMAND(sar_trace_compare, "sar_trace_compare - compares } } +CON_COMMAND(sar_trace_sync, "sar_trace_sync - syncs all the hovered traces to the fastest trace.\n") { + std::unordered_map trace_ticks; + + int min_tick = INT_MAX; + std::string fastest_trace_name; + + for (auto &h : hovers) { + int tick = h.tick; + + if (tick < min_tick) { + min_tick = tick; + fastest_trace_name = h.trace_name; + } + + trace_ticks[h.trace_name] = tick; + } + + auto fastest_trace = playerTrace->GetTrace(fastest_trace_name); + + for (const auto& [name, tick] : trace_ticks) { + auto trace = playerTrace->GetTrace(name); + + trace->tasTickOffset = min_tick - tick; + } +} + void PlayerTrace::EnterLogScope(const char *name) { if (!playerTrace->ShouldRecord()) return; auto trace = this->GetTrace(sar_trace_record.GetString()); diff --git a/src/Features/PlayerTrace.hpp b/src/Features/PlayerTrace.hpp index 71ab5e356..254cdf70d 100644 --- a/src/Features/PlayerTrace.hpp +++ b/src/Features/PlayerTrace.hpp @@ -48,6 +48,7 @@ struct PortalLocations { struct Trace { int startSessionTick; int startTasTick; + int tasTickOffset = 0; std::vector positions[2]; std::vector eyepos[2]; std::vector angles[2]; From 8d6a362b8fb0cb92fe1e671b4a17357d57339efd Mon Sep 17 00:00:00 2001 From: rudkoLA Date: Wed, 17 Jun 2026 21:30:18 +0300 Subject: [PATCH 2/6] clean up code --- src/Features/PlayerTrace.cpp | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Features/PlayerTrace.cpp b/src/Features/PlayerTrace.cpp index 7f1355f91..fbd984d33 100644 --- a/src/Features/PlayerTrace.cpp +++ b/src/Features/PlayerTrace.cpp @@ -1241,28 +1241,16 @@ CON_COMMAND(sar_trace_compare, "sar_trace_compare - compares } CON_COMMAND(sar_trace_sync, "sar_trace_sync - syncs all the hovered traces to the fastest trace.\n") { - std::unordered_map trace_ticks; - - int min_tick = INT_MAX; - std::string fastest_trace_name; + size_t min_tick = SIZE_MAX; for (auto &h : hovers) { - int tick = h.tick; - - if (tick < min_tick) { - min_tick = tick; - fastest_trace_name = h.trace_name; - } - - trace_ticks[h.trace_name] = tick; + min_tick = std::min(min_tick, h.tick); } - auto fastest_trace = playerTrace->GetTrace(fastest_trace_name); - - for (const auto& [name, tick] : trace_ticks) { - auto trace = playerTrace->GetTrace(name); + for (auto &h : hovers) { + auto trace = playerTrace->GetTrace(h.trace_name); - trace->tasTickOffset = min_tick - tick; + trace->tasTickOffset = min_tick - h.tick; } } From 299a7f2857e7133272235d7e7ef0e64c470850da Mon Sep 17 00:00:00 2001 From: rudkoLA Date: Thu, 18 Jun 2026 00:11:03 +0300 Subject: [PATCH 3/6] make offset preserve through trace recording via trace name --- src/Features/PlayerTrace.cpp | 16 +++++++++++++++- src/Features/PlayerTrace.hpp | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Features/PlayerTrace.cpp b/src/Features/PlayerTrace.cpp index fbd984d33..010c80398 100644 --- a/src/Features/PlayerTrace.cpp +++ b/src/Features/PlayerTrace.cpp @@ -137,6 +137,12 @@ void PlayerTrace::AddPoint(std::string trace_name, void *player, int slot, bool if (traces.count(trace_name) == 0) { traces[trace_name] = Trace(); traces[trace_name].startSessionTick = session->GetTick(); + + auto it = playerTrace->tickOffsets.find(trace_name); + + if (it != playerTrace->tickOffsets.end()) { + traces[trace_name].tasTickOffset = it->second; + } } Trace &trace = traces[trace_name]; @@ -890,6 +896,10 @@ int PlayerTrace::GetTasTraceTick() { return max_tas_tick; } +void PlayerTrace::SetTickOffset(std::string &trace_name, int offset) { + playerTrace->tickOffsets[trace_name] = offset; +} + HUD_ELEMENT2(trace, "0", "Draws info about current trace bbox tick.\n", HudType_InGame | HudType_Paused) { if (!sv_cheats.GetBool()) return; playerTrace->DrawTraceHud(ctx); @@ -1250,7 +1260,11 @@ CON_COMMAND(sar_trace_sync, "sar_trace_sync - syncs all the hovered traces to th for (auto &h : hovers) { auto trace = playerTrace->GetTrace(h.trace_name); - trace->tasTickOffset = min_tick - h.tick; + auto offset = min_tick - h.tick; + + trace->tasTickOffset = offset; + + playerTrace->SetTickOffset(h.trace_name, offset); } } diff --git a/src/Features/PlayerTrace.hpp b/src/Features/PlayerTrace.hpp index 254cdf70d..4dc9b32f7 100644 --- a/src/Features/PlayerTrace.hpp +++ b/src/Features/PlayerTrace.hpp @@ -70,6 +70,8 @@ class PlayerTrace : public Feature { private: // In order to arbitrarily number traces std::map traces; + // Universal map for trace tick offsets to be able to preserve them between trace recordings + std::unordered_map tickOffsets; std::string lastRecordedTrace; public: PlayerTrace(); @@ -115,6 +117,8 @@ class PlayerTrace : public Feature { void CheckTraceChanged(); // Get the current trace bbox tick for TAS stuff, or -1 if there isn't one int GetTasTraceTick(); + // Set tick offset globally + void SetTickOffset(std::string &trace_name, int offset); // Returns an identifier for the scope which should be passed to ExitLogScope void EnterLogScope(const char *name); From 50983b7b7abcec6d7ad88cc539aac0afbfadfa6d Mon Sep 17 00:00:00 2001 From: rudkoLA Date: Thu, 18 Jun 2026 00:46:46 +0300 Subject: [PATCH 4/6] add trace sync reset commands --- docs/cvars.md | 2 ++ src/Features/PlayerTrace.cpp | 26 ++++++++++++++++++++++++++ src/Features/PlayerTrace.hpp | 2 ++ 3 files changed, 30 insertions(+) diff --git a/docs/cvars.md b/docs/cvars.md index 4e318157c..ddb8bce6a 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -693,6 +693,8 @@ |sar_trace_reveal|0|Only draw traces until the specified tick. Set to bbox to draw until the bbox tick.| |sar_trace_show|cmd|sar_trace_show [trace name] - show the trace with the given name| |sar_trace_sync|cmd|sar_trace_sync - syncs all the hovered traces to the fastest trace.| +|sar_trace_sync_reset|cmd|sar_trace_sync_reset \ - resets the sync of the player trace by the given name.| +|sar_trace_sync_reset_all|cmd|sar_trace_sync_reset_all - resets the sync of all the player traces.| |sar_trace_teleport_at|cmd|sar_trace_teleport_at \ [player slot] [trace name] - teleports the player at the given trace tick on the given trace ID (defaults to hovered one or the first one ever made) in the given slot (defaults to 0).| |sar_trace_teleport_eye|cmd|sar_trace_teleport_eye \ [player slot] [trace name] - teleports the player to the eye position at the given trace tick on the given trace (defaults to hovered one or the first one ever made) in the given slot (defaults to 0).| |sar_trace_use_shot_eyeoffset|1|Uses eye offset and angles accurate for portal shooting.| diff --git a/src/Features/PlayerTrace.cpp b/src/Features/PlayerTrace.cpp index 010c80398..7deb1aa63 100644 --- a/src/Features/PlayerTrace.cpp +++ b/src/Features/PlayerTrace.cpp @@ -900,6 +900,16 @@ void PlayerTrace::SetTickOffset(std::string &trace_name, int offset) { playerTrace->tickOffsets[trace_name] = offset; } +void PlayerTrace::ResetAllTraceOffsets() { + for (auto it = playerTrace->traces.begin(); it != playerTrace->traces.end(); ++it) { + Trace &trace = it->second; + + trace.tasTickOffset = 0; + } + + playerTrace->tickOffsets.clear(); +} + HUD_ELEMENT2(trace, "0", "Draws info about current trace bbox tick.\n", HudType_InGame | HudType_Paused) { if (!sv_cheats.GetBool()) return; playerTrace->DrawTraceHud(ctx); @@ -1268,6 +1278,22 @@ CON_COMMAND(sar_trace_sync, "sar_trace_sync - syncs all the hovered traces to th } } +CON_COMMAND(sar_trace_sync_reset_all, "sar_trace_sync_reset_all - resets the sync of all the player traces.\n") { + playerTrace->ResetAllTraceOffsets(); +} + +CON_COMMAND(sar_trace_sync_reset, "sar_trace_sync_reset - resets the sync of the player trace by the given name.\n") { + if (args.ArgC() < 2) { + return console->Print(sar_trace_sync_reset.ThisPtr()->m_pszHelpString); + } + + std::string trace_name = args[1]; + + playerTrace->GetTrace(trace_name)->tasTickOffset = 0; + + playerTrace->SetTickOffset(trace_name, 0); +} + void PlayerTrace::EnterLogScope(const char *name) { if (!playerTrace->ShouldRecord()) return; auto trace = this->GetTrace(sar_trace_record.GetString()); diff --git a/src/Features/PlayerTrace.hpp b/src/Features/PlayerTrace.hpp index 4dc9b32f7..67cf21bc0 100644 --- a/src/Features/PlayerTrace.hpp +++ b/src/Features/PlayerTrace.hpp @@ -119,6 +119,8 @@ class PlayerTrace : public Feature { int GetTasTraceTick(); // Set tick offset globally void SetTickOffset(std::string &trace_name, int offset); + // Reset all offsets + void ResetAllTraceOffsets(); // Returns an identifier for the scope which should be passed to ExitLogScope void EnterLogScope(const char *name); From c184e7b28b954a0167e1bbb845cd90c78fd4b7e0 Mon Sep 17 00:00:00 2001 From: rudkoLA Date: Thu, 18 Jun 2026 10:25:21 +0300 Subject: [PATCH 5/6] add option for specifying main trace --- docs/cvars.md | 2 +- src/Features/PlayerTrace.cpp | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/cvars.md b/docs/cvars.md index ddb8bce6a..fbd68dc77 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -692,7 +692,7 @@ |sar_trace_record|0|Record the trace to a slot. Set to 0 for not recording| |sar_trace_reveal|0|Only draw traces until the specified tick. Set to bbox to draw until the bbox tick.| |sar_trace_show|cmd|sar_trace_show [trace name] - show the trace with the given name| -|sar_trace_sync|cmd|sar_trace_sync - syncs all the hovered traces to the fastest trace.| +|sar_trace_sync|cmd|sar_trace_sync [main trace] - syncs all the hovered traces to the fastest or given trace.| |sar_trace_sync_reset|cmd|sar_trace_sync_reset \ - resets the sync of the player trace by the given name.| |sar_trace_sync_reset_all|cmd|sar_trace_sync_reset_all - resets the sync of all the player traces.| |sar_trace_teleport_at|cmd|sar_trace_teleport_at \ [player slot] [trace name] - teleports the player at the given trace tick on the given trace ID (defaults to hovered one or the first one ever made) in the given slot (defaults to 0).| diff --git a/src/Features/PlayerTrace.cpp b/src/Features/PlayerTrace.cpp index 7deb1aa63..ae3320e9a 100644 --- a/src/Features/PlayerTrace.cpp +++ b/src/Features/PlayerTrace.cpp @@ -1260,17 +1260,30 @@ CON_COMMAND(sar_trace_compare, "sar_trace_compare - compares } } -CON_COMMAND(sar_trace_sync, "sar_trace_sync - syncs all the hovered traces to the fastest trace.\n") { - size_t min_tick = SIZE_MAX; +CON_COMMAND(sar_trace_sync, "sar_trace_sync [main trace] - syncs all the hovered traces to the fastest or given trace.\n") { + size_t pivot_tick = SIZE_MAX; - for (auto &h : hovers) { - min_tick = std::min(min_tick, h.tick); + if (args.ArgC() == 2) { + for (auto &h : hovers) { + if (args[1] == h.trace_name) { + pivot_tick = h.tick; + break; + } + } + } else { + for (auto &h : hovers) { + pivot_tick = std::min(pivot_tick, h.tick); + } + } + + if (pivot_tick == SIZE_MAX) { + return; } for (auto &h : hovers) { auto trace = playerTrace->GetTrace(h.trace_name); - auto offset = min_tick - h.tick; + auto offset = pivot_tick - h.tick; trace->tasTickOffset = offset; From 60718d9c21e9101e04b38ad5a7c73fc2f4b70261 Mon Sep 17 00:00:00 2001 From: rudkoLA Date: Thu, 18 Jun 2026 18:30:51 +0300 Subject: [PATCH 6/6] add offset command and clean up --- docs/cvars.md | 7 ++- src/Features/PlayerTrace.cpp | 108 ++++++++++++++++++++++++++++------- src/Features/PlayerTrace.hpp | 1 + 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/docs/cvars.md b/docs/cvars.md index fbd68dc77..f58211541 100644 --- a/docs/cvars.md +++ b/docs/cvars.md @@ -684,6 +684,10 @@ |sar_trace_export|cmd|sar_trace_export \ [trace name] - Export trace data into a csv file.| |sar_trace_font_size|3.0|The size of text overlaid on recorded traces.| |sar_trace_hide|cmd|sar_trace_hide [trace name] - hide the trace with the given name| +|sar_trace_offset|cmd|sar_trace_offset \ \ - sets a tick offset for given trace.| +|sar_trace_offset_clear|cmd|sar_trace_offset_clear - clears the offset of all the player traces.| +|sar_trace_offset_sync|cmd|sar_trace_offset_sync - syncs all the hovered traces to the fastest trace.| +|sar_trace_offset_sync_to|cmd|sar_trace_offset_sync_to [main trace] - syncs all the hovered traces to the given trace.| |sar_trace_override|1|Clears old trace when you start recording to it instead of recording on top of it.| |sar_trace_playback_rate|0|Playback rate of the trace bbox. Loops upon finishing.| |sar_trace_portal_opacity|100|Opacity of trace portal previews.| @@ -692,9 +696,6 @@ |sar_trace_record|0|Record the trace to a slot. Set to 0 for not recording| |sar_trace_reveal|0|Only draw traces until the specified tick. Set to bbox to draw until the bbox tick.| |sar_trace_show|cmd|sar_trace_show [trace name] - show the trace with the given name| -|sar_trace_sync|cmd|sar_trace_sync [main trace] - syncs all the hovered traces to the fastest or given trace.| -|sar_trace_sync_reset|cmd|sar_trace_sync_reset \ - resets the sync of the player trace by the given name.| -|sar_trace_sync_reset_all|cmd|sar_trace_sync_reset_all - resets the sync of all the player traces.| |sar_trace_teleport_at|cmd|sar_trace_teleport_at \ [player slot] [trace name] - teleports the player at the given trace tick on the given trace ID (defaults to hovered one or the first one ever made) in the given slot (defaults to 0).| |sar_trace_teleport_eye|cmd|sar_trace_teleport_eye \ [player slot] [trace name] - teleports the player to the eye position at the given trace tick on the given trace (defaults to hovered one or the first one ever made) in the given slot (defaults to 0).| |sar_trace_use_shot_eyeoffset|1|Uses eye offset and angles accurate for portal shooting.| diff --git a/src/Features/PlayerTrace.cpp b/src/Features/PlayerTrace.cpp index ae3320e9a..32b08eacb 100644 --- a/src/Features/PlayerTrace.cpp +++ b/src/Features/PlayerTrace.cpp @@ -142,6 +142,8 @@ void PlayerTrace::AddPoint(std::string trace_name, void *player, int slot, bool if (it != playerTrace->tickOffsets.end()) { traces[trace_name].tasTickOffset = it->second; + } else { + playerTrace->tickOffsets[trace_name] = 0; } } @@ -910,6 +912,20 @@ void PlayerTrace::ResetAllTraceOffsets() { playerTrace->tickOffsets.clear(); } +std::vector PlayerTrace::GetOffsetAutoComplete() { + std::vector completions{}; + + for (auto it = playerTrace->tickOffsets.begin(); it != playerTrace->tickOffsets.end(); ++it) { + auto trace_name = it->first; + + auto offsets = it->second; + + completions.push_back(trace_name + " " + std::to_string(offsets)); + } + + return completions; +} + HUD_ELEMENT2(trace, "0", "Draws info about current trace bbox tick.\n", HudType_InGame | HudType_Paused) { if (!sv_cheats.GetBool()) return; playerTrace->DrawTraceHud(ctx); @@ -1260,20 +1276,53 @@ CON_COMMAND(sar_trace_compare, "sar_trace_compare - compares } } -CON_COMMAND(sar_trace_sync, "sar_trace_sync [main trace] - syncs all the hovered traces to the fastest or given trace.\n") { +DECL_COMMAND_COMPLETION(sar_trace_offset) { + std::vector completions = playerTrace->GetOffsetAutoComplete(); + + for (auto& comp : completions) { + if (items.size() == COMMAND_COMPLETION_MAXITEMS) { + break; + } + + if (std::strlen(match) != std::strlen(cmd)) { + if (std::strstr(comp.c_str(), match)) { + items.push_back(comp); + } + } else { + items.push_back(comp); + } + } + + FINISH_COMMAND_COMPLETION(); +} + +CON_COMMAND_F_COMPLETION( + sar_trace_offset, + "sar_trace_offset - sets a tick offset for given trace.\n", + FCVAR_NONE, + AUTOCOMPLETION_FUNCTION(sar_trace_offset)) { + if (args.ArgC() < 3) { + return console->Print(sar_trace_offset.ThisPtr()->m_pszHelpString); + } + + std::string trace_name = args[1]; + auto offset = atoi(args[2]); + + auto trace = playerTrace->GetTrace(trace_name); + + if (trace) { + trace->tasTickOffset = offset; + } + + playerTrace->SetTickOffset(trace_name, offset); +} + +CON_COMMAND(sar_trace_offset_sync, "sar_trace_offset_sync - syncs all the hovered traces to the fastest trace.\n") { size_t pivot_tick = SIZE_MAX; - if (args.ArgC() == 2) { - for (auto &h : hovers) { - if (args[1] == h.trace_name) { - pivot_tick = h.tick; - break; - } - } - } else { - for (auto &h : hovers) { - pivot_tick = std::min(pivot_tick, h.tick); - } + + for (auto &h : hovers) { + pivot_tick = std::min(pivot_tick, h.tick); } if (pivot_tick == SIZE_MAX) { @@ -1291,20 +1340,37 @@ CON_COMMAND(sar_trace_sync, "sar_trace_sync [main trace] - syncs all the hovered } } -CON_COMMAND(sar_trace_sync_reset_all, "sar_trace_sync_reset_all - resets the sync of all the player traces.\n") { - playerTrace->ResetAllTraceOffsets(); -} - -CON_COMMAND(sar_trace_sync_reset, "sar_trace_sync_reset - resets the sync of the player trace by the given name.\n") { +CON_COMMAND(sar_trace_offset_sync_to, "sar_trace_offset_sync_to [main trace] - syncs all the hovered traces to the given trace.\n") { if (args.ArgC() < 2) { - return console->Print(sar_trace_sync_reset.ThisPtr()->m_pszHelpString); + return console->Print(sar_trace_offset_sync_to.ThisPtr()->m_pszHelpString); } + + size_t pivot_tick = SIZE_MAX; - std::string trace_name = args[1]; + for (auto &h : hovers) { + if (args[1] == h.trace_name) { + pivot_tick = h.tick; + break; + } + } - playerTrace->GetTrace(trace_name)->tasTickOffset = 0; + if (pivot_tick == SIZE_MAX) { + return; + } + + for (auto &h : hovers) { + auto trace = playerTrace->GetTrace(h.trace_name); + + auto offset = pivot_tick - h.tick; - playerTrace->SetTickOffset(trace_name, 0); + trace->tasTickOffset = offset; + + playerTrace->SetTickOffset(h.trace_name, offset); + } +} + +CON_COMMAND(sar_trace_offset_clear, "sar_trace_offset_clear - clears the offset of all the player traces.\n") { + playerTrace->ResetAllTraceOffsets(); } void PlayerTrace::EnterLogScope(const char *name) { diff --git a/src/Features/PlayerTrace.hpp b/src/Features/PlayerTrace.hpp index 67cf21bc0..3f1186a6e 100644 --- a/src/Features/PlayerTrace.hpp +++ b/src/Features/PlayerTrace.hpp @@ -121,6 +121,7 @@ class PlayerTrace : public Feature { void SetTickOffset(std::string &trace_name, int offset); // Reset all offsets void ResetAllTraceOffsets(); + std::vector GetOffsetAutoComplete(); // Returns an identifier for the scope which should be passed to ExitLogScope void EnterLogScope(const char *name);