Add GPS momentum telemetry
This commit is contained in:
@@ -2,6 +2,29 @@
|
|||||||
|
|
||||||
Current date/time context: 2026-06-27, local timezone America/Chicago.
|
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
|
## 2026-06-27 Momentum Penalty Test Installed
|
||||||
|
|
||||||
- The game was cold, so an enabled momentum-penalty test DLL was installed to
|
- 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 kEnableGpsTraceHooks = true;
|
||||||
constexpr bool kEnableGpsQueryLifecycleHooks = false;
|
constexpr bool kEnableGpsQueryLifecycleHooks = false;
|
||||||
constexpr bool kEnableGpsQueryResultTraceHooks = true;
|
constexpr bool kEnableGpsQueryResultTraceHooks = true;
|
||||||
|
constexpr bool kEnableGpsQueryResultRecordDump = false;
|
||||||
constexpr bool kEnableGpsAsyncTrafficProbe = false;
|
constexpr bool kEnableGpsAsyncTrafficProbe = false;
|
||||||
constexpr bool kEnableGpsTrafficEdgeWeightPatch = false;
|
constexpr bool kEnableGpsTrafficEdgeWeightPatch = false;
|
||||||
constexpr bool kEnableGpsSpatialEdgeWeightPatch = false;
|
constexpr bool kEnableGpsSpatialEdgeWeightPatch = false;
|
||||||
@@ -256,6 +257,7 @@ struct GpsMomentumPenaltyPatchSite
|
|||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
uintptr_t targetRva;
|
uintptr_t targetRva;
|
||||||
|
std::atomic<uint64_t>* counter = nullptr;
|
||||||
std::array<uint8_t, kGpsMomentumRelaxPatchSize> original{};
|
std::array<uint8_t, kGpsMomentumRelaxPatchSize> original{};
|
||||||
void* cave = nullptr;
|
void* cave = nullptr;
|
||||||
uint8_t* penaltyConstant = nullptr;
|
uint8_t* penaltyConstant = nullptr;
|
||||||
@@ -347,9 +349,11 @@ std::array<uint8_t, kGpsNodeMultiplierRoadTailPatchSize> gGpsNodeMultiplierRoadT
|
|||||||
void* gGpsNodeMultiplierRoadTailCave = nullptr;
|
void* gGpsNodeMultiplierRoadTailCave = nullptr;
|
||||||
uint8_t* gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
|
uint8_t* gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
|
||||||
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
|
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
|
||||||
|
alignas(8) std::atomic<uint64_t> gGpsMomentumPenaltyCallsA{0};
|
||||||
|
alignas(8) std::atomic<uint64_t> gGpsMomentumPenaltyCallsB{0};
|
||||||
std::array<GpsMomentumPenaltyPatchSite, 2> gGpsMomentumPenaltyPatchSites = {{
|
std::array<GpsMomentumPenaltyPatchSite, 2> gGpsMomentumPenaltyPatchSites = {{
|
||||||
{"expand-list-a", kGpsExpandListARelaxPatchRva},
|
{"expand-list-a", kGpsExpandListARelaxPatchRva, &gGpsMomentumPenaltyCallsA},
|
||||||
{"expand-list-b", kGpsExpandListBRelaxPatchRva},
|
{"expand-list-b", kGpsExpandListBRelaxPatchRva, &gGpsMomentumPenaltyCallsB},
|
||||||
}};
|
}};
|
||||||
std::mutex gGpsCostLogMutex;
|
std::mutex gGpsCostLogMutex;
|
||||||
std::mutex gGpsAsyncLogMutex;
|
std::mutex gGpsAsyncLogMutex;
|
||||||
@@ -378,10 +382,13 @@ struct TrackedGpsQuery
|
|||||||
uint32_t submitCall = 0;
|
uint32_t submitCall = 0;
|
||||||
uint32_t resultFetchCalls = 0;
|
uint32_t resultFetchCalls = 0;
|
||||||
uint32_t statusCalls = 0;
|
uint32_t statusCalls = 0;
|
||||||
|
uint64_t momentumCallsAStart = 0;
|
||||||
|
uint64_t momentumCallsBStart = 0;
|
||||||
|
bool momentumSummaryLogged = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::mutex gTrackedGpsQueryMutex;
|
std::mutex gTrackedGpsQueryMutex;
|
||||||
std::array<TrackedGpsQuery, 32> gTrackedGpsQueries{};
|
std::array<TrackedGpsQuery, 128> gTrackedGpsQueries{};
|
||||||
uint32_t gTrackedGpsQueryNext = 0;
|
uint32_t gTrackedGpsQueryNext = 0;
|
||||||
|
|
||||||
struct GpsEdgeCostSample
|
struct GpsEdgeCostSample
|
||||||
@@ -728,6 +735,18 @@ void EmitAbsoluteRaxJump(uint8_t* aCave, size_t& aOffset, uintptr_t aTarget)
|
|||||||
aCave[aOffset++] = 0xE0;
|
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)
|
void BuildAbsoluteJumpPatch(uint8_t* aPatch, size_t aPatchSize, uintptr_t aTarget)
|
||||||
{
|
{
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
@@ -853,6 +872,10 @@ bool InstallGpsMomentumPenaltyPatchSite(GpsMomentumPenaltyPatchSite& aSite,
|
|||||||
|
|
||||||
const auto addPenaltyOffset = offset;
|
const auto addPenaltyOffset = offset;
|
||||||
emit({0xF3, 0x0F, 0x58, 0x35, 0x00, 0x00, 0x00, 0x00}); // addss xmm6, [rip+disp32]
|
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({0xF3, 0x0F, 0x58, 0xC6}); // addss xmm0, xmm6
|
||||||
emit({0x0F, 0x28, 0xDE}); // movaps xmm3, 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)
|
void AppendGpsResultElementArraySummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject)
|
||||||
{
|
{
|
||||||
uint32_t capacity = 0;
|
uint32_t capacity = 0;
|
||||||
@@ -3105,6 +3153,11 @@ void TrackGpsQuery(uint32_t aQueryId, void* aManager, uintptr_t aSubmitReturnRva
|
|||||||
{
|
{
|
||||||
query.submitReturnRva = aSubmitReturnRva;
|
query.submitReturnRva = aSubmitReturnRva;
|
||||||
query.submitCall = aSubmitCall;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3117,6 +3170,9 @@ void TrackGpsQuery(uint32_t aQueryId, void* aManager, uintptr_t aSubmitReturnRva
|
|||||||
query.submitCall = aSubmitCall;
|
query.submitCall = aSubmitCall;
|
||||||
query.resultFetchCalls = 0;
|
query.resultFetchCalls = 0;
|
||||||
query.statusCalls = 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,
|
bool NoteTrackedGpsQueryResultFetch(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot,
|
||||||
@@ -3139,6 +3195,31 @@ bool NoteTrackedGpsQueryResultFetch(uint32_t aQueryId, void* aManager, TrackedGp
|
|||||||
return false;
|
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,
|
bool NoteTrackedGpsQueryStatus(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot,
|
||||||
uint32_t& aPerQueryCall)
|
uint32_t& aPerQueryCall)
|
||||||
{
|
{
|
||||||
@@ -4474,6 +4555,30 @@ bool DetourGpsQueryResultFetch(void* aManager, uint32_t aQueryId, void* aOutPath
|
|||||||
(!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2))
|
(!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2))
|
||||||
: (result && isMapRouteQuery);
|
: (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)
|
if (shouldLog)
|
||||||
{
|
{
|
||||||
std::ostringstream line;
|
std::ostringstream line;
|
||||||
@@ -4488,7 +4593,7 @@ bool DetourGpsQueryResultFetch(void* aManager, uint32_t aQueryId, void* aOutPath
|
|||||||
AppendPointerWithRva(line, "outPath", aOutPath);
|
AppendPointerWithRva(line, "outPath", aOutPath);
|
||||||
AppendReturnRva(line, returnAddress);
|
AppendReturnRva(line, returnAddress);
|
||||||
AppendPathBufferSummary(line, "outPath", aOutPath);
|
AppendPathBufferSummary(line, "outPath", aOutPath);
|
||||||
if (result && isMapRouteQuery)
|
if (kEnableGpsQueryResultRecordDump && result && isMapRouteQuery)
|
||||||
{
|
{
|
||||||
AppendGpsResultObjectSummary(line, "gpsResult", aOutPath);
|
AppendGpsResultObjectSummary(line, "gpsResult", aOutPath);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user