Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VaritrafV

Stops GTA V from parking identical cars next to each other in ambient traffic.

Single player only. See Safety.

Draft. The explanation below is a hypothesis that fits the evidence, not documentation of the engine. Everything in What the binary says is measurable on your own copy of the game, and I would rather be corrected than believed.

Background

Identical cars turning up together is not new and not specific to any one install. It is a long-running trait of RAGE, and the community explanation has been stable for years: the engine keeps a bounded pool of vehicle models in memory, and under pressure it would rather reuse a model it already has than stream in a new one.

GTA IV showed the loud version of this: the city-wide flood of identical taxis that everyone who modded that game remembers. The account that stuck is that its vehicle budget was small and fixed, and once heavy replacement models exhausted it the streamer fell back on whatever was guaranteed to stay resident. I have never verified that mechanism and I am repeating it as received wisdom, but the symptom was real enough that a generation of mods existed to raise the budget.

GTA V swapped the fixed budget for zone-based pools and added a Population Variety slider, which makes the failure far less dramatic without removing the pressure underneath it.

I hit this in my own game and went looking for an explanation, which is how I found SteXmaN's 2015 GTAForums thread, Traffic diversity problem. His description matches mine closely: the same five to seven models cycling endlessly, heavy on SUVs. He also noted something telling — in an unmodified game the spawn set changes as you move between areas, but with heavy replacement models it largely stops rotating.

Two things are worth separating out of all this:

  • The engine reusing the player's own car is one route to the symptom, not the whole of it. It is the explanation you hear most often, and it does happen, but it is not the only way to end up with twins parked at a junction.
  • Memory pressure explains why the engine tolerates a duplicate. It does not explain why the duplicate is parked twenty metres from its twin. Those are two different problems, and this mod only addresses the second one.

The hypothesis

Watch a busy junction long enough and you will see two or three of the same car sitting next to each other. Not similar — identical, same model, same spot.

My reading of it is that three things line up:

  1. The engine picks traffic models one at a time, and each pick only knows about the cars that already exist. Nothing coordinates a pick with the ones made moments earlier nearby.

  2. There is a distance rule, and it is switched off. vehicles.meta is widely understood to carry a per-vehicle identicalModelSpawnDistance, and to leave it at 0 for most vehicles. A minimum distance of zero is not a rule.

    This is the weakest link in the chain and I have not verified it. Those files live inside the RPF archives, so checking means opening vehicles.meta in OpenIV and reading the value yourself. Note also that an absent field and a field set to 0 look identical from outside: every parsable attribute gets a default whether the modder wrote it or not.

  3. The pool of loaded models is small, bounded by a memory budget rather than by how many cars exist. When only a handful are resident, "spawn a duplicate" beats "spawn nothing".

If that is right, then the fix is not to clean up after a duplicate — it is to refuse the pick that would create one.

What the binary says

None of this needs the mod. It is all measurable with a Python script and the game's own executable.

Observation On the Steam build I tested (July 2026)
Code sections on disk Entropy 8.00, byte histogram flat — encrypted
.rdata Entropy 5.40 — plain, readable
The float 0.19 Appears exactly once in the whole binary
The float 90000 Appears 6 times. 90000 = 300², which reads like a squared distance
Functions referencing both Exactly one

That last row is the whole trick. One rare constant plus one corroborating constant is enough to name a single function out of ~91,000, without ever disassembling anything.

The function that comes back is 3126 bytes at RVA 00708134, and its first bytes back up the guess:

48 89 48 08   mov [rax+08h], rcx     ; 1st argument spilled
48 89 50 10   mov [rax+10h], rdx     ; 2nd
4C 89 40 18   mov [rax+18h], r8      ; 3rd
44 88 48 20   mov [rax+20h], r9b     ; 4th -- a byte, so a bool

then it zeroes two 1000-byte arrays (250 entries of 4 bytes each) and bails out early if a global pointer is null. A model-selection routine that tallies 250 buckets and gives up when its configuration is not loaded is exactly the shape I expected.

Your own log prints all of these numbers, so you can check the reasoning rather than take my word for it.

What this does

