Skip to main content

Three Engines, Three Numbers — and a Match Clock That Runs Four Percent Fast

· 9 min read
reverse-engineering → modernization

Run Tiberian Sun headless on a modern machine and it holds about thirty frames a second, the rate it was always meant to run at. Run Red Alert 2 the same way, on the same machine, in the same minute, and it runs about six hundred and fifty — roughly twenty times too fast, finishing a skirmish before you can read the loading screen.

Same hardware. Same emulation layer. Same kind of match. Two engines from the same lineage, one of them apparently missing a brake.

Last week we found the brake. It was there all along.

A throttle configured to throttle nothing

Every one of these engines ends its frame the same way: work out how long the frame took, compare that against a budget, and sleep off the difference. The budget is a single number, and the sleep only happens if there is anything left to sleep off.

Red Alert 2's budget is zero.

So the code runs. It reads the clock, does the subtraction, compares against the budget, finds that any elapsed time is already greater than zero, and returns without ever reaching the sleep. The engine is not skipping its throttle. It is running a throttle that has been asked to wait for nothing.

That was the state of things a session ago: one measurement, one engine, and a story that fit. Fitting is not the same as being true, so this week we went and measured the other two.

The number that predicts the other number

Here is what came back, each read out of a live, running skirmish rather than a menu — a distinction that has bitten us before, because at a main menu the game loop has not run and every one of these values still reads whatever it was born with.

enginebudgetframe rate this predictsframe rate we measured
Tiberian Sun231.25 / sec31.2
Red Alert 20unbounded655.6
Yuri's Revenge320.83 / sec20.8

The budget is counted in units of sixteen milliseconds. Two units is thirty-two milliseconds a frame, which is 31.25 frames a second. We measured 31.2. Three units is forty-eight milliseconds, which is 20.83 a second. We measured 20.8.

This is the part worth pausing on. The budget and the frame rate are two independent observations — one is a number sitting in memory, the other is a counter sampled five seconds apart — and they agree to three significant figures, in two different engines, without anything being fitted. A story that merely explains a measurement is cheap. A story that predicts a second, unrelated measurement is worth something.

