The Traffic Marshal
Here’s why the “realism” of modern traffic simulations can be so frustrating: a small traffic jam of individual cars can have a cumulative effect that quickly grows out of hand.
Traffic engineers model this with the BPR equation, which models how real accumulated traffic grows at a quadratic rate relative to capacity.
If the bottleneck were to suddenly clear, the road downstream can now discharge at full capacity, but cars are still arriving from upstream at some rate. The queue only shrinks at the difference between what can now get through and what’s still piling in: the drain rate is capacity minus activity, not just capacity.
So the clearance time depends on how close arrivals still are to capacity. If the road is still busy—say arrivals are at 90% of capacity—the queue drains at only 10% of capacity, which means it takes roughly nine times longer to dissipate than it took to form at the same demand. A jam that built over 20 minutes can take a couple of hours to fully unwind if traffic stays heavy.
This is theoretically realistic—except, of course, for the part where a road instantaneously increases capacity. That’s something unique to a computer system overseen by a human player. And that human player craves instant feedback, making the crawling unwind of blocked traffic a real disadvantage in gameplay experience.
This has informed one of Throughput’s critical design deviations from other modern citybuilders. Because its representation is pure abstraction—there’s no individual car with a physical place along its route—Throughput’s system can be highly responsive to changes.
The pathfinding in Throughput moves as fast as your machine. Critical matching is kicked off near the 4-bar boundary (with predictive heuristics on how long it will take to complete), but the rest is optimized as much as possible in between each tick.
As it sands now, however, in a city that spans the entire map, we still might see only 20% or so optimize at a time. While still highly dynamic, I wanted more headroom to add depth.
Throughput was built entirely using gdscript, Godot engine’s special scripting language. All programmers acknowledge scripting languages are notoriously slow, but in a way where we make a sage comment about it and move on, since the benefits in architecture flexibility usually outweigh the costs.
With algorithmically heavy workloads though, the application starts to run into real performance bottlenecks. In this case, converting to using C++ and creating native architecture builds is one of the strongest tools in the toolbox.
Making the choice to use C++ means that I’ll have to compile each build separately on a real machine of each type—windows, mac, and linux. Not so bad to do once, but incredibly painful to do constantly.
Therefore, the sharpest approach is the hybrid one—algorithmic optimization goes into a C++ “core.” This allows me to solidify a steady state for what C++ is good at, and I keep flexible gdscript for painting and UI—what it’s good at.
I’ll show you how to determine when making this structural tradeoff is worth it—for any language pair.
When building this sort of cross-language architecture, the boundary between languages is the main thing to worry about. We have to worry about the cost of marshaling: porting data from one language’s structure to another.
There’s one context in which the word “marshaling” is very familiar: parsing json into Golang. Converting json into Go data structures is the biggest reason why choosing Go “because it’s faster” is often the wrong path.
Parsing data into another data structure is typically linear. Therefore, in order to make up for a language swap, you usually look for whether the compute operations scale superlinearly.

With webservers this often isn’t the case; the majority of requests are handoff to the database. This is why in performance for frontline servers, Node.js can be the choice that wins: while in Golang you’d be paying cumbersome cost to marshal json per request, json is quite familiar to Node.
This method is how we can also examine the tradeoff with gdscript and C++, albeit with slightly different constants: Conversion is more intensive, but the payoff in compute speed is an order of magnitude larger.
While the theoretical payoff in this case is much more clearly tempting, the case where data is constantly moving across the linguistic boundary a huge potential failure mode. Luckily for this traffic sim, matching and optimization are pretty well isolated—they’re already threaded.
And the payoff is visibly real. On a city that spans the entire map, it can now optimize every link 12x over. To really get a feel for this, you’ll get to try it yourself: I’ll soon be releasing a public branch on Steam with the C++ core. You’ll get to see just how quickly the map changes—and tell me if it actually feels too responsive.
There’s a lot I can potentially do next with all this extra computational space this architecture buys me. That’s the real beauty in what good performance architecture does: it expands our possibilities.