It sits in front of that function:

  1. Let the engine make its normal choice.
  2. If the same model was placed within SpacingMeters recently, answer "nothing suitable right now" instead.
  3. The engine skips that spawn and tries again elsewhere a moment later.

Nothing is ever swapped after the fact, because nothing gets built in the first place. That is also why it looks natural — a skipped spawn is something the engine does constantly on its own.

What it does not do

It changes which model gets placed, never what is available to place. If the loaded pool is mostly trucks and SUVs, it stays mostly trucks and SUVs — just no longer the same truck three times at one junction.

That distinction matters because the two problems live in different places. Declining a pick cannot conjure a saloon that is not in memory; refusing everything would only produce empty road. Which classes are resident at all is decided upstream, by population group weightings and how much memory the streamer has to work with — see the last section for the levers that actually move it.

Prior art

The problem is old and has been attacked before. The closest relative came out of the same thread: Traffic Spawner by alphazolam, which watches for duplicate vehicles and replaces off-screen ones with random models from a list.

It works, and it can do something this mod cannot: fix duplicates that already exist. The trade is inherent to the approach — a car has to be swapped after it has been created, so it needs a minimum distance setting to stop vehicles transforming in view, and it costs frame time to keep scanning.

VaritrafV takes the other end. It declines the pick, so the duplicate is never created and there is nothing to swap: no transformations, and no per-frame work beyond a comparison against the last two dozen spawns. The cost is that it is powerless over traffic that already exists, including everything spawned before it loaded.

Neither is strictly better. If you want existing traffic cleaned up as you drive, use that one. If you want the duplicates not to happen in the first place, use this one.

Building

Needs Visual Studio with Desktop development with C++, and git.

build.bat

Produces build\VaritrafV.asi. MinHook is fetched automatically on first build.

To run the tests:

build.bat test

Visual Studio 18 prints 'vswhere.exe' is not recognized during the build. That comes from Microsoft's own vcvars64.bat, not from here, and nothing is wrong — the compiler is found either way.

Installing

Copy VaritrafV.asi and VaritrafV.ini next to GTA5.exe, and make sure you have an ASI loader there too. Any x64 proxy the game actually imports will do — dinput8.dll is the usual choice.

Configuring

VaritrafV.ini:

SpacingMeters=70

Higher means more variety and slightly thinner traffic.

Checking it works

The log is at %TEMP%\VaritrafV.log:

=== VaritrafV ===
spacing: 70 m
attempt 1
  constants: 0.19 x1, 90000 x6
  functions: 4 using the first, 10 using the second, 1 using both
  found at RVA 00708134, 3126 bytes
    +000  48 8B C4 44 88 48 20 4C 89 40 18 48 89 50 10 48
hook active

The number that matters is the last one on the functions: line:

  • 1 — identified beyond doubt, hook goes in.
  • 0 — the anchors do not match this build; nothing is patched.
  • more than 1 — ambiguous, so nothing is patched. Add a third constant in src/resolve.cpp to narrow it down.

After a successful find the log also lists the constants that function reads, sorted rarest first. That list is where the current anchors came from, and where replacements come from if a game update ever breaks them — see Where the anchors come from.

attempt 1 finding nothing is normal. The code sections are encrypted until the anti-tamper layer unpacks them shortly after launch.

Once traffic is spawning, a line appears every 30 seconds:

spawns: 96 allowed, 14 refused (13%)

That is the only thing proving the mod does work rather than just sitting there.

The counts are cumulative since launch, so the first reading is dominated by the initial population fill and reads much higher than the steady state. On my run at 70 m it went 59% in the first busy window and settled around 28%, and traffic did not visibly thin out. Near zero would mean the spacing never triggers at all.

One caveat on reading it: a refusal is not the same as a duplicate prevented. After a refusal the engine simply tries again, and if it offers the same model nearby a moment later that is counted a second time. The number tells you the veto is firing, not how many twins it saved you from.

How it finds the function

The code is encrypted on disk, so there is nothing to scan for in the file. It is decrypted in memory — it has to be, to run — and this mod runs inside that process, so it reads what the game already unpacked for itself.

That still leaves ~91,000 anonymous functions. The way in is that .rdata, where float constants live, is not encrypted. Scanning for RIP-relative references to the two constants above narrows it to one function, and RtlLookupFunctionEntry turns that address into an entry point.

