diff --git a/docs/compaction-handoff.md b/docs/compaction-handoff.md index fcf952c..128c837 100644 --- a/docs/compaction-handoff.md +++ b/docs/compaction-handoff.md @@ -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 diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index c963d3c..c95bdd5 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -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* counter = nullptr; std::array original{}; void* cave = nullptr; uint8_t* penaltyConstant = nullptr; @@ -347,9 +349,11 @@ std::array gGpsNodeMultiplierRoadT void* gGpsNodeMultiplierRoadTailCave = nullptr; uint8_t* gGpsNodeMultiplierRoadTailHighwayConstant = nullptr; bool gGpsNodeMultiplierRoadTailPatchApplied = false; +alignas(8) std::atomic gGpsMomentumPenaltyCallsA{0}; +alignas(8) std::atomic gGpsMomentumPenaltyCallsB{0}; std::array 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 gTrackedGpsQueries{}; +std::array 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(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="; + 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(reinterpret_cast(aResultObject) + 0x30), routeCapacity); + TryReadMemory(reinterpret_cast(reinterpret_cast(aResultObject) + 0x34), routeCount); + TryReadMemory(reinterpret_cast(reinterpret_cast(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(totalCalls) * static_cast(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); }