Add GPS momentum telemetry
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user