Trace GPS resolver during map queries
This commit is contained in:
+69
-14
@@ -4,12 +4,13 @@ Current date/time context: 2026-06-20/21, local timezone America/Chicago.
|
||||
|
||||
## Current Installed State
|
||||
|
||||
- Latest installed probe commit: `54f014b Trace full GPS query results`.
|
||||
- Latest installed probe source: resolver-window probe. Use `git log -1
|
||||
--oneline` for the exact checkpoint.
|
||||
- Installed RED4ext plugin:
|
||||
`Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll`.
|
||||
- Installed build is read-only. It should not alter route behavior.
|
||||
- Latest archived test log:
|
||||
`logs/EdgeWeightGPS_query_route_probe_2308.log`.
|
||||
`logs/EdgeWeightGPS_resolve_handle_probe_4650.log`.
|
||||
- Game is cold as of the latest user message.
|
||||
|
||||
## New Toolbox Tools
|
||||
@@ -129,15 +130,23 @@ Packed-handle resolver:
|
||||
- It resolves the handle into a 0x68-byte segment record and a 0x14-byte point
|
||||
record. The point record's `+0x13 & 0x3f` is the search-side class consumed by
|
||||
`0x44f838`.
|
||||
- The current probe logs selected resolver calls at return RVAs `0x44d26c`,
|
||||
`0x44d304`, `0x44d99f`, `0x44d9cd`, `0x44db50`, `0x44db88`, and `0x44dd92`,
|
||||
plus the first 64 successful calls as a guard against a bad static address
|
||||
assumption. Logs are capped at 512 calls.
|
||||
- Live logging corrected the selected post-call return RVAs to:
|
||||
- `0x44cb9e`: 0x38 intermediate-record builder. It writes packed handle,
|
||||
`point+0x10` mask, `point+0x13` class, and coordinates from
|
||||
`segment+0x18`.
|
||||
- `0x44dd97`: geometry/materialization path. It walks the `segment+0x28`,
|
||||
`segment+0x30`, and `segment+0x38` arrays for the resolved point.
|
||||
- The current probe opens a five-second resolver trace window before the
|
||||
original `0x70a42c` submitter runs when the caller is `0x8d20d4` and
|
||||
`query_fcc=5`. Inside that window it logs selected resolver callsites with
|
||||
per-return-site caps instead of spending the whole budget on startup noise.
|
||||
- Each resolver line includes the decoded handle, segment/point pointers,
|
||||
segment pointer fields `+0x08` through `+0x38`, raw segment bytes, and raw
|
||||
point bytes. The goal is to determine whether the selected-path segment still
|
||||
carries, or points to, the traffic lane hash/resource flags before the final
|
||||
0x28 route records are emitted.
|
||||
segment pointer fields `+0x08` through `+0x38`, raw segment bytes, raw point
|
||||
bytes, raw `segment+0x08` metadata, the `segment+0x28` point-index record,
|
||||
and small raw dumps from `segment+0x20`, `+0x30`, and `+0x38`. The goal is to
|
||||
determine whether the selected-path segment still carries, or points to, the
|
||||
traffic lane hash/resource flags before the final 0x28 route records are
|
||||
emitted.
|
||||
|
||||
## Latest 23:08 Test Result
|
||||
|
||||
@@ -202,14 +211,60 @@ Inference:
|
||||
highway-heavy. Therefore final route-record `u08` low byte and search point
|
||||
`point+0x13` class are different domains.
|
||||
|
||||
## Latest 46:50 Resolver Test Result
|
||||
|
||||
Archived log:
|
||||
`logs/EdgeWeightGPS_resolve_handle_probe_4650.log`.
|
||||
|
||||
User timing:
|
||||
|
||||
- 46:50 Continue on main menu
|
||||
- 47:23 Space to continue
|
||||
- 47:35 map open
|
||||
- 47:43 side job
|
||||
- 47:51 Sinnerman
|
||||
- 47:57 Claire
|
||||
- 48:09 custom
|
||||
- 48:2x Aldecaldo camp
|
||||
- 48:57 game cold / `OnExit`
|
||||
|
||||
Route summary:
|
||||
|
||||
- routes=6, records=170, matched route records to lane hashes=170/170.
|
||||
- qid 1: startup/minimap, 18 records, 43 ms, 1216.5 pavement.
|
||||
- qid 2: side job, 12 records, 34 ms, 578.6 pavement.
|
||||
- qid 3: Sinnerman, 23 records, 45 ms, 897.4 pavement, 160.4 highway,
|
||||
89.8 road.
|
||||
- qid 4: Claire, 18 records, 35 ms, 1216.5 pavement.
|
||||
- qid 5: custom, 19 records, 45 ms, 1006.3 pavement, 343.4 highway,
|
||||
38.0 road.
|
||||
- qid 6: Aldecaldo, 80 records, 340 ms, 5853.5 road, 1492.9 highway,
|
||||
1111.5 pavement, 24.6 gpsonly.
|
||||
|
||||
Resolver sample findings:
|
||||
|
||||
- The first resolver probe captured only the startup guard window because the
|
||||
guessed return RVAs were post-call-off by a few bytes.
|
||||
- It still proved `GPSResolveHandle 0x44e1a8` attached successfully.
|
||||
- Actual resolver returns in the first 64 logged samples:
|
||||
`0x44dd97:57`, `0x44cb9e:7`.
|
||||
- Resolver point classes in those samples: `4:60`, `5:4`.
|
||||
- The 0x68 segment header contains generation/counts/pointers; final
|
||||
`nodeRefHash` values are not inline in those 0x68 bytes.
|
||||
- Static disassembly shows `segment+0x10` is the 20-byte point array,
|
||||
`segment+0x18` is the coordinate array, and the materializer indexes
|
||||
`segment+0x28` by `pointIndex*12`.
|
||||
|
||||
## Next Work
|
||||
|
||||
- Install the resolver probe and run the usual map route routine.
|
||||
- Archive the plugin log as `logs/EdgeWeightGPS_resolve_handle_probe_<time>.log`.
|
||||
- Compare `GPSResolveHandle` handles/segment bytes against the final
|
||||
- Install the updated resolver-window probe and run the usual map route
|
||||
routine.
|
||||
- Archive the plugin log as
|
||||
`logs/EdgeWeightGPS_resolve_window_probe_<time>.log`.
|
||||
- Compare `GPSResolveHandle` pointer-target dumps against the final
|
||||
`GPSQueryResultFetch` route-record `h00` values joined to
|
||||
`work/raw-segment-json/all.traffic_persistent.json`.
|
||||
- If the resolver's 0x68 segment record or its pointer fields expose
|
||||
- If the resolver's pointer-target fields expose
|
||||
`worldTrafficLanePersistent.nodeRefHash` or equivalent road/highway flags,
|
||||
use that data inside `DetourGpsEdgeCost` to weight true resource-side
|
||||
highways/major roads. If not, follow the segment pointer fields statically.
|
||||
|
||||
@@ -81,10 +81,14 @@ Active build note:
|
||||
This is meant to identify whether class/mask filtering, rather than the edge
|
||||
multiplier table, is dominating route choice.
|
||||
- The resolver probe logs selected packed-handle resolutions from the
|
||||
search/materialization corridor. It decodes point index, segment index, and
|
||||
generation bits, then dumps the resolved 0x68-byte segment record and
|
||||
0x14-byte point record. The purpose is to see whether the edge-cost stage can
|
||||
reach final lane hashes/resource-side road classes before a path is chosen.
|
||||
search/materialization corridor. The selected post-call return sites are
|
||||
`0x44cb9e` (0x38 intermediate-record builder) and `0x44dd97`
|
||||
(geometry/materialization path). For full-map GPS queries from caller
|
||||
`0x8d20d4` with `query_fcc=5`, it opens a five-second resolver trace window,
|
||||
decodes point index/segment index/generation bits, then dumps the resolved
|
||||
segment/point records plus small raw slices behind segment pointer fields.
|
||||
The purpose is to see whether route materialization can reach final lane
|
||||
hashes/resource-side road classes before the final 0x28 records are emitted.
|
||||
|
||||
Current route-probe focus:
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ constexpr uintptr_t kGpsQuerySubmitRva = 0x070A42C;
|
||||
constexpr uintptr_t kGpsQueryDispatchRva = 0x070A570;
|
||||
constexpr uintptr_t kGpsQueryResultFetchRva = 0x07094B8;
|
||||
constexpr uintptr_t kGpsQueryStatusRva = 0x0AA5704;
|
||||
constexpr uintptr_t kGpsMapRouteSubmitReturnRva = 0x08D20D4;
|
||||
constexpr uintptr_t kGpsDispatchAdvanceRva = 0x08D17D8;
|
||||
constexpr uintptr_t kGpsAsyncWorkerTickRva = 0x0126B28;
|
||||
constexpr uintptr_t kGpsRouteJobStartRva = 0x0818928;
|
||||
@@ -177,6 +178,8 @@ constexpr uintptr_t kGpsFilteredCostProviderVtableRva = 0x2AE60F8;
|
||||
constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00;
|
||||
constexpr uintptr_t kGpsNodeMultiplierRva = 0x040BB40;
|
||||
constexpr uintptr_t kGpsCostMultiplierTableRva = 0x3154D28;
|
||||
constexpr uint64_t kGpsResolveTraceWindowMs = 5000;
|
||||
constexpr uint32_t kGpsResolveSelectedRetLogLimit = 4096;
|
||||
constexpr std::array<float, 7> kGpsPatchedCostMultipliers = {
|
||||
12.0f, 7.0f, 2.5f, 0.0f, 3.0f, 2.0f, 1.25f,
|
||||
};
|
||||
@@ -271,6 +274,8 @@ uint32_t gGpsAuxMultiplierLogCount = 0;
|
||||
uint32_t gGpsNodeMultiplierLogCount = 0;
|
||||
uint32_t gGpsCostTablePatchLogCount = 0;
|
||||
uint32_t gGpsProviderClassPatchLogCount = 0;
|
||||
std::array<uint32_t, 4> gGpsResolveHandleRetLogCounts{};
|
||||
uint64_t gGpsResolveTraceWindowUntilMs = 0;
|
||||
std::array<uint32_t, 64> gGpsEdgeCostClassLogCounts{};
|
||||
std::mutex gGpsCostLogMutex;
|
||||
std::mutex gGpsAsyncLogMutex;
|
||||
@@ -500,6 +505,18 @@ std::filesystem::path GetLogPath()
|
||||
return std::filesystem::path(L"red4ext") / L"plugins" / L"EdgeWeightGPS" / L"EdgeWeightGPS.log";
|
||||
}
|
||||
|
||||
uint64_t GetElapsedMs()
|
||||
{
|
||||
if (!gLogStartTick || !gLogFrequency)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LARGE_INTEGER counter{};
|
||||
QueryPerformanceCounter(&counter);
|
||||
return static_cast<uint64_t>((counter.QuadPart - gLogStartTick) * 1000 / gLogFrequency);
|
||||
}
|
||||
|
||||
void Log(std::string_view aMessage)
|
||||
{
|
||||
std::lock_guard lock(gLogMutex);
|
||||
@@ -515,13 +532,7 @@ void Log(std::string_view aMessage)
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t elapsedMs = 0;
|
||||
if (gLogStartTick && gLogFrequency)
|
||||
{
|
||||
LARGE_INTEGER counter{};
|
||||
QueryPerformanceCounter(&counter);
|
||||
elapsedMs = static_cast<uint64_t>((counter.QuadPart - gLogStartTick) * 1000 / gLogFrequency);
|
||||
}
|
||||
const auto elapsedMs = GetElapsedMs();
|
||||
|
||||
out << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0')
|
||||
<< elapsedMs % 1000 << std::setfill(' ') << " +" << elapsedMs << "ms " << aMessage << "\n";
|
||||
@@ -1072,20 +1083,38 @@ void AppendGpsPackedHandleSummary(std::ostringstream& aLine, const char* aPrefix
|
||||
|
||||
bool IsSelectedGpsResolveReturnRva(uintptr_t aReturnRva)
|
||||
{
|
||||
constexpr std::array<uintptr_t, 7> kSelectedReturnRvas = {
|
||||
0x44D26C,
|
||||
0x44D304,
|
||||
0x44D99F,
|
||||
0x44D9CD,
|
||||
0x44DB50,
|
||||
0x44DB88,
|
||||
0x44DD92,
|
||||
constexpr std::array<uintptr_t, 2> kSelectedReturnRvas = {
|
||||
0x44CB9E,
|
||||
0x44DD97,
|
||||
};
|
||||
|
||||
return std::find(kSelectedReturnRvas.begin(), kSelectedReturnRvas.end(), aReturnRva) !=
|
||||
kSelectedReturnRvas.end();
|
||||
}
|
||||
|
||||
size_t GetGpsResolveReturnBucket(uintptr_t aReturnRva)
|
||||
{
|
||||
switch (aReturnRva)
|
||||
{
|
||||
case 0x44CB9E:
|
||||
return 0;
|
||||
case 0x44DD97:
|
||||
return 1;
|
||||
default:
|
||||
return gGpsResolveHandleRetLogCounts.size();
|
||||
}
|
||||
}
|
||||
|
||||
void ExtendGpsResolveTraceWindow(uint64_t aNowMs)
|
||||
{
|
||||
std::lock_guard lock(gGpsCostLogMutex);
|
||||
const auto untilMs = aNowMs + kGpsResolveTraceWindowMs;
|
||||
if (untilMs > gGpsResolveTraceWindowUntilMs)
|
||||
{
|
||||
gGpsResolveTraceWindowUntilMs = untilMs;
|
||||
}
|
||||
}
|
||||
|
||||
void AppendGpsCostProviderSummary(std::ostringstream& aLine, const char* aPrefix, void* aProvider)
|
||||
{
|
||||
const auto prefix = std::string(aPrefix);
|
||||
@@ -3411,6 +3440,16 @@ int32_t DetourGpsQuerySubmit(void* aManager, void* aQuery)
|
||||
|
||||
const auto callCount = gGpsQuerySubmitLogCount;
|
||||
const auto shouldLog = callCount < kMaxCalls;
|
||||
uint32_t queryKind = 0;
|
||||
const auto hasQueryKind = aQuery && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aQuery) + 0xCC),
|
||||
queryKind);
|
||||
const auto shouldTraceResolver =
|
||||
returnRva == kGpsMapRouteSubmitReturnRva && hasQueryKind && queryKind == 5;
|
||||
if (shouldTraceResolver)
|
||||
{
|
||||
ExtendGpsResolveTraceWindow(GetElapsedMs());
|
||||
}
|
||||
|
||||
if (shouldLog)
|
||||
{
|
||||
std::ostringstream line;
|
||||
@@ -3423,6 +3462,10 @@ int32_t DetourGpsQuerySubmit(void* aManager, void* aQuery)
|
||||
AppendUint32Field(line, "query_f0c", aQuery, 0x0C);
|
||||
AppendUint32Field(line, "query_fcc", aQuery, 0xCC);
|
||||
AppendVector4Field(line, "query_pos58", aQuery, 0x58);
|
||||
if (shouldTraceResolver)
|
||||
{
|
||||
line << " resolveTraceWindowMs=" << kGpsResolveTraceWindowMs;
|
||||
}
|
||||
AppendReturnRva(line, returnAddress);
|
||||
LogRed4ext(line.str());
|
||||
++gGpsQuerySubmitLogCount;
|
||||
@@ -3952,36 +3995,62 @@ int32_t DetourGpsResolveHandle(void* aGraph, uint64_t aHandle, void* aOutSegment
|
||||
const auto hasSegment = aOutSegment && TryReadMemory(aOutSegment, segment);
|
||||
const auto hasPoint = aOutPoint && TryReadMemory(aOutPoint, point);
|
||||
|
||||
const auto pointIndex = aHandle & 0xFFFFF;
|
||||
uint32_t callCount = 0;
|
||||
uint32_t returnCallCount = 0;
|
||||
bool shouldLog = false;
|
||||
{
|
||||
std::lock_guard lock(gGpsCostLogMutex);
|
||||
callCount = gGpsResolveHandleLogCount++;
|
||||
const auto returnBucket = GetGpsResolveReturnBucket(returnRva);
|
||||
if (returnBucket < gGpsResolveHandleRetLogCounts.size())
|
||||
{
|
||||
returnCallCount = gGpsResolveHandleRetLogCounts[returnBucket]++;
|
||||
}
|
||||
const auto inTraceWindow = GetElapsedMs() <= gGpsResolveTraceWindowUntilMs;
|
||||
shouldLog = result >= 0 && hasSegment && hasPoint &&
|
||||
(IsSelectedGpsResolveReturnRva(returnRva) || callCount < 64) && callCount < 512;
|
||||
((IsSelectedGpsResolveReturnRva(returnRva) && inTraceWindow &&
|
||||
returnCallCount < kGpsResolveSelectedRetLogLimit) ||
|
||||
callCount < 32);
|
||||
}
|
||||
|
||||
if (shouldLog)
|
||||
{
|
||||
auto* segmentMeta = ReadPointerField(segment, 0x08);
|
||||
auto* pointArray = ReadPointerField(segment, 0x10);
|
||||
auto* coordArray = ReadPointerField(segment, 0x18);
|
||||
auto* aux20 = ReadPointerField(segment, 0x20);
|
||||
auto* aux28 = ReadPointerField(segment, 0x28);
|
||||
auto* aux30 = ReadPointerField(segment, 0x30);
|
||||
auto* aux38 = ReadPointerField(segment, 0x38);
|
||||
|
||||
const auto auxPointOffset = pointIndex * 12;
|
||||
auto* aux28Point = aux28 ? reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(aux28) + auxPointOffset) : nullptr;
|
||||
|
||||
std::ostringstream line;
|
||||
line << "hook GPSResolveHandle 0x44e1a8 call=" << callCount << " value=0x" << std::hex
|
||||
<< static_cast<uint32_t>(result) << " handle=0x" << aHandle << std::dec;
|
||||
line << "hook GPSResolveHandle 0x44e1a8 call=" << callCount << " retCall=" << returnCallCount
|
||||
<< " value=0x" << std::hex << static_cast<uint32_t>(result) << " handle=0x" << aHandle << std::dec;
|
||||
AppendGpsPackedHandleSummary(line, "handle", aHandle);
|
||||
AppendPointerWithRva(line, "graph", aGraph);
|
||||
AppendPointerWithRva(line, "outSegment", aOutSegment);
|
||||
AppendPointerWithRva(line, "outPoint", aOutPoint);
|
||||
AppendPointerWithRva(line, "segment", segment);
|
||||
AppendPointerWithRva(line, "point", point);
|
||||
AppendPointerField(line, "segment_p08", segment, 0x08);
|
||||
AppendPointerField(line, "segment_p10", segment, 0x10);
|
||||
AppendPointerField(line, "segment_p18", segment, 0x18);
|
||||
AppendPointerField(line, "segment_p20", segment, 0x20);
|
||||
AppendPointerField(line, "segment_p28", segment, 0x28);
|
||||
AppendPointerField(line, "segment_p30", segment, 0x30);
|
||||
AppendPointerField(line, "segment_p38", segment, 0x38);
|
||||
AppendPointerWithRva(line, "segment_p08", segmentMeta);
|
||||
AppendPointerWithRva(line, "segment_p10", pointArray);
|
||||
AppendPointerWithRva(line, "segment_p18", coordArray);
|
||||
AppendPointerWithRva(line, "segment_p20", aux20);
|
||||
AppendPointerWithRva(line, "segment_p28", aux28);
|
||||
AppendPointerWithRva(line, "segment_p30", aux30);
|
||||
AppendPointerWithRva(line, "segment_p38", aux38);
|
||||
AppendGpsPointSummary(line, "pointSummary", point);
|
||||
AppendBytesPointer(line, "segment_raw68", segment, 0x68);
|
||||
AppendBytesPointer(line, "point_raw14", point, 0x14);
|
||||
AppendBytesPointer(line, "segment_p08_raw30", segmentMeta, 0x30);
|
||||
AppendBytesPointer(line, "segment_p20_raw40", aux20, 0x40);
|
||||
AppendBytesPointer(line, "segment_p28_point_raw18", aux28Point, 0x18);
|
||||
AppendBytesPointer(line, "segment_p30_raw40", aux30, 0x40);
|
||||
AppendBytesPointer(line, "segment_p38_raw40", aux38, 0x40);
|
||||
if (gActiveGpsRouteProducerCall != UINT32_MAX)
|
||||
{
|
||||
line << " routeProducerCall=" << gActiveGpsRouteProducerCall;
|
||||
|
||||
Reference in New Issue
Block a user