Document full GPS solver lead

This commit is contained in:
2026-06-26 22:45:14 -05:00
parent 7c2ef33952
commit 165bb661f1
3 changed files with 302 additions and 17 deletions
+129 -5
View File
@@ -873,15 +873,139 @@ identical record counts. Therefore `0x44f838` remains a real local graph edge
cost callback, but changing it does not change the final selected route records
for the tested full player GPS routes.
Static disassembly after that failure found a separate, broader async traffic
solver upstream of final route-record materialization. The useful path is:
```text
map route submit 0x8d20d4
shared query submit 0x70a42c
query dispatch 0x70a570
async request enqueue 0x8d17d8 / 0x8d1880
worker queue loop 0x126c06
candidate/projection setup 0x883cd8
solver start wrapper 0x1caa7a0
solver candidate initialization 0x88367c
route job start/advance 0x818928
solver advance loop 0x40ade4
neighbor expansion 0x40b304 / 0x40b540
relax/update helper 0x40ba58
output route build 0x818ba8
```
This path is distinct from the local `0x44f054` VAND search. The worker around
`0x126c06` pulls queued route requests from `routeSystem+0x47c0`. Depending on
state it either calls `0x818928` directly or first calls `0x883cd8`, which
resolves/projects the start and end positions into candidate arrays. The
wrapper at `0x1caa7a0` starts the solver by storing the route mode at
`solver+0xc4`, writing route masks at `solver+0xc0/+0xc2`, copying candidate
arrays into `solver+0x70/+0x80`, and calling `0x88367c`.
`0x88367c` marks start candidate lane/node records with `node+0x9a |= 1`, marks
end candidates with `node+0x9a |= 2`, computes initial node-state costs at
offsets `+0x10/+0x14`, and queues initial states into the open list at
`solver+0xd0`.
`0x40ade4` is the full-route solver advance loop. It processes the open list
while `solver+0xdc != 0`, calls helpers including `0x40b304` and `0x40b540` to
expand neighbors, and marks completed node states with byte `+0x22 = 2`.
Current lane/node records are addressed as:
```text
laneNode = solver + 0xf8 + currentNodeIndex * 0xa0
```
`0x40b304` walks one neighbor list from the current record; `0x40b540` walks a
second list with a slightly different edge-record shape. Both filter neighbors
through `0x40b774`, which reads the 16-bit lane/node flags at `node+0x88`:
```text
pass iff (solver+0xc0 & flags88) != 0
and (solver+0xc2 & flags88) == 0
```
The main cost equation visible in `0x40b304` is:
```text
g = current.g + progress_delta
* 0x40bb40(solver, currentLaneNode)
* directionMultiplier
h = distance(neighborPosition, solverTargetPosition)
f = g + h
```
`directionMultiplier` is normally `1.0`; for reverse/special progress branches
it can come from `0x40bb00(solver, currentLaneNode)` or become `FLT_MAX`.
`0x40ba58` is the relax/update function. When a candidate improves, it writes
`f` to node-state `+0x14`, `g` to `+0x10`, progress to `+0x18`, predecessor to
`+0x1e`, marks state byte `+0x22 = 1`, and pushes the state back into
`solver+0xd0`.
The decisive vanilla multiplier is `0x40bb40`. For driving-style modes it reads
the same `node+0x88` flags and handles them roughly as:
```text
if mode == 1:
pavement flag 0x0008 -> 1.0
otherwise -> 1.5
if mode in {0, 2, 4}:
if Road flag 0x0010:
base = 1.0 or 1.2 depending on a low node flag
if node+0x93 == 1: base *= 1.1
return base
else:
return table[solver+0x54]
fallback:
return 1.0
```
The non-road table at `0x3154d28` starts:
```text
10.0, 6.0, 2.0, 0.0, 4.0, 2.5, 1.5, 0.0
```
The auxiliary multiplier at `0x40bb00` usually returns `1.1` for road cases,
or, for one mode-2 branch, reads a smaller table at `0x3154d38`:
```text
4.0, 2.5, 1.5, 0.0
```
The important design finding is that vanilla has a real edge-cost surface in
the final async solver, but it does not special-case the `Highway` flag
(`0x4000`). A highway lane that is also a road is simply a road. That matches
the original symptom: the solver is weighted enough to avoid obviously invalid
surfaces, but it has no strong reason to stay on highway corridors for long
vehicle trips.
The current RED4ext prototype therefore hooks `0x40bb40` and only changes
driving-mode calls (`solver/job + 0xc4 == 2`). It reads flags from
`node+0x88`, leaves vanilla results intact for ordinary roads and unknown
surfaces, and applies this diagnostic multiplier set:
```text
Highway 0.35
GPSOnly 1.10
Pavement 1.25
Other 1.00
```
`0x40bb00` is currently hooked only for signature logging. If the node
multiplier test moves routes but produces weird reverse/turnaround behavior,
the next step is to tune or gate the auxiliary multiplier as well.
## Future Improvements
Better versions of this mod could:
- identify the broader async traffic route search/expansion loop around the
`0x8d25xx` to `0x8d49xx` cluster
- disassemble the full map-route submit/result chain around `0x8d20d4`,
`0x70a42c`, `0x70a570`, `0x7094b8`, and the `0x520783` result drain to find
the route selection or copy step before final records are emitted
- tune the `0x40bb40` solver multiplier after live A/B testing confirms visible
route changes
- inspect and, if needed, patch `0x40bb00` so reverse/special progress branches
receive the same road-class policy as forward expansion
- add a tiny live-tuning file for the solver multipliers, similar to the earlier
`spatial_weights.bin`, once the hook is confirmed stable
- replace the crashing `0x8d46cc` detour with a narrower inline patch or a
safer caller-side hook
- decode enough of the `VAND` graph to join navigation points to traffic lane