Traffic Simulation on a 25 MHz Budget: What Pizza Tycoon Had to Get Right
Source: hackernews
The Pizza Legacy blog post on Pizza Tycoon’s traffic system is the kind of retroengineering writeup that earns its Hacker News traction honestly. It describes how a 1993 DOS business sim managed to animate a living city of cars on hardware where every instruction had to justify its existence. Pizza Tycoon (released in Germany in 1993 by Cybernetic Corporation under Software 2000, reaching North America as Pizza Connection in 1994) put you in charge of a pizza restaurant chain spread across a city that had actual traffic. Real cars with routes, moving on roads, making the world feel inhabited. On a 25 MHz CPU.
That number deserves some unpacking. A 25 MHz processor in 1993 was likely a 386 DX-25 or an early 486 DX-25. The 386 at that clock speed could manage roughly 8-9 million instructions per second under realistic conditions, with no on-chip cache and no integrated floating-point unit. The 486 DX-25 was meaningfully better, with an 8KB L1 cache and an integrated FPU, but floating-point operations still cost 20 to 70 clock cycles each depending on the instruction. Memory access stalls, bus contention, and the overhead of VGA rendering ate deeply into whatever headroom you thought you had. A frame budget at 20 frames per second gave you about 1.25 milliseconds of CPU time per frame on a 25 MHz part. Everything the game did, simulation, rendering, audio, input, had to fit inside that window.
What Traffic Simulation Actually Requires
A minimal traffic system needs a road network representation, a way to assign destinations to vehicles, a routing algorithm, a movement model, and some form of spacing behavior so vehicles do not phase through each other. For Pizza Tycoon, the problem was harder than it looks, because the game needed delivery vehicles that routed to specific customer addresses. The traffic cars visible on roads needed to look purposeful, not random. You cannot fake that with simple ambient animation when the player is watching deliveries navigate the same streets.
Running A* or Dijkstra for each vehicle each time it needs a new route was not feasible. On a city-scale road network with dozens of active vehicles, that is thousands of graph traversal operations per frame. At 25 MHz, that budget does not exist.
The Toolkit
DOS-era game programmers solved this class of problem with a consistent set of techniques that are worth understanding in their own right.
Fixed-point arithmetic was the baseline. Floating-point was too slow, so coordinates, velocities, and distances were stored as integers with an implicit binary decimal point. The 16.16 format was common: the upper 16 bits hold the integer part, the lower 16 bits hold the fractional part. Addition and subtraction are native 32-bit integer ops. Multiplication requires a shift:
typedef int32_t fixed16;
#define TO_FIXED(x) ((x) << 16)
#define FIXED_MUL(a, b) ((int32_t)(((int64_t)(a) * (b)) >> 16))
#define FIXED_TO_INT(x) ((x) >> 16)
On a 386, a 32-bit multiply cost around 9 to 38 cycles depending on operand values. On a 486, it dropped to 13 cycles deterministically. Good simulation code minimized multiplications in the per-vehicle update path.
Lookup tables eliminated transcendental functions. If you needed a sine or cosine for vehicle heading math, you precomputed 256 or 512 values at startup and indexed into the table with a byte or short. That turned a 100-cycle FPU operation into a single memory access.
Sparse updates let you decouple simulation frequency from frame rate. Vehicles far off-screen do not need full position updates every frame. Staggering updates across frames, updating one quarter of the vehicle pool per frame, dropped effective per-vehicle cost by 75% with no visible consequence to the player.
Pre-computed routing tables are the key insight for navigation on constrained hardware. If your road network has N intersections, you can precompute a next-hop table at map load time: for every pair of nodes (source, destination), store which neighboring node to move to next. The table is N×N bytes or shorts. For a city with 150 intersections, that is 22,500 bytes, trivial to hold in memory. Building it requires one Dijkstra or BFS pass per destination node during load, an operation the player waits for once. At runtime, routing a vehicle reduces to a single array access per intersection crossed.
The per-vehicle update function ends up looking something like this:
void update_vehicle(Vehicle *v, uint8_t next_hop[MAX_NODES][MAX_NODES]) {
v->position += v->speed; /* fixed-point addition */
if (v->position >= v->segment->length) {
v->position -= v->segment->length;
if (v->destination_node >= 0) {
/* Delivery vehicle: use routing table */
int next = next_hop[v->current_node][v->destination_node];
v->segment = road_segment(v->current_node, next);
v->current_node = next;
} else {
/* Ambient traffic: random valid turn */
v->segment = random_exit(v->current_node);
v->current_node = v->segment->end_node;
}
}
}
The separation between ambient traffic and routed delivery vehicles is architectural. Ambient cars use random intersection choices, which requires no computation beyond selecting from a small list of exits. Delivery vehicles pay the table lookup cost, but that cost is O(1) and cache-friendly.
How SimCity Solved a Simpler Problem
SimCity (1989) faced a version of this problem and solved it by collapsing the vehicle abstraction entirely. Its road network is a grid of tiles, and SimCity does not simulate individual vehicles at all. Instead, it tracks traffic density as a per-tile integer, updated each simulation tick by a cellular automaton that propagates demand from residential zones through road tiles toward commercial and industrial zones. The car sprites you see on roads are visual representations of tile density, not agents with routes.
This approach scales with the number of road tiles, not the number of vehicles, and eliminates pathfinding entirely. It is fast, predictable, and elegant. SimCity 2000 (1993) kept the same model while adding subway traffic and more detailed zone types. Maxis had no reason to change an architecture that fit the problem so cleanly.
Pizza Tycoon could not use this approach because its simulation semantics required individual vehicle identity. Your delivery scooter going to table 7 at address 14 Corso Vittorio had to arrive at that specific location, and the player needed to track its progress. Flow-based simulation cannot represent that.
The Modern Comparison
Cities: Skylines (2015) runs a full agent-based traffic simulation with pathfinding per vehicle. It distributes work across threads, uses hardware capable of billions of operations per second, and still produces pathological traffic problems: gridlock from bad intersection design, vehicles taking perverse routes to avoid congestion, the famous highway off-ramp pile-ups that have spawned an entire genre of YouTube content. The brute-force approach at scale surfaces emergent behaviors that are genuinely difficult to tune, because the system is complicated enough that its failure modes are hard to predict.
The pre-computed routing table approach used in 1990s games has a different failure profile. It is deterministic and inspectable. If a vehicle takes the wrong route, you look at the table entry for that source-destination pair and see exactly why. The state space is small enough to reason about directly.
Modern games have returned to hybrid approaches for this reason. Flow-based logic handles global routing and congestion modeling; agents handle local animation and collision response. The intellectual ancestry of that split traces directly to the resource pressure that forced 1993 developers to separate concerns by necessity rather than choice.
What the Constraints Produced
The engineering from the DOS era was forced into conceptual clarity by hardware that would not tolerate waste. Pre-computed routing tables, tile-based movement, sparse updates, and fixed-point arithmetic are not just historical curiosities. They remain the correct tools for constrained environments: embedded systems, microcontrollers, WebAssembly runtimes where every byte of computation budget matters.
The Pizza Legacy documentation project is doing useful work by surfacing how these systems were actually built. The source code and design decisions of 1990s games are primary sources for a set of engineering tradeoffs that the industry keeps rediscovering under new names. Reading how a pizza delivery simulation fit into 25 MHz is, without much exaggeration, applied computer science.