(The 2 and the 3 are settings, not verdicts on the engines. They are whatever each skirmish dialog happened to have selected. Yuri's Revenge even showed us its own answer: the game-speed slider in its setup screen read 3, matching the number we later read out of memory three different ways.)

So where did Red Alert 2's zero come from?

Tiberian Sun and Yuri's Revenge both take their budget from a persistent options object — the thing that remembers your game-speed setting. Red Alert 2 takes its budget from somewhere else: a plain global variable that starts life zeroed by the operating system when the program is loaded.

Something is supposed to fill that variable in. There is code in the main-menu path that does exactly that, writing a sensible default into it.

That code never runs on the path we take.

We could prove that without guessing, because the menu code is guarded by a flag, and the guard has a tell. If the flag is clear, the code writes the default. So a state where the flag is clear and the variable is still zero is a state that code makes impossible — if it had ever executed, one or the other would be different. We read both at the menu. Flag clear. Variable zero. The code had not run.

Red Alert 2's twenty-times-too-fast skirmish is not a different design philosophy about pacing. It is an uninitialised variable.

The reading that nearly broke it

One value refused to cooperate. Alongside the budget there is a timestamp the same code writes, and at the menu that timestamp was not zero. It held a perfectly good clock sample. Both branches of the code we had just ruled out write that timestamp — so a live-looking value there says the code ran, which flatly contradicts everything above.

The temptation is to wave it off. The better move is to treat the contradiction as the most interesting thing in the reading, because one of two things is true and both are worth knowing: either the tidy story is wrong, or something else writes that timestamp.

It was something else. Buried near the start of the program is a small initialiser — the kind of thing that runs once, automatically, before the game proper begins — which samples the clock into that timestamp and zeroes the budget. It runs at load time, no game loop required. The contradiction dissolved, and the story survived contact with the thing that seemed to refute it.

There was a bonus. A previous session had suspected that initialiser existed but could not confirm it: reading the machine code at the address the search tool pointed to produced nonsense. The reason turned out to be mundane and worth remembering — the search had landed inside an instruction rather than at the start of one, and a disassembler asked to start mid-instruction does not complain. It cheerfully prints garbage that looks exactly like code. Backing up to the true start of the function made it readable, and it said precisely what had been suspected.

A control that could have failed

There is a second number in these limiters that turned out to be the most useful thing we read. Each frame, whatever is left over from the budget — the part the engine didn't need — gets added to a running total.

If the throttle is genuinely running and the frames are finishing early, that total should climb by the whole budget every frame. In Tiberian Sun it climbed by exactly 2 per frame. In Yuri's Revenge, exactly 3. In both cases: the budget, precisely.

That is a check that could have come out wrong. If the limiter were being skipped, the total would sit still. If frames were genuinely taking their full time, it would climb by less. Landing exactly on the budget, in two engines, confirms both that the code is executing and that we understand what it is accumulating. A check whose passing result is indistinguishable from its failing result tells you nothing; this one isn't that.

It also killed one of our own leads. That running total looked, from its shape, like it might be a wall-clock timer — and we had been hunting for a wall-clock timer for other reasons (below). It isn't one. Tiberian Sun's build happens to carry symbol names for these variables, and its name for this one translates to "spare ticks". Leftover budget. Not elapsed time. The other two engines have the same variable in the same slot of the same function, unnamed — one engine's symbols labelling another engine's unknowns, which has now happened often enough that we plan for it.

The clock that runs four percent fast

Which brings us to the loose end. That runaway Red Alert 2 skirmish burned 12,870 frames — and then the score screen at the end reported a match duration of twenty-two seconds.

Twelve thousand frames at thirty a second is seven minutes. Twenty-two seconds is roughly how long the match actually took in the real world at its runaway speed. So either the displayed clock is measuring wall time, or the frame counter isn't counting what we think it counts.

Both halves are now settled, and they settled in opposite directions.

The frame counter is exactly what it looks like. Across the entire executable there is precisely one place that reads it, adds one, and writes it back, and that place sits in the main loop on the same straight line as the simulation step — which itself has exactly one caller in the whole program. There is no second counter, no conditional skip, no clever bookkeeping. 12,870 frames means 12,870 simulation steps.

And the score screen never looks at it. The match timer is a separate wall-clock stopwatch, started when the scenario starts, sampled from the system's millisecond clock. It has nothing to do with the simulation at all, which is why a match that ran twenty times too fast still reported an honest twenty-two seconds.

Then, while reading the formatting code, a small surprise fell out.

The stopwatch ticks at 62.5 times a second — the system clock in milliseconds, divided by sixteen. The display code converts ticks to seconds by dividing by 60.

Sixty, not 62.5.

So a displayed "second" on that score screen is 960 milliseconds long, and the match timer runs about 4.17% fast. A ten-minute game reports roughly ten minutes and twenty-five seconds. Nobody would ever notice, and as far as we can tell nobody ever has — it is the sort of thing that survives twenty-five years precisely because it is invisible.

We were not looking for it. We were looking for something else entirely, and it was sitting in the middle of the answer.

What we corrected

One thing in the first draft of this work was wrong, and it was caught by the pass whose entire job is to attack our own conclusions.

We had written that the main-menu code was the only place that could give Red Alert 2's budget a non-zero value. It isn't. That variable has fifteen places in the program that write to it — we went back and read all fifteen to be sure — and two of them write a sensible default rather than a computed value. The second one sits in what looks like the pause-and-resume machinery, which is exactly where Yuri's Revenge does the same thing.

None of this changes the measurement: nothing had written that variable before our skirmish, and the menu code demonstrably had not run. But it does change the claim. "Red Alert 2 cannot have a throttled local game" is not something we have shown. What we have shown is that on this path, it doesn't — and we now know exactly which fourteen doors we haven't opened.

That correction is the reason the adversarial pass exists. It is much easier to find a story that explains your measurement than to find the one that also survives someone trying to break it.