How SimCity Squeezed a Whole City Simulation into 16 MHz

Translated by a model; the original is in Russian.

· Technology
The original SimCity ran on an Intel 386SX clocked at 16 MHz. The trick behind it still matters for modern games: don't run all your code every frame.

cover.svg

Modern hardware has spoiled us: even the cheapest CPUs now pack at least four cores and churn through billions of operations a second. The idea that real software once ran below a gigahertz feels like fiction. Yet whole games once lived on hardware a thousand times weaker than that.

When people recall the technical feats of retro games, they usually point to graphical milestones like Doom or Quake. The author, though, is far more impressed by early city builders: simulating a whole city's economy on a sliver of today's power seems impossible, given that even modern builder games can struggle on powerful machines.

A 386SX at 16 MHz

The original SimCity listed Intel's 386SX among its requirements, a chip that ran at 16 MHz. (The author notes that an earlier version of the piece mistakenly said 12.5 MHz — that was the clock of the base chip, not the SX variant.) So how did Maxis run a full city simulation on such hardware, when today even trivial idle games demand gigahertz?

There are many reasons, most of them external — the OS ecosystem and so on. But the game's own code holds some clever tricks too. Fortunately, SimCity's source was released under the GPL in 2008 as Micropolis (only the code was opened, not the trademark, hence the name), so we can study it in detail.

One frame, not the whole simulation

The heart of the simulation sits in a single function, Simulate(), in s_sim.c. It is called every frame and in turn invokes updates for individual systems — crime, fire, tax collection, and so on. The problem: running them all every frame would have crushed a 1989 CPU.

The solution is elegant: SimCity simply skips most of the simulation. The function takes a mod16 parameter that cycles through 0 to 15 and decides which part of the simulation runs on a given frame. The load is smeared across sixteen frames.

How does this affect gameplay? Barely at all. On average an aspect reacts to the player's action after eight frames, fifteen in the worst case. For a shooter that input lag would be a disaster, but for a city builder the author argues a gentle response may actually feel better than an instant one.

The trick is still around

It might seem specific to one genre, but it underlies plenty of optimizations. In the racing game Assetto Corsa (2014), reflections on car bodies use cubemaps, and refreshing all six faces every frame is too costly — a config variable called FACES_PER_FRAME controls how many faces update at once.

You can go further and decide the amount of work per frame at runtime, based on screen size or time since the last update. That's exactly how Unreal's Lumen works: it doesn't recompute all lighting every frame, which explains both its beauty and its occasional oddities with half-updated reflections.

The takeaway is simple. Next time you write gameplay code, ask yourself: do I really need to run all of this every frame?


Source: Lobsters

Comments

Sign in, to comment.

  • No comments yet.