# Compaction Handoff Current date/time context: 2026-06-26, local timezone America/Chicago. ## 2026-06-26 Static Update - Installed RED4ext state is safe: the game plugin directory contains `EdgeWeightGPS.dll.disabled`, not an active `EdgeWeightGPS.dll`. - Latest committed safe source before this update: `d68a874 Disable unstable traffic scorer hook`. - Current source has `kEnableGpsTrafficEdgeWeightPatch = false` and `kEnableGpsLocalSearchHooks = false`. - The real route search is confirmed at `0x44f054`, called from `0x44cc7c`. It is an A*/Dijkstra-style loop: validate packed handles, push start state, pop best open-list state, traverse adjacent handles, call provider filter slot `+0x08`, call provider edge-cost slot `+0x10`, update predecessor/cost, then materialize handles through `0x44eb68`. - Both base and filtered cost-provider vtables point slot `+0x10` at `0x44f838`. The provider vtable RVAs are still `0x2ae6120` (base) and `0x2ae60f8` (filtered). - `0x44f838` computes geometric edge distance and multiplies by `provider[0x08 + ((neighborPoint[0x13] & 0x3f) * 4)]`. - Base provider constructor `0x451158` fills all 64 class multipliers with `1.0`. Filtered provider constructor `0x44b9b4` changes the vtable and `0x44bda4` copies route masks/special zones into the provider, but ordinary GPS route edge cost is still effectively geometric distance. - `0x44f7bc` is the heuristic helper, not edge cost. It returns distance to the target scaled by the constant at `0x1431ef3a0` unless the special route-shape helper supplies an adjusted distance. - `0x44eb68` only walks predecessor links and writes packed handles. The richer final route records are produced afterward by `0x44cc7c`/query result code. - The packed handles resolve into `VAND` navigation blobs from `base\worlds\03_night_city\_compiled\default\navigation_*.streamingsector`, not directly into `all.traffic_persistent`. Final route records can still be joined to `worldTrafficLanePersistent.nodeRefHash`, but that is downstream of the search. - Added `tools/analyze_vand_navigation.py` to decode VAND blobs from extracted streamingsector JSON. On the three extracted samples: `files=3 blobs=300 points=5059 coords=5046`, all masks were `0x0003`, and dominant classes were `1`, `4`, `5`, `3`. - Practical implication: traffic `maxSpeed` and `all.lane_connections` probabilities are not the edge-cost surface. The next viable patch should either hook `0x44f838` and classify edges spatially against precomputed highway/arterial corridors, or patch the navigation VAND graph/classes after extracting the relevant streamingsector resources. ## Current Installed State - Installed RED4ext plugin is disabled: `Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll.disabled`. - There is no active `EdgeWeightGPS.dll` in the plugin directory. - Source defaults are currently safe/read-only. They should not alter route behavior unless rebuilt with hook toggles changed. - Latest archived test log: `logs/EdgeWeightGPS_resolve_handle_probe_4650.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 = false` - `kEnableRouteUiTraceHooks = false` If rebuilt with the current defaults, active hooks are query/async lifecycle only. The local search/cost hooks below require `kEnableGpsLocalSearchHooks = true`: - 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`. - 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, 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 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. ## 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 updated resolver-window probe and run the usual map route routine. - Archive the plugin log as `logs/EdgeWeightGPS_resolve_window_probe_