Add GPS momentum telemetry

This commit is contained in:
2026-06-27 03:05:34 -05:00
parent e064a49c37
commit d288b9fa6b
2 changed files with 132 additions and 4 deletions
+23
View File
@@ -2,6 +2,29 @@
Current date/time context: 2026-06-27, local timezone America/Chicago.
## 2026-06-27 Momentum Telemetry Test Installed
- Installed telemetry DLL at about `03:04` local while the game was cold.
Installed size: `16792931` bytes.
- Source flags for the installed test:
`kEnableGpsNodeMultiplierInlinePatch = true`,
`kEnableGpsMomentumPenaltyInlinePatch = true`,
`kEnableGpsQueryResultTraceHooks = true`, and
`kEnableGpsQueryResultRecordDump = false`.
- `solver_weights.bin` is vanilla `1.0`, so the highway multiplier hook should
be neutral. The behavioral change remains the fixed ordinary-edge additive
penalty only: `kGpsMomentumFixedEdgePenalty = 8.0f`.
- New telemetry is a compact `gps-momentum-summary` line emitted once per
completed tracked map-route query. It logs `callsA`, `callsB`, `callsTotal`,
`penaltyTotal`, global call counters, plus light route/result counts. The
counters are incremented directly in the two patched ordinary expansion caves.
- The raw route-record dump is disabled for this run to keep logs readable.
- The installed RED4ext log was truncated to zero before handing the game back.
- Next test routine: launch, load the fixed car save, open the map, plot the
usual sequence with a few seconds between each: side job, Sinnerman, Claire,
custom pin. More destinations are useful only after verifying the summary
lines are present.
## 2026-06-27 Momentum Penalty Test Installed
- The game was cold, so an enabled momentum-penalty test DLL was installed to
+109 -4
View File
@@ -110,6 +110,7 @@ constexpr bool kEnableGpsProviderClassPatchTrace = false;
constexpr bool kEnableGpsTraceHooks = true;
constexpr bool kEnableGpsQueryLifecycleHooks = false;
constexpr bool kEnableGpsQueryResultTraceHooks = true;
constexpr bool kEnableGpsQueryResultRecordDump = false;
constexpr bool kEnableGpsAsyncTrafficProbe = false;
constexpr bool kEnableGpsTrafficEdgeWeightPatch = false;
constexpr bool kEnableGpsSpatialEdgeWeightPatch = false;
@@ -256,6 +257,7 @@ struct GpsMomentumPenaltyPatchSite
{
const char* name;
uintptr_t targetRva;
std::atomic<uint64_t>* counter = nullptr;
std::array<uint8_t, kGpsMomentumRelaxPatchSize> original{};
void* cave = nullptr;
uint8_t* penaltyConstant = nullptr;
@@ -347,9 +349,11 @@ std::array<uint8_t, kGpsNodeMultiplierRoadTailPatchSize> gGpsNodeMultiplierRoadT
void* gGpsNodeMultiplierRoadTailCave = nullptr;
uint8_t* gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
alignas(8) std::atomic<uint64_t> gGpsMomentumPenaltyCallsA{0};
alignas(8) std::atomic<uint64_t> gGpsMomentumPenaltyCallsB{0};
std::array<GpsMomentumPenaltyPatchSite, 2> gGpsMomentumPenaltyPatchSites = {{
{"expand-list-a", kGpsExpandListARelaxPatchRva},
{"expand-list-b", kGpsExpandListBRelaxPatchRva},
{"expand-list-a", kGpsExpandListARelaxPatchRva, &gGpsMomentumPenaltyCallsA},
{"expand-list-b", kGpsExpandListBRelaxPatchRva, &gGpsMomentumPenaltyCallsB},
}};
std::mutex gGpsCostLogMutex;
std::mutex gGpsAsyncLogMutex;
@@ -378,10 +382,13 @@ struct TrackedGpsQuery
uint32_t submitCall = 0;
uint32_t resultFetchCalls = 0;
uint32_t statusCalls = 0;
uint64_t momentumCallsAStart = 0;
uint64_t momentumCallsBStart = 0;
bool momentumSummaryLogged = false;
};
std::mutex gTrackedGpsQueryMutex;
std::array<TrackedGpsQuery, 32> gTrackedGpsQueries{};
std::array<TrackedGpsQuery, 128> gTrackedGpsQueries{};
uint32_t gTrackedGpsQueryNext = 0;
struct GpsEdgeCostSample
@@ -728,6 +735,18 @@ void EmitAbsoluteRaxJump(uint8_t* aCave, size_t& aOffset, uintptr_t aTarget)
aCave[aOffset++] = 0xE0;
}
void EmitAbsoluteLockedIncrement(uint8_t* aCave, size_t& aOffset, uintptr_t aTarget)
{
aCave[aOffset++] = 0x48; // mov rax, imm64
aCave[aOffset++] = 0xB8;
std::memcpy(aCave + aOffset, &aTarget, sizeof(aTarget));
aOffset += sizeof(aTarget);
aCave[aOffset++] = 0xF0; // lock inc qword ptr [rax]
aCave[aOffset++] = 0x48;
aCave[aOffset++] = 0xFF;
aCave[aOffset++] = 0x00;
}
void BuildAbsoluteJumpPatch(uint8_t* aPatch, size_t aPatchSize, uintptr_t aTarget)
{
size_t offset = 0;
@@ -853,6 +872,10 @@ bool InstallGpsMomentumPenaltyPatchSite(GpsMomentumPenaltyPatchSite& aSite,
const auto addPenaltyOffset = offset;
emit({0xF3, 0x0F, 0x58, 0x35, 0x00, 0x00, 0x00, 0x00}); // addss xmm6, [rip+disp32]
if (aSite.counter)
{
EmitAbsoluteLockedIncrement(cave, offset, reinterpret_cast<uintptr_t>(aSite.counter));
}
emit({0xF3, 0x0F, 0x58, 0xC6}); // addss xmm0, xmm6
emit({0x0F, 0x28, 0xDE}); // movaps xmm3, xmm6
@@ -2556,6 +2579,31 @@ void AppendPathBufferSummary(std::ostringstream& aLine, const char* aPrefix, voi
}
}
void AppendGpsResultObjectLightCounts(std::ostringstream& aLine, const char* aPrefix, void* aResultObject)
{
if (!aResultObject)
{
aLine << " " << aPrefix << "_result=<null>";
return;
}
uint32_t routeCapacity = 0;
uint32_t routeCount = 0;
uint32_t pointCount = 0;
auto* routeData = ReadPointerField(aResultObject, 0x28);
auto* pointData = ReadPointerField(aResultObject, 0x38);
TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aResultObject) + 0x30), routeCapacity);
TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aResultObject) + 0x34), routeCount);
TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aResultObject) + 0x3C), pointCount);
AppendPointerWithRva(aLine, aPrefix, aResultObject);
AppendPointerWithRva(aLine, (std::string(aPrefix) + "_routeData").c_str(), routeData);
aLine << " " << aPrefix << "_routeCapacity=" << routeCapacity << " " << aPrefix
<< "_routeCount=" << routeCount;
AppendPointerWithRva(aLine, (std::string(aPrefix) + "_pointData").c_str(), pointData);
aLine << " " << aPrefix << "_pointCount=" << pointCount;
}
void AppendGpsResultElementArraySummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject)
{
uint32_t capacity = 0;
@@ -3105,6 +3153,11 @@ void TrackGpsQuery(uint32_t aQueryId, void* aManager, uintptr_t aSubmitReturnRva
{
query.submitReturnRva = aSubmitReturnRva;
query.submitCall = aSubmitCall;
query.resultFetchCalls = 0;
query.statusCalls = 0;
query.momentumCallsAStart = gGpsMomentumPenaltyCallsA.load(std::memory_order_relaxed);
query.momentumCallsBStart = gGpsMomentumPenaltyCallsB.load(std::memory_order_relaxed);
query.momentumSummaryLogged = false;
return;
}
}
@@ -3117,6 +3170,9 @@ void TrackGpsQuery(uint32_t aQueryId, void* aManager, uintptr_t aSubmitReturnRva
query.submitCall = aSubmitCall;
query.resultFetchCalls = 0;
query.statusCalls = 0;
query.momentumCallsAStart = gGpsMomentumPenaltyCallsA.load(std::memory_order_relaxed);
query.momentumCallsBStart = gGpsMomentumPenaltyCallsB.load(std::memory_order_relaxed);
query.momentumSummaryLogged = false;
}
bool NoteTrackedGpsQueryResultFetch(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot,
@@ -3139,6 +3195,31 @@ bool NoteTrackedGpsQueryResultFetch(uint32_t aQueryId, void* aManager, TrackedGp
return false;
}
bool TryMarkTrackedGpsQueryMomentumSummary(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot,
uint64_t& aCallsA, uint64_t& aCallsB, uint64_t& aGlobalCallsA,
uint64_t& aGlobalCallsB)
{
std::lock_guard lock(gTrackedGpsQueryMutex);
for (auto& query : gTrackedGpsQueries)
{
if (!query.active || query.id != aQueryId || query.manager != aManager || query.momentumSummaryLogged)
{
continue;
}
aGlobalCallsA = gGpsMomentumPenaltyCallsA.load(std::memory_order_relaxed);
aGlobalCallsB = gGpsMomentumPenaltyCallsB.load(std::memory_order_relaxed);
aCallsA = aGlobalCallsA - query.momentumCallsAStart;
aCallsB = aGlobalCallsB - query.momentumCallsBStart;
aSnapshot = query;
query.momentumSummaryLogged = true;
return true;
}
return false;
}
bool NoteTrackedGpsQueryStatus(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot,
uint32_t& aPerQueryCall)
{
@@ -4474,6 +4555,30 @@ bool DetourGpsQueryResultFetch(void* aManager, uint32_t aQueryId, void* aOutPath
(!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2))
: (result && isMapRouteQuery);
if (result && isMapRouteQuery)
{
TrackedGpsQuery summaryQuery{};
uint64_t callsA = 0;
uint64_t callsB = 0;
uint64_t globalCallsA = 0;
uint64_t globalCallsB = 0;
if (TryMarkTrackedGpsQueryMomentumSummary(aQueryId, aManager, summaryQuery, callsA, callsB, globalCallsA,
globalCallsB))
{
const auto totalCalls = callsA + callsB;
const auto fixedPenalty = kGpsMomentumFixedEdgePenalty;
std::ostringstream line;
line << "gps-momentum-summary queryId=" << aQueryId << " resultFetchCall=" << perQueryCall;
AppendTrackedGpsQuery(line, summaryQuery);
line << " callsA=" << callsA << " callsB=" << callsB << " callsTotal=" << totalCalls
<< " fixedPenalty=" << fixedPenalty
<< " penaltyTotal=" << static_cast<double>(totalCalls) * static_cast<double>(fixedPenalty)
<< " globalCallsA=" << globalCallsA << " globalCallsB=" << globalCallsB;
AppendGpsResultObjectLightCounts(line, "gpsResult", aOutPath);
LogRed4ext(line.str());
}
}
if (shouldLog)
{
std::ostringstream line;
@@ -4488,7 +4593,7 @@ bool DetourGpsQueryResultFetch(void* aManager, uint32_t aQueryId, void* aOutPath
AppendPointerWithRva(line, "outPath", aOutPath);
AppendReturnRva(line, returnAddress);
AppendPathBufferSummary(line, "outPath", aOutPath);
if (result && isMapRouteQuery)
if (kEnableGpsQueryResultRecordDump && result && isMapRouteQuery)
{
AppendGpsResultObjectSummary(line, "gpsResult", aOutPath);
}