Files
2077-gps-mod/docs/traffic-system-debrief.md
T
2026-06-19 22:30:58 -05:00

11 KiB

Cyberpunk 2077 Traffic/GPS System Debrief

This is based on the public NativeDB RTTI dump, WolvenKit exports from the local Steam install, and the generated all.traffic_persistent resource from the Phantom Liberty archive.

Current Working Theory

The first hypothesis was that the in-game car GPS routes over the generated traffic lane graph and consumes lane metadata such as maxSpeed, highway flags, and lane connection probabilities. Live tests have not supported that:

  • boosting highway maxSpeed changed freeway traffic speed but did not change player GPS route choice
  • changing all.lane_connections exit probabilities did not change player GPS route choice
  • probing the obvious TrafficSystem_Pathfinding and StartPathfinding/StopPathfinding string-xref wrappers did not fire during deliberate world-map route plotting

The stronger current theory is that player GPS route selection goes through a native GPS/mappin query path, with traffic data used by traffic simulation and possibly as an input to cooked GPS data, but not as a live route-cost graph that can be steered by simple resource weighting.

Static GPS Query Candidates

Static disassembly of Cyberpunk2077.exe found script/native registration thunks for RunGPSQuery and UpdateGPSQuery. The string xrefs themselves are registration code, but their registered function pointers lead to more interesting native wrappers:

RunGPSQuery string rva:       0x2cd9978
RunGPSQuery registration xref: 0x147d068
RunGPSQuery wrapper rva:      0x29bd5ac
RunGPSQuery helper rva:       0x29bcf14

UpdateGPSQuery string rva:       0x2cd9968
UpdateGPSQuery registration xref: 0x147cfb4
UpdateGPSQuery wrapper rva:      0x29bd6c8
UpdateGPSQuery helper rva:       0x29bd254

RunGPSQueryHelper appears to read source/target vector-ish script arguments, resolve a target object/mappin, build temporary bitsets/sets, and call helpers near 0x204abdc and 0x204ac1c. Its return value is used as a success flag by the wrapper.

UpdateGPSQueryHelper appears related but takes a request type and target. Its return value is also used as a success flag.

The next RED4ext probe should hook these helpers with return-value-preserving signatures, not the registration thunks:

bool RunGPSQueryHelper(void* this_, void* from, void* to, void* debugText,
                       void* resultText, float maxDistance);
bool UpdateGPSQueryHelper(void* this_, uint32_t requestType, void* target,
                          void* debugText);

These signatures are inferred from the wrapper call sites and Windows x64 calling convention. If either helper fires exactly when a map destination is selected, it is a much better path into the actual route computation than the traffic-pathfinding probes.

The High-Level Shape

Cyberpunk appears to split "navigation" into at least two major domains:

  • pedestrian/NPC navigation over navmesh resources
  • vehicle/GPS routing over generated traffic lane resources

The public script/native type dump exposes worldNavigationScriptInterface and AINavigationSystem methods for navmesh path queries, but those are character navigation tools. They are not the car GPS. The car GPS system type exists as gamegpsGPSSystem, but its public RTTI surface does not expose planner methods that a REDscript mod can override.

That means the practical mod surface is data, not algorithm replacement. The native gamegpsGPSSystem likely builds or queries a graph from traffic lane resources, then renders a GPS line from the selected lane path.

Relevant Game Types

The useful RTTI types are:

  • gamegpsGPSSystem
  • gamegpsSettings
  • worldTrafficPersistentResource
  • worldTrafficPersistentData
  • worldTrafficLanePersistent
  • worldTrafficLanePlayerGPSInfo
  • worldTrafficLanePersistentFlags

gamegpsSettings contains display/refresh settings:

  • lineEffectOnFoot
  • lineEffectVehicle
  • fixedPathOffset
  • fixedPortalMappinOffset
  • pathRefreshTimeInterval
  • lastPlayerNavmeshPositionRefreshTimeIntervalSecs
  • maxPathDisplayLength

Those affect GPS presentation and refresh behavior, not edge selection.

worldTrafficPersistentResource is the key resource type. Its root chunk has data: worldTrafficPersistentData, which has:

  • lanes: array<worldTrafficLanePersistent>
  • neighborGroups: array<array<Uint16>>

Each lane has the actual graph metadata:

  • outLanes
  • inLanes
  • outline
  • accumulatedLengths
  • crowdCreationInfo
  • maxSpeed
  • deadEndStart
  • length
  • width
  • area
  • flags
  • subGraphId
  • playerGPSInfo
  • neighborGroupIndex
  • nodeRefHash
  • laneNumber
  • seqNumber
  • isReversed
  • roadMaterials
  • polygon

playerGPSInfo contains:

  • subGraphId
  • stronglyConnectedComponentId

That strongly suggests the GPS planner has a precomputed connectivity graph and uses connected-component/subgraph metadata to reject impossible paths quickly.

Resource Location

In this install, the important resource is:

base\worlds\03_night_city\sectors\_generated\traffic\all.traffic_persistent

It exists in both the basegame Night City archive and the Phantom Liberty Night City archive. For a Phantom Liberty install, the EP1 archive version should win:

archive/pc/ep1/ep1_1_nightcity.archive

WolvenKit can extract, serialize, deserialize, and pack this resource on Linux through wolvenkit.cli 8.18.1.

Lane Flags

worldTrafficLanePersistentFlags:

