Files
2077-gps-mod/docs/compaction-handoff.md
T

7.5 KiB

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
  • 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
  • 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.

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:

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

  • Static disassembly around result-drain/producer RVAs 0x520783, 0x41be2c, 0x41be94, 0x70a42c, and 0x8d20d4.
  • The plausible patch point is upstream of result copy: find where search output is converted to 0x28 route records or where candidate lanes are ranked with enough lane-hash/resource context to prefer highway/major-road lanes.
  • Post-0x7094b8 rewriting would likely only change displayed path records and risks desynchronizing instructions from the real planner.

Useful Commands

Build/install:

toolbox run -c 2077 ./tools/build_red4ext_shim.sh
toolbox run -c 2077 ./tools/install_red4ext_shim.sh

Clear plugin log:

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:

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:

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.