Files
2077-gps-mod/docs/compaction-handoff.md
T
2026-06-20 21:45:09 -05:00

252 lines
8.8 KiB
Markdown

# Compaction Handoff
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`.
- 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`.
- Game is cold as of the latest user message.
## New Toolbox Tools
The Fedora toolbox `2077` now has:
- `rizin` 0.7.4
- `python3-capstone` 5.0.5
- `python3-pefile` 2024.8.26
If adding Python-only tooling, create/use a venv and maintain `requirements.txt`
instead of one-off pip installs.
## Active Probe Configuration
In `red4ext/EdgeWeightGPS/src/Main.cpp`:
- `kEnableGpsProviderClassPatch = false`
- `kEnableGpsProviderClassPatchTrace = false`
- `kEnableGpsTraceHooks = true`
- `kEnableGpsQueryLifecycleHooks = true`
- `kEnableGpsRouteJobLifecycleHooks = false`
- `kEnableGpsRouteJobStartTrace = false`
- `kEnableGpsLocalSearchHooks = true`
- `kEnableRouteUiTraceHooks = false`
Active hooks:
- query lifecycle:
- `GPSQuerySubmit 0x70a42c`
- `GPSQueryDispatch 0x70a570`
- `GPSQueryResultFetch 0x7094b8`
- `GPSQueryStatus 0xaa5704`
- `GPSSearch 0x44f054`
- `GPSRouteProducer 0x44cc7c`
- `GPSResolveHandle 0x44e1a8`
- `GPSEdgeCost 0x44f838`
- filtered provider filter `0x44ff68`
- base provider filter `0x450b08`
Async route-job hooks (`0x8d17d8`, `0x126b28`, `0x883cd8`,
`0x11f5f60`, `0x818ba8`) are split behind
`kEnableGpsRouteJobLifecycleHooks` and currently disabled to keep the next
run focused on solved route result records plus local search/cost stats.
The filter probe records pass/fail totals by GPS point class and by bits in
`point + 0x10`, tested against provider masks at `+0x108` and `+0x10a`.
## Why This Probe Exists
Data and behavior patches tested so far:
- Traffic `maxSpeed` patches affected NPC freeway speed but not GPS route
choice.
- Lane connection probability patches did not change GPS route choice.
- Global cost-table patch produced weird routing but mostly confirmed we were
near the solver, not a clean highway preference knob.
- Provider class override patches fired correctly but produced no perceptible
route change after preserving vanilla class `14`.
Static disassembly clarified the provider split:
- Provider vtable slot `+0x08` is an admissibility filter, not cost.
- Provider vtable slot `+0x10` is geometric edge cost, multiplying distance by
`provider[0x08 + class * 4]`.
- We patched only slot `+0x10` inputs. If slot `+0x08` filtering dominates the
candidate graph, those cost changes can fire and still not visibly steer GPS.
## Important Native Findings
GPS pipeline landmarks:
- shared query submit: `0x70a42c`
- query dispatch: `0x70a570`
- route producer: `0x44cc7c`
- local search loop: `0x44f054`
- packed-handle resolver: `0x44e1a8`
- edge cost: `0x44f838`
- query result fetch: `0x7094b8`
The search loop is A*/Dijkstra-like:
- initialize/open list with start node
- pop best node
- check destination handle
- traverse adjacent lane handles
- call provider filter
- call provider cost
- update candidate cost/best predecessor
Provider details:
- Base provider vtable RVA: `0x2ae6120`
- Filtered provider vtable RVA: `0x2ae60f8`
- Base provider constructor `0x451158` fills 64 class multipliers with `1.0`
and sets mask `+0x108` to `0xffff`.
- Filtered provider constructor `0x44b9b4` changes vtable to `0x2ae60f8`.
- Filtered provider commonly has class `14 = 0.05`; preserve this unless
deliberately testing it.
Route result records:
- `0x7094b8` output records are 0x28-byte route segment records.
- `h00` in those records matches `worldTrafficLanePersistent.nodeRefHash`.
- Route records can be joined to `work/raw-segment-json/all.traffic_persistent.json`.
- The low byte of route-record `u08` is not the same as the search point class.
- The log's human wall timestamp has a millisecond rollover bug around second
boundaries. Use the `+Nms` elapsed field for ordering and durations.
Packed-handle resolver:
- `0x44e1a8` decodes the packed route handle used by the search/materialization
side:
- low 20 bits: point index
- bits 20..47: segment index
- high bits: generation/check value
- 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.
- 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.
## Latest 23:08 Test Result
The user clicked:
- 23:08 continue on main menu
- 23:30 space to continue
- 23:46 map open
- 23:56 side job
- 24:06 Sinnerman
- 24:14 Claire
- 24:21 custom
- 24:30 Aldecaldo camp
- 24:47 far gig east
- 25:03 Violence
- 25:10 cyberpsycho unfinished highway
Use:
```bash
python3 tools/summarize_gps_route_records.py \
--traffic-json work/raw-segment-json/all.traffic_persistent.json \
logs/EdgeWeightGPS_query_route_probe_2308.log
```
Important result table:
- qid 1: startup/minimap route, 18 records, 50 ms, 1216.5 pavement length.
- qid 2: side job, 12 records, 32 ms, 578.6 pavement length.
- qid 3: Sinnerman, 23 records, 45 ms, 897.4 pavement, 160.4 highway,
89.8 road.
- qid 4: Claire, 18 records, 34 ms, 1216.5 pavement.
- qid 5: custom, 17 records, 44 ms, 1006.3 pavement, 241.0 highway,
38.0 road.
- qid 6: Aldecaldo camp, 80 records, 325 ms, 5853.5 road, 1492.9 highway,
1111.5 pavement, 24.6 gpsonly.
- qid 7: far east gig, 81 records, 144 ms, 5086.2 road, 1523.0 highway,
1111.5 pavement, 24.6 gpsonly.
- qid 8: Violence, 55 records, 97 ms, 1991.5 pavement, 358.5 highway,
86.1 road.
- qid 9: cyberpsycho unfinished highway, 58 records, 147 ms, 2657.4 pavement,
519.0 highway, 113.2 road, 56.7 gpsonly.
All 362 route-record handles matched traffic lane hashes in the extracted
resource.
Local search/provider aggregate from this run:
- 159 searches, 4036 provider filter calls, only 3 filter failures.
- Filter pass classes: `3:3114`, `5:451`, `15:310`, `1:116`, `4:42`.
- Edge source classes: `3:3244`, `5:487`, `15:318`, `1:117`, `4:58`.
- Edge return RVAs: `0x44f457:4033`, `0x44f629:151`,
`0x8d74d3:30`, `0x8d76b0:10`.
Inference:
- Provider vtable slot `+0x08` filtering is almost not pruning these route
searches, so it is not the dominant reason class-cost patches failed to
visibly steer GPS.
- Search-side edge classes are only `{1,3,4,5,15}` in this run. Final emitted
route-record classes include `{0,1,2,3,4,5,7,8,9}` and class 7/8/9 are
highway-heavy. Therefore final route-record `u08` low byte and search point
`point+0x13` class are different domains.
## 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
`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
`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.
- Avoid post-`0x7094b8` rewriting unless everything upstream fails; that would
likely only change displayed path records and risks desynchronizing
instructions from the real planner.
## Useful Commands
Build/install:
```bash
toolbox run -c 2077 ./tools/build_red4ext_shim.sh
toolbox run -c 2077 ./tools/install_red4ext_shim.sh
```
Clear plugin log:
```bash
truncate -s 0 '/var/home/salt/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.log'
```
Inspect current plugin log:
```bash
tail -n 240 '/var/home/salt/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.log'
```
Disassemble with rizin in toolbox:
```bash
toolbox run -c 2077 rizin -q -A '/var/home/salt/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Cyberpunk 2077/bin/x64/Cyberpunk2077.exe'
```
## Caution
- Do not revert user changes or old untracked logs unless explicitly asked.
- Commit early/often.
- Do not install Python packages one by one with pip; use `requirements.txt`.