FromRoadSpline = 1
Bidirectional = 2
PatrolRoute = 4
Pavement = 8
Road = 16
Intersection = 32
NeverDeadEnd = 64
TrafficDisabled = 128
CrossWalk = 256
GPSOnly = 512
ShowDebug = 1024
Blockade = 2048
Yield = 4096
NoAIDriving = 8192
Highway = 16384
NoAutodrive = 32768

The important flags for GPS weighting are Road, Intersection, GPSOnly, and Highway. The important flags to avoid touching are Pavement, CrossWalk, TrafficDisabled, and Blockade.

Observed Lane Distribution

The local EP1 all.traffic_persistent resource contains 33,952 lanes when extracted from the archive's raw compressed segment.

Observed maxSpeed distribution:

all lanes:
  1: 12852
  15: 12107
  10: 5859
  17: 3027
  14: 96
  8: 8
  9: 2
  4: 1

highway lanes:
  17: 2700
  15: 164
  1: 2

road lanes:
  15: 11943
  10: 671
  17: 327
  14: 96
  8: 4

pavement lanes:
  1: 12848
  10: 5188
  8: 4
  9: 2
  4: 1

The values are not real-world mph/kph. They are compact game-scale lane speeds. Because highways are already mostly 17 and normal roads are mostly 15, a large value like 90 would be reckless. The mod currently uses conservative defaults:

  • highway speed target: 25
  • road speed floor: 15

That changes 2,519 lanes:

  • 2,429 highway boosts
  • 90 road floor boosts

It leaves pavement/crosswalk/blocked/disabled lanes alone.

What The Mod Actually Does

The mod does not replace the pathfinding algorithm.

It edits the traffic graph's lane metadata so the native GPS planner has better cost inputs. If the planner factors maxSpeed into traversal cost in the usual way, then a segment with the same physical length but higher maxSpeed becomes cheaper. That nudges the native planner toward highways and high-capacity roads without having to hook the planner itself.

Conceptually:

old cost ~= lane.length / lane.maxSpeed
new cost ~= lane.length / patched_lane.maxSpeed

The actual game formula is native and not exposed in RTTI, so this is an inference. The inference is supported by the presence of maxSpeed, length, connectivity data, and GPS-specific lane graph metadata in the same resource.

Why Not Just Write A New A*?

A standalone A* is easy. Integrating it into Cyberpunk's GPS is the hard part.

The game already has:

  • traffic lane connectivity
  • lane polygons/outlines
  • player lane matching
  • destination lane matching
  • dynamic refresh
  • minimap/world map rendering
  • quest mappin target integration
  • portal/entrance handling
  • route line effects

The script-facing gamegpsGPSSystem type does not expose hooks for replacing that pipeline. A custom planner would still need a way to feed its result back into the GPS renderer and route-following systems. Without native RED4ext hooks, that is much riskier than patching the resource data the existing pipeline already consumes.

Packaging Flow

The working flow is:

  1. Read the archive index and extract the exact compressed segment for resource hash 3419764573789342681.
  2. Decompress that KARK segment with WolvenKit CLI's Oodle command.
  3. Serialize the CR2W resource to JSON.
  4. Patch lane maxSpeed values.
  5. Deserialize the patched JSON back to CR2W.
  6. Put the CR2W back under the original resource path.
  7. Pack an archive.
  8. Place the archive in archive/pc/mod.

Do not use plain cp77tools extract for this resource. In this install, WolvenKit CLI extracted a 9,984,106 byte CR2W, while the stock archive segment declares and decompresses to 10,673,648 bytes. A vanilla control archive built from the smaller extraction crashed during load. A vanilla control archive built from the raw segment reached the game and the GPS worked normally.

The generated archive currently contains exactly:

base\worlds\03_night_city\sectors\_generated\traffic\all.traffic_persistent

Risks And Unknowns

The first major finding from in-game testing is that maxSpeed is not a useful GPS weighting lever. Raising highway lanes as high as 120 did not change GPS routes in the test case, but it did make freeway traffic noticeably faster. That means maxSpeed should be treated as traffic simulation data, not GPS route cost data.

The actual lane adjacency is not populated in all.traffic_persistent; every lane's outLanes array serialized empty. The adjacency is in:

base\worlds\03_night_city\sectors\_generated\traffic\all.lane_connections

That resource is a worldTrafficPersistentLaneConnectionsResource with one row per lane. Each row has inLanes and outlanes. Each outgoing connection contains laneIndex, exitProbabilityCompressed, isSharpAngle, nextLaneEntryPosition, and thisLaneExitPosition.

There is still no explicit edge cost field. The current best data-only test is to patch exitProbabilityCompressed: strongly favor road/GPSOnly/highway connections that enter or remain on highways, and lower competing non-highway exits. A prepared experimental archive changes 145 outgoing connection probabilities across 136 lanes.

The Oodle library was not available inside the toolbox, so WolvenKit packed with its Kraken fallback. A vanilla full-resource control archive loaded in game and GPS worked normally, so the earlier crash was caused by bad extracted data rather than the Kraken-packed archive container.

The patch targets the EP1 traffic resource because Phantom Liberty is installed. On a non-PL install, the basegame archive resource would need to be patched instead.

Future Improvements

Better versions of this mod could:

  • build district-specific presets
  • patch all.lane_connections probabilities around ramps and highway exits
  • inspect GPSOnly connector lanes separately
  • compare route output with probability-only archives enabled
  • locate any native or baked edge-cost data not exposed in RTTI
  • generate a diff report of every changed lane with flags, length, speed, and graph component
  • ship multiple archives: conservative, strong, and experimental

The next practical step is in-game route testing between known bad waypoint pairs, especially cases where vanilla GPS exits highways too early or cuts through surface streets.