The pizzalegacy.nl retrospective on Pizza Tycoon’s traffic system picked up 292 HackerNews points because someone dug into a 1994 DOS game’s internals and found something worth explaining. Pizza Tycoon, also released as Pizza Connection in some markets, was a business simulation from Software 2000 in which you managed a pizza restaurant chain in a living city. The city had real traffic: individual vehicles navigated road networks and moved through intersections, creating a sense of urban activity. Making that work on the hardware of the time required deliberate engineering choices about what to compute and what to convincingly approximate.
What 25 MHz Meant in Practice
The Intel 80486 DX at 25 MHz was the mainstream processor of the early 1990s. Under favorable conditions it executed roughly 25 million instructions per second, backed by 8KB of on-chip L1 cache. At 30 frames per second, the entire frame budget came to around 833,000 cycles. Sound, graphics, input, AI, and physics all competed for those cycles.
Floating-point was expensive. On the 486 SX, the cost-sensitive variant without a hardware FPU, software-emulated floating-point multiply ran 40 cycles or more. A standard library sin() call ran over 100 cycles. Integer multiply was around 12 cycles; integer divide around 40.
In that environment, any system that sounded computationally expensive demanded a clear account of what could be precomputed, deferred, or replaced with something cheaper that produced equivalent-looking results.
Tile Graphs and Pre-Computed Routes
DOS-era city builders worked on tile grids, and the grid was the first significant simplification for traffic routing. A vehicle’s path through the city was not a curve through continuous 2D space; it was a sequence of transitions along road tiles. Each road tile connected to adjacent road tiles in the cardinal directions, forming a sparse graph. Pathfinding reduced from a geometry problem to a graph traversal.
More importantly, routing did not happen in real-time during normal gameplay. Routes could be computed when the player edited the road network, stored as waypoint sequences, and retrieved at vehicle dispatch. Between waypoints, movement was purely linear: advance along the current segment, check for arrival at the next node, update heading. No search, no dynamic planning per frame.
This two-level approach mirrors what modern navigation software does: precompute the graph of major intersection nodes offline using Dijkstra or a contraction hierarchy, then fill in local movement procedurally at runtime. The expensive computation happens during map edits, when the player is busy drawing roads anyway. The vehicle’s per-frame cost is a position increment and a boundary check.
Time-Sliced Updates
Updating 50 vehicles every frame at 30 FPS is expensive. Updating 8 per frame and cycling through the full list costs one-sixth as much. The visual result is indistinguishable because the perceptual threshold for AI update frequency is low. A car moving at city speeds and updated 5 to 8 times per second reacts to events with about 100 to 200 milliseconds of delay, which is imperceptible during normal play. Visual motion appears smooth because position interpolation between updates is linear.
This technique goes by several names: staggered updates, distributed simulation, time-sliced AI. It appeared in virtually every DOS game with multiple independent agents. The tradeoff is reaction latency. A vehicle heading toward a road the player just removed might briefly continue in the wrong direction before its next update fires. In practice this was barely noticeable and never a meaningful correctness problem.
Modern games with large agent populations use the same technique, sometimes called “LOD for AI” or “budget-based AI updates.” The principle is identical; only the scale differs.
Fixed-Point Arithmetic and Lookup Tables
Any per-frame calculation that looked like it required floating-point was done in fixed-point, typically in 16.16 format: a 32-bit integer where the upper 16 bits represent the integer part and the lower 16 the fractional component. Addition and subtraction work identically to normal integers. Multiplication requires a right shift by 16 after the multiply to restore the correct scale.
// 16.16 fixed-point vehicle position update
int32_t x_pos; // upper 16 = tile X, lower 16 = sub-tile fractional progress
int32_t speed; // fixed-point speed in the same unit format
x_pos += speed; // one integer ADD, no FPU instruction needed
Direction calculations that would normally require trigonometry used lookup tables instead. A 256-entry table of pre-computed sine values cost 512 bytes of memory and reduced any directional velocity calculation to a single array read, around 2 to 4 cycles instead of 100 or more. Vehicle headings were stored as a single byte (0 to 255 over a full rotation), and the table converted that heading into fixed-point velocity components for X and Y movement.
Lookup tables replaced runtime math throughout DOS-era game code: for square roots, for perspective projection, for palette operations, for anything where the input space was bounded and discrete. The memory cost was trivial; the cycle savings across thousands of calls per frame were substantial.
Intersection Control Without Collision Detection
Checking whether two vehicles occupied the same intersection required comparing their positions pairwise. With many vehicles, that comparison grew quadratically in cost. The practical solution was to not do it.
Intersections maintained simple counters representing the number of vehicles currently passing through. A vehicle approaching an intersection checked whether the counter was below a threshold. If so, it incremented the counter and proceeded; if not, it entered a waiting state. Exiting vehicles decremented the counter.
This is not physically accurate. Multiple vehicles could be in transit through the same intersection simultaneously under this model. But the visual result was plausible, and congestion appeared naturally: when traffic was heavy, more vehicles hit the waiting state and backed up behind busy intersections. The jam-like behavior emerged from the counter arithmetic without any explicit detection or jam-formation logic.
What SimCity and Transport Tycoon Chose Instead
SimCity (1989, Maxis) made a more radical simplification: no individual vehicles at all. Road tiles maintained traffic density values updated by polling residential and commercial zones. Vehicles on screen were cosmetic sprites drawn proportional to density, not agents with routes or internal states. The result was efficient and scaled to any city size, but it felt statistical. Players could observe aggregate traffic patterns but could not follow a specific car or understand why a particular block was congested.
Pizza Tycoon committed to individual vehicles and paid the engineering cost for that choice. The richer visual result required the architecture described above.
Chris Sawyer’s Transport Tycoon, also released in 1994 and written almost entirely in hand-optimized x86 assembly, solved a related problem by constraining the domain. Vehicles followed explicit route sequences through predefined track segments, with each segment limited to single occupancy. Collision avoidance was structurally impossible: an occupied segment could not be entered. The approach was extremely efficient, but it worked because transport networks are sparse and structurally constrained. An open road network traversable in arbitrary paths required something more flexible.
What the Constraint Produced
The consistent result across all these systems is that hardware constraint forced clarity about what traffic simulation needed to accomplish. The target was a convincing impression of a living city, responsive to player actions. Physical accuracy and transportation-engineering-grade statistical fidelity were beside the point.
Those goals are achievable with a fraction of the computation that naive simulation requires, if the architecture is chosen to match them. Tile graphs make routing tractable. Time-sliced updates let agent count scale without proportionally scaling frame cost. Fixed-point arithmetic eliminates the most expensive operations. Counter-based intersection control replaces collision detection with accounting. Each decision trades accuracy for an approximation cheap enough to run alongside rendering, sound, and everything else the game needed to do in 833,000 cycles.
Modern city builders like Cities: Skylines run full A* pathfinding for hundreds of thousands of individual citizen agents on hardware many orders of magnitude faster than a 486 DX/25. They still use level-of-detail tricks for distant agents, still cache routes between major nodes, and still budget AI updates per frame. The performance envelope changed enormously. The design principle that drove the original choices did not.
The specific engineering in Pizza Tycoon’s traffic system is a clear case of that principle applied under severe constraint. When the frame budget is tight enough, the definition of what has to be computed becomes a deliberate decision, and the answers tend to be durable.