No hardcoded offsets, so a game patch does not break it: 0.19 is still 0.19, and the search finds the function wherever it moved to.

Where the anchors come from

Two arbitrary-looking numbers deserve better than "trust me", so here is the path to them. It is worth reading not for provenance but because it is the procedure you would need if an update ever breaks the current pair.

Start from the symptom. Cars are ending up too close to identical ones, so somewhere there is a distance check. Games compare squared distances to avoid a square root, so squares of round, human-chosen distances are worth a look. In .rdata, which is not encrypted, that narrows things a lot — of twenty round distances I tried, fifteen were rare enough to anchor on:

 75 m ->   5625  x1      250 m ->  62500  x1
125 m ->  15625  x1      300 m ->  90000  x6
150 m ->  22500  x5      400 m -> 160000  x1

That is a shortlist of candidates, not an answer. You take one, find the functions that reference it, and look for something shaped like a model picker.

Then let the function tell you the rest. Once you have a candidate, the constants it reads are far more informative than anything you can guess from outside. That is why the log prints them, rarest first:

  constants it reads, rarest first (rarer means a sharper anchor):
    0.19           x1
    90000          x6
    0.09           x4
    ...

0.19 appears once in the entire binary, which makes it a far sharper anchor than the distance constant that led there — so it becomes the primary and the squared distance stays on as corroboration.

One thing this is not. You could not have found 0.19 by hunting for rare constants directly. Of the floats between 0.0001 and 1.0 in .rdata, 6588 are distinct and 5765 of those occur exactly once — being unique is the normal case for a tuning value, not a clue. It is only findable once you already have the function.

Safety

Do not run this in GTA Online. Memory patching there is a fast ban.

The mod refuses to install itself if BattlEye is loaded, but that is a backstop, not a licence — launch single player directly.

Layout

src/
  main.cpp      DllMain, startup thread, config
  resolve.*     finding the function in memory
  hook.*        the interception itself
  dedup.h       the spacing rule (pure logic, unit tested)
  log.h         diagnostics
tests/
  test_dedup.cpp    the spacing rule
  test_resolve.cpp  the anchor scan, pointed at the test binary itself
tools/          optional: turn a log hex dump into a byte pattern

Both tests run without the game. The resolve test matters most: it is the only way to find out the scan is broken without waiting on a log file inside GTA.

If the hypothesis is wrong

It is falsifiable, which is the point. If duplicates keep appearing while the log reports a healthy refusal rate, then the pick I am intercepting is not the one creating them, and the explanation above needs revising rather than the spacing value.

The refusal counter also tests assumption #2 sideways. If the engine were already keeping identical models apart, a 70 m veto would almost never have anything to refuse. A refusal rate that is clearly above zero says something was getting through that should not have been — which is what the assumption predicts.

Note that none of this is load-bearing for the mod itself: VaritrafV never reads identicalModelSpawnDistance. It enforces SpacingMeters and ignores whatever the engine believes about spacing. The assumption explains the symptom; it is not a dependency of the fix.

Worth knowing either way: if you only care about a handful of vehicles, you do not need this mod. Set identicalModelSpawnDistance to something like 100 for them in vehicles.meta. No code, and it goes at assumption #2 directly. VaritrafV exists for applying the idea to every model at once without editing hundreds of entries.

If one class is dominating

A different complaint, and not one this mod can answer: a junction full of box trucks that all happen to be different models. Nothing here is being duplicated, so there is nothing for the spacing rule to refuse.

The levers for that, cheapest first:

  1. Population Variety, in the graphics settings. It sets how much room the game gives to keeping distinct models resident, and it is one slider.
  2. popgroups.ymt and popcycle.dat, which exist precisely to set the mix of classes per area and time of day.
  3. The vehicle memory budget, which decides how many models fit at all.

None of those is an ASI mod, which is the point — it is a data problem with a data answer. The one argument for solving it in code instead is that a mod editing popgroups.ymt collides with every other mod editing popgroups.ymt, and doing it at runtime does not. That may end up being a separate mod.

About

GTA V ASI mod that stops identical vehicles spawning next to each other in traffic. Single player only.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages