The first frames always lie: when the speedometer reads the wrong second

← All field notes

While building the day and night cycle for The Long Watch, we wired up a small check: wake a tiny scene, animate the sun for a stretch of frames, then make sure the frame rate is at least fifty. The first time we ran it, it failed hard — seventeen frames a second, against a scene that was essentially empty. The obvious culprit was the thing we’d just added. The obvious culprit was wrong.

An empty room that somehow ran slow

Seventeen frames a second is a stutter you can feel — about thirty-three short of the floor we’d set. And it was being reported for a scene with almost nothing in it: a patch of ground, a moving light, no creatures, no weather, no terrain churning away. We had just taught the lighting to animate every frame, so the first guess wrote itself. Animating the sun must be expensive.

That guess had the appealing shape of a real bug. It pointed at the newest code, it explained the symptom, and it suggested an obvious fix — do less work per frame. We could have spent a day shaving the lighting math and watched the number not move, because nothing we were measuring was actually slow. The reading was honest about what it measured. It just wasn’t measuring what we thought.

What the speedometer is actually reading

The number an engine hands you for frames a second isn’t the speed of the single most recent frame. It’s a rolling average over a window of recent frames — a speedometer that smooths the last little while, not one that reads the instant you glance down. That smoothing is usually a kindness: it keeps the display from flickering between two values that differ only by a hair. But it has a sharp edge, and we’d just walked straight into it.

For the first couple of seconds after a world wakes up, the engine is still warming up. It’s setting up the graphics layer, compiling shaders, doing all the one-time work that only ever happens once per launch. Those opening frames are genuinely slow — tens, even hundreds of milliseconds each, against a steady-state frame of around a sixtieth of a second. They are also, crucially, still sitting inside the averaging window. So the average we read was a blend: a handful of brutally slow start-up frames, mixed with the fast frames that came after, dragging the reported number far below what the world actually ran at once it settled.

We hadn’t measured a slow world. We’d measured a fast world too early — while the slow first frames were still inside the average.

The proof was almost anticlimactic. We let the same scene keep running and watched the number climb on its own, with no code changed. By the time the frame counter reached a hundred and eighty, the slow start-up frames had aged out of the window, and the average reflected the real steady state: a healthy sixty. Nothing got faster. The lie simply expired.

The fix was patience, not speed

So the fix wasn’t to optimize anything. There was nothing to optimize. The fix was to stop asking how fast the world was running until it had actually settled into running. We moved the check to read the rate late — well past the warm-up — and it passed cleanly, every time, against the exact same scene that had “failed” an hour earlier.

The trap has a quiet cruelty to it: the fix is trivial once you’ve named the cause, but the cause is invisible in the failing output. The check told us “seventeen frames a second.” It did not tell us “you asked before the room was warm.” That second sentence we had to supply ourselves, and only the second sentence was true.

There was a small vindication waiting, too. An earlier boot check we’d written had always read its frame rate late — not for any principled reason we’d articulated, just because it felt right to let the scene settle. It turned out to have been right all along, for exactly this reason. We’d been quietly doing the correct thing in one place while walking into the trap in another.


From one bug to a standing rule

One failed check is an anecdote. What made it worth writing down was that we turned it into a discipline. Every performance check in the game now waits until the frame counter is well past start-up before it trusts a frame-rate reading — a deliberate warm-up window, sized so the start-up spikes have cleared the average before anyone reads it. The very next day, when we built the weather systems, every one of their performance probes read the rate late as a matter of course. The rule had stuck before the ink was dry.

Two companion habits grew out of the same insight. A frame-rate number only means anything on real graphics hardware, so these checks refuse to trust a reading taken in a stripped-down, display-less run — the warm-up there never even happens the way it would for a player. And because the warm-up spikes are real, we measure one-off stutters with a separate yardstick that deliberately ignores the opening frames, so the cost of starting up never gets mistaken for a hitch in the middle of play.

The window has to grow with the world

The honest complication is that “well past start-up” isn’t a fixed number — it moves as the world gets richer. When the first long-lived canopy tree arrived, its placement scan front-loaded more work at wake-up, pushing the moment the world reached steady state later; we lifted the wait. When the world grew from two plant species to seven and the population climbed past several thousand plants, the simulation’s first full sweep over every plant — done in small slices, a few each frame — took hundreds of frames to finish, so we lifted the wait again to land safely after that first pass.

Each of those was a change to when we read the speedometer, never to how fast the world actually ran. That distinction matters more than it sounds. It would have been easy, each time, to read a low number too early and conclude the new feature had made the world slower — and to “fix” a slowdown that was never there. The world held a steady sixty throughout. We were only ever adjusting the moment of the reading to keep pace with a world that took a little longer to settle.

Even a settled reading can be fooled

There was one last refinement, subtler than the rest. Even after warm-up, a single reading at one exact frame can be fooled by a momentary hiccup — the machine briefly busy with something else, or a thermal dip — with the number recovering a frame or two later. A check that sampled one instant would occasionally fail for no real reason: a transient that had nothing to do with the game.

So instead of trusting one frame, the checks now take the best reading across a small window of frames near the end of the run. A passing fluke — one unlucky dip — no longer fails the check, because the peak of the window steps over it. But a genuine, sustained slowdown still keeps even that peak below the floor, so a real regression can’t hide inside the window. We’d started by distrusting frames that came too early; we ended by distrusting any single frame at all, and trusting the shape of a short stretch instead.

The whole arc is one principle, learned the hard way and refined three times. A frame-rate number is only honest after the world has warmed up, on real hardware, read across a window rather than a single deceptive instant. The same kind of patience runs all the way through this game — you don’t win, you tend — and it turns out even the speedometer needs it. The trouble was never a slow world. It was a world we asked about before it was ready to answer.

Keep reading

Concept art · pre‑alpha