# Compaction Handoff

Current date/time context: 2026-06-20/21, local timezone America/Chicago.

## Current Installed State

- Latest commit: `64ddf7a Trace GPS provider filters`.
- Installed RED4ext plugin:
  `Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll`.
- Installed build is read-only. It should not alter route behavior.
- Plugin log is cleared and empty before the next test:
  `Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.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`
- `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.

## Next User Test

Ask the user to run the usual routine:

1. Launch game.
2. Hit Continue on main menu.
3. Hit Space to Continue.
4. Wait for minimap/GPS to settle.
5. Open map.
6. Plot the usual three routes plus one custom highway-ish target.
7. Close game.

Ask for timestamps for:

- Space-to-Continue
- map open
- each route click
- game cold

After the run:

1. Archive the log to `logs/EdgeWeightGPS_provider_filter_probe_<time>.log`.
2. Inspect `GPSQuerySubmit`/`GPSQueryResultFetch` lines for tracked route IDs
   from submit return RVA `0x8d20d4`, especially successful `0x7094b8`
   records with `gpsResult_ptr28_rec40`.
3. Inspect `GPSSearch result` lines for `filterPassClasses`,
   `filterFailClasses`, `filterPassMaskBits`, and `filterFailMaskBits`.
4. Compare filter pass/fail classes to `edgeSrcClasses` and route output
   records. The key question is whether the class/mask filter is selecting the
   graph subset before cost weighting can matter.

## 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`.
