City Traffic on a 25 MHz CPU: The Simulation Architecture Behind Pizza Tycoon
Source: hackernews
The 486DX at 25 MHz delivered somewhere between 20 and 25 MIPS for integer-heavy workloads, and games targeting that hardware in 1994 were acutely aware of every cycle. At 15 frames per second, a typical target for a city builder where smoothness mattered less than city size, the entire frame budget worked out to roughly 1.67 million cycles. Rendering the VGA screen alone, a 320x200 framebuffer at 256 colors, consumed tens of thousands of cycles just to blit. The simulation had to fit in whatever remained.
Pizza Tycoon, released in 1994 by Cybic Entertainment and published under the Software 2000 label, sits in an interesting position in this history. It was neither a pure city builder like SimCity nor a pure business sim; it made restaurant placement decisions visible through an animated city that responded to them. Customers walked and drove to your restaurants. Traffic near a thriving location thickened visibly. The simulation had to be cheap enough to run continuously while the player was actively manipulating the interface, not just during a slow tick between turns.
The retroactive writeup at pizzalegacy.nl unpacks how the traffic system was actually implemented, and the choices described there reflect a set of tradeoffs that virtually every city builder of the era was navigating simultaneously.
The Pathfinding Problem at 25 MHz
A* on a 128x128 tile grid, a reasonable size for a city builder map of the period, has a worst-case node expansion count in the tens of thousands. Each node expansion on a 486 required managing an open set and a closed set, computing f = g + h, and making memory accesses that, when they missed the 8KB L1 cache, cost between 4 and 7 wait states at typical 70ns DRAM speeds. Conservative estimates put a full-grid A* search at 300,000 to 800,000 cycles, depending on implementation quality and cache behavior.
Running that search once per agent per frame for even a modest crowd was not possible. The entire simulation budget was in roughly the same range as a single bad pathfinding call. Developers of the era converged on a small set of techniques to manage this, and most city builders used some combination of them.
The first was path caching. Once a route was computed for an agent, it was stored as a waypoint list and followed until the destination changed or the road topology changed. Per-frame work per agent dropped to something trivial: advance position along the current segment, test for arrival at the next waypoint, switch segments. For a reasonable crowd, this could be done in a few hundred cycles per agent.
The second was simulation decoupling. The city simulation ran on a coarse tick measured in seconds of real time, while the renderer ran at whatever rate the hardware could sustain. Will Wright designed SimCity around exactly this split: the simulation updated at roughly 2 Hz, subdivided into spatial sectors so that only a portion of the map was recalculated on any given tick. Rendering showed the most recent simulation state with animated sprites that conveyed activity regardless of whether the underlying values had changed.
The third was the statistical model, taken to its logical extreme in SimCity’s traffic system. Rather than simulating individual vehicles at all, SimCity maintained a traffic-density byte per road tile, updated by running a simplified shortest-path calculation from residential zones to commercial and industrial destinations, incrementing density values along those paths. This was effectively a multi-source BFS on the road graph, running at simulation-tick rate, O(road tiles) rather than O(agents). The visible cars on screen were sprites placed procedurally based on density thresholds, not tracked entities.
How the Era’s Alternatives Compared
The range of approaches visible across 1994’s management simulators shows how differently developers weighed the tradeoffs.
Chris Sawyer wrote Transport Tycoon almost entirely in 80386 assembly language, which gave him enough performance headroom to simulate individual vehicles with real pathfinding while staying within the hardware budget. His key design decision was representing the road and rail network as a graph of track segments rather than a grid of tiles, dramatically compressing the search space. A vehicle pathfinding from depot to destination searched a graph of hundreds of segments, not a grid of tens of thousands of tiles. Paths were pre-computed at departure and on signal-state changes, not per frame.
Bullfrog’s Theme Park, also from 1994, took a hierarchical approach to visitor pathfinding. The park was divided into zones, and visitors pathfound to zone boundaries first, then within zones. This reduced the worst-case search to the zone scale rather than the park scale, keeping per-agent pathfinding tractable even with visitor populations in the hundreds. Agents were also updated on a staggered schedule, spread across frames in buckets rather than all processed simultaneously, smoothing the computational cost across time.
The most extreme optimization appeared in Impressions Games’ Caesar (1992) and its successors. Citizens were not pathfinding agents at all. They were walkers that emanated from buildings and traveled road networks using deterministic turn rules, distributing services by physical proximity. No pathfinding ran anywhere in the agent loop. Traffic was a visual emergent property of building placement and road layout rather than a simulated quantity.
What Pizza Tycoon Was Solving For
The specific challenge Pizza Tycoon faced was that traffic was not just visual flavor; it was the primary feedback signal for whether a restaurant was succeeding. Customers had to plausibly travel from their locations in the city to the player’s restaurants, and that travel had to respond to changes the player made, such as opening a new location or adjusting prices. The simulation had to be reactive, not just decorative.
This ruled out the pure statistical model. A diffusion-based system would show traffic density responding to road connectivity, but would not naturally model a customer choosing between two competing restaurants based on distance and quality. Some form of destination assignment had to happen, which meant some form of path computation had to happen, which meant the cost of that computation had to be managed carefully.
The techniques described in the pizzalegacy.nl writeup reflect this requirement. Path computation was amortized over time rather than running constantly, with agents following pre-cached routes and only replanning when their circumstances changed. The visible city traffic served as a dense representation of a simulation that was actually sparser than it appeared.
Fixed-Point Arithmetic Throughout
One detail worth noting explicitly is that floating-point math was essentially absent from DOS game simulations of this period. The 486SX, the budget variant of the processor that many users actually owned, had no FPU at all. The 486DX had an integrated FPU, but floating-point multiply still ran at 16 to 24 cycles compared to 13 cycles for a 32-bit integer multiply. Developers used fixed-point arithmetic pervasively: positions as 16.16 fixed-point values, distances computed with integer approximations of Euclidean distance (often |dx| + |dy| or a lookup-table-corrected octagonal approximation), trigonometric values read from 256-entry byte lookup tables.
A tile-based road graph with directional bitmasks looked something like this in practice:
/* Connectivity flags packed into a byte */
#define ROAD_NORTH 0x01
#define ROAD_SOUTH 0x02
#define ROAD_EAST 0x04
#define ROAD_WEST 0x08
typedef struct {
uint8_t road_flags; /* N/S/E/W connectivity */
uint8_t traffic; /* density 0-255 */
uint8_t zone_type; /* residential/commercial/etc */
uint8_t reserved;
} Tile; /* 4 bytes, cache-line friendly */
Tile map[MAP_HEIGHT][MAP_WIDTH]; /* flat array for sequential access */
The data structures were designed around cache behavior because misses were so expensive on the memory subsystems of the period. A 128x128 tile map at 4 bytes per tile fits in 64KB, well outside the 8KB L1 cache but small enough that working sets for a local pathfinding region stayed warm.
The Broader Pattern
What the Pizza Tycoon traffic system illustrates, and what the pizzalegacy.nl retrospective surfaces, is that simulation quality in this era was almost entirely about choosing the right representation. A simulation that modeled the right quantities at the right granularity, with the right update frequency, could produce convincing behavior within a budget that a single naive pathfinding call would exhaust. The developers who built lasting systems in this period found ways to minimize required computation while still producing an output the player could read and respond to.
The same principle recurs in modern game development, described with different vocabulary. Entity-component systems, spatial hashing, hierarchical LOD for AI updates, and deferred simulation ticks all address the same underlying problem: simulation cost scales with agent count and update frequency, and the goal is to maximize perceived fidelity per unit of compute. The hardware numbers are several orders of magnitude different. The structure of the solutions is not.