What DrivEngine actually is
Most game traffic follows a recorded path. DrivEngine gives each car a driver instead. This is an overview of how roads become lanes, decisions and movement.
What DrivEngine actually is
Drive through almost any open world game and stop your car sideways across a lane. Watch what happens. In most games one of three things follows. The traffic drives through you. The traffic stops forever. Or the traffic quietly despawns and respawns behind you, as if the street were a treadmill.
Those results usually come from a reasonable design choice. The cars are following playback. A spline says where to go, a speed curve says how fast, and a sensor says when to brake. It is cheap and predictable. For background scenery, it is often the right choice.
DrivEngine exists for the games where traffic is not scenery.
This first article is an overview. The later parts go into the places where the project became difficult.
Three ways to build traffic, and what each one costs
Play back a path. Cars follow authored splines. Cheap, controllable, and the illusion breaks the instant the player does something the author did not draw. Every recovery case has to be scripted by hand, and there are always more cases than you scripted.
Simulate a city. This is what traffic engineering software does. Every vehicle is modelled, every junction is modelled, and the whole town is stepped forward so a planner can ask whether a new bus lane helps. It is correct and it is slow, and it is built to answer questions no game asks. A game does not care about the annual average delay at an intersection. A game cares about what happens in the thirty seconds around the player.
Give each car a driver. Each car knows where it wants to go, looks at the road and at the other cars, and decides what to do. Nothing is played back. If the player blocks a lane, the drivers behind see a blocked lane, because a blocked lane is what it is.
The third option is the interesting one and it is also the one that fails in embarrassing ways if you are careless. Cars deadlock at four way junctions. Two cars both decide to go and meet in the middle. A car takes a turn no real car could steer. A queue backs up into a junction and locks the whole grid. Every one of these has bitten this project, and most of the later articles in this series are about one of them.
The four stages
I split the system into four stages.
1. Author
You describe roads and junctions in a form a human can edit. A road is a chain of nodes. Each node carries a point, a direction, and a cross section: how many lanes, how wide, which way each one drives, whether a lane is drivable at all or is a shoulder or a median. A junction is a point with arms, where each arm is the same kind of cross section, plus a list of which arm lane connects to which other arm lane.
That last list is the part people underestimate. A junction is not a shape. A junction is a set of permitted movements. Whether a left turn exists at this intersection is a fact about the junction, and it has to be decided somewhere.
You can author this by hand in the studio, generate it procedurally from a seed, or stamp in a prebuilt junction or interchange. They all produce the same data, because they are all writing the same authoring layer.
2. Bake
The authoring layer is comfortable for humans and useless for drivers. A driver does not want a cross section and a node chain. A driver wants "here is a curve, this is where it starts, this is what it connects to, this is where another car's path cuts across mine".
The bake turns one into the other. It produces lanes, which are the actual drivable geometry, and it connects them into a graph. A lane knows its curve, its height, and its left and right bounds. Relations between lanes carry everything else: this lane leads into that one, a driver may change from this lane to the one beside it, these two lanes merge into the same exit, and these two lanes cross each other, here, at this distance along each of them.
Where two paths cross is computed once during the bake, not every frame by every car. The next article explains why this data stays separate from the editable road.
3. Drive
Every car carries a driver, and the driver is split into three tiers.
The route tier answers "where am I going". It runs a search over the lane graph, and its cost is travel time rather than distance, so a driver prefers a longer fast road to a shorter slow one.
The decision tier answers "what should I be doing right now". This is where yielding lives, and queueing, and stopping at crosswalks, and obeying a signal, and the judgement that a junction exit is too full to enter. It does not steer. It produces a target speed and a steering target.
The control tier answers "how do I move the wheel and the pedals". Two controllers, one for steering, one for speed, and then a vehicle model that turns pedal positions into motion.
Each tier is blind to the one below it. The decision tier does not know whether the car underneath is a kinematic model in our own code or a full physics vehicle owned by a game engine. That is not tidiness for its own sake, it is what makes the last stage possible.
4. Embed
The same simulation core runs in two places: the studio, which is our own tool for building and watching, and Unreal, through a C++ bridge. The game engine can hand the simulation a car whose transform it owns, so a physics vehicle and an AI car can share a road and see each other.
There is also a streaming rule, because no game simulates a whole city. You give it the regions that matter, usually a box around the player, and a wanted car count. Cars outside every region go away, new cars appear at sensible places inside them, and the population stays at the number you asked for. A car that the player never sees does not need to be simulated, and the cheapest car is the one that does not exist.
Testing the result
"Believable traffic" is not something you can put in a test. So the project does not test that. It tests things that are true or false.
Some of them are invariants, meaning rules that must never break. A car must stay inside the bounds of its lane. A car must not move backwards along its own path. Two car bodies must not overlap. A car must not be given a target speed it can no longer reach by braking comfortably, because a target it cannot reach is a plan that will be paid for with the emergency brake.
Some of them are measurements. How many trips finished. How much throughput the town carried. How often a driver had to brake hard. How many times a group of cars waited on each other in a ring, with nobody able to move.
And here is the part that took the longest to learn: a single run proves nothing. Traffic is chaotic in the technical sense. Two runs with different random seeds diverge, and a change that looks like a ten percent improvement on one seed is often noise. So a measurement here means a sweep over many seeds, and a number is treated as a sample rather than as an incident. There is a whole article in this series about that, because measurement discipline turned out to be the single highest value thing in the project, higher than any individual driving rule.
Runs can also be recorded to disk and replayed. A recording holds the baked road facts and one row per car per tick, and it is scored by exactly the same code that scores a live run. When a crash happens once in a hundred runs, you need to be able to keep it.
Limits
It is not a traffic engineering tool. It does not claim that its junction throughput matches a real intersection in a real city. It aims at traffic that a player reads as correct, which is a different and in some ways harsher target, because a player is standing inside the intersection rather than looking at a summary table.
It is not a physics engine. It has a simple vehicle model for its own use, and the moment a real engine is present, the real engine drives the car.
It is not a renderer. It produces road geometry and car transforms. What you do with them is your business.
And it is not finished. The parts I am most confident in are the authoring layer, the bake, and the measurement harness. The part that still gets rewritten is the moment when two cars want the same piece of asphalt at the same time. That is the hardest problem in the whole project, and two articles in this series are about the ways I got it wrong.
The next article starts with the split between the road a person edits and the lanes a car drives. After that come junctions, drivers, and the failures that forced those parts to change.
Most game traffic is played back rather than driven. A spline says where a car goes and a sensor says when to brake. That is cheap and often good enough. DrivEngine is for games where the player can interfere with traffic and expect it to recover.
The project gives every car a destination and a driver. The driver reads the same roads and nearby traffic that the player sees. If the player blocks a lane, the other cars see a blocked lane rather than a broken animation.
A car goes through four stages.
- Author. Roads and junctions in a form a person can edit, by hand or from a seed.
- Bake. Turn that into lanes, which are the real drivable curves, plus a graph that says which lane leads into which, where two paths cross, and where they merge.
- Drive. Three tiers per car: route, decision, control. Each tier is blind to the one below it.
- Embed. The same core runs in our own studio and inside Unreal, where the game engine can own a car and the traffic still sees it.
"Believable" is too vague to test. The tests use specific rules instead: a car stays inside its lane, two car bodies do not overlap, and a target speed remains reachable. Traffic varies too much for one run to mean much, so measurements cover many random seeds.
The part that still gets rewritten is the moment two cars want the same piece of asphalt.
This is the short version. The full article has the reasoning, the numbers, and the parts that went wrong.