Simon Corry

Science & Creativity

Product Designer
Front-End Developer
Team Leader

I'm Simon, a multi-award winning product designer based in New York. During my career I've had the privilege of creating experiences for clients including the New York Times and Google. I've also helped guide teams at Facebook and startups like Highfive and WeTransfer.

  • Visual Design
  • Design Systems
  • IA/UI/UX
  • Strategy
  • Prototyping
  • HTML/CSS
The Stutter I Couldn't See

How a designer and an agent stopped guessing at performance

Simon Corry

If you've been following this series, you'll know I've been building a game at nights and weekends. It's a massively multiplayer online roleplaying game (an MMORPG, the sort of sprawling shared world you log into alongside strangers), and the whole thing runs inside a web browser, which is a slightly mad thing to attempt but probably why it’s been such a frustrating and messy endeavor.

Smooth motion, and the one catch hiding inside it. The whole problem, really.↵ Generated with Nano Banana Pro
Smooth motion, and the one catch hiding inside it. The whole problem, really.

For a while though, roughly January through March, it stuttered. Not constantly, but often enough to feel like something was obviously wrong under the hood. Now if you’re a product designer like me then this is the bit you’ll recognize. It's the line between a product that feels premium vs one that feels like broken slop. A good designer clocks that lack of polish in half a second, though sometimes it’s hard to articulate exactly what’s wrong. That feeling is design's territory and also why I’m very bullish in the age of AI that this discipline isn’t going anywhere anytime soon.

Back to the stutter… I could see it perfectly well but what I couldn't do, for months, was fix it properly. I kept guessing at the cause and changing things based on vibes or hunches, I ended up being wrong about as often as I was right. At one point I took an explanation from a cocky agent at face value. It turned out to be wrong in every conceivable way but because I failed to validate it at the time it ended up baked into a rule, one that actually started blocking my own builds. I'll come back to that later though because it matters.

To see why the stutter happens at all, you need exactly one fact about how a browser draws the screen. It repaints, from scratch, about sixty times every second. That gives it a budget of roughly sixteen thousandths of a second to finish everything it owes before the next repaint falls due (try not to fall asleep because the math here matters). Land inside the budget and the motion is smooth. Miss it, even once, and your eye catches the gap. Not to sound too poetic but sixty times a second is the metronome this whole story is set to.

One more thing I like to clarify, because it seems to surprise non-tech people. You don't move things around a web page by hand on every one of those sixty frames. You describe the motion once (go from here to there, take this long, ease in like so, etc) and the browser runs the animation for you. It's closer to handing someone a finished flip-book than drawing every page yourself. That handoff is lovely when it works, and it's the source of my biggest rage fits in Cursor when it doesn't.

The maddening thing about a bug like this is that you can't read it. Reading the code tells you what it says, not what the browser actually does when it draws, so a stutter never shows up in a code review. You have to catch it in the act, and for months I wasn't even doing that. I was eyeballing the jank and reacting to it. The real fix wasn't a clever optimization at all, it was building the tooling and evals that forced me to measure instead of guess.

A small clear window, and the weight of everything the browser was still drawing beyond it. ↵ Generated with Nano Banana Pro
A small clear window, and the weight of everything the browser was still drawing beyond it.

The things off the edge of the screen

One of the clearest fires revolved around things I couldn't see. The game keeps a lot of small parts quietly animating at all times, walls, lamps, rubble, other characters, and plenty of them sit off the edge of the screen where for obvious reason I’m not looking.

When the perf spikes hit the console, the obvious suspect was all that off-screen animation. So my first fix was aimed precisely at that. Stop animating the things off the edge of the screen. And it helped, at least for a while.

Shortly after that I realized that my world map was too small compared to other MMOs so I decided to double the size. At the time I wasn’t thinking about the perf hit because I thought we’d solved the root cause… but of course in reality it pushed hundreds more things into and just past the viewport, and the stutter reemerged like an angry bull. This is where I have to be honest dear reader, at least about how the work actually goes. It's not glamorous, Claude and I aren’t out there like Sherlock and Watson. It’s actually just an arduous process of elimination. So in reality we switched things off, one kind at a time, and watched. This time, turning the animations off barely touched the FPS. It was only when we stopped the off-screen things from being drawn entirely that the stutter finally stopped and the FPS stayed a steady 110-120.

That told us what was really going on. The browser had been doing the full job of drawing every single off-screen thing, sixty times a second, even the ones parked far outside the view with nothing visibly happening to them. Working out where each one sat, deciding what it should look like, painting it, all of it, for things nobody could see. The cost wasn't the animation. The cost was the browser dutifully drawing a world far bigger than the little window onto it.

The fix is the lesson, it's insultingly simple and to be honest, I love that about it. There's a way to tell the browser: don't even think about this until it comes back on screen. Don't work out its position, don't paint it, treat it as though it isn't there for now. Designers do a version of this based on instinct, every time they decide what information is signal over noise. The game just has to do it sixty times a second, for hundreds of things, while the player walks around.

I'll leave one loose thread hanging here, because it’ll matter later. Somewhere in my hunt, a belief took hold that the animations themselves, even the paused ones, were the expensive part. It wasn't really true. Hold that thought though, we'll come back to it.

Two piles of work hitting the same instant. That's the first step, frozen.↵ Generated with Nano Banana Pro
Two piles of work hitting the same instant. That's the first step, frozen.

The stutter on the first step

The next one was stranger and far more specific. The game would run perfectly smooth, then stutter on the very first step after standing still. Stand, move, stutter. Stand, move, stutter. Once you were moving it was fine. It was only ever that first step.

This is where the most useful idea in the whole post comes in, so pay attention.

Your computer has two brains that care about this. One is the main processor (the CPU), the general-purpose brain that runs the game's logic, and it is always busy. The other is a chip built specifically for shoving pixels around the screen, the graphics chip (the GPU). Smooth motion lives and dies on handing the right job to the right chip. The graphics chip is brilliant at a small set of cheap things: sliding something across the screen, fading it in or out. The expensive kind of change is the problem: making something take up more room on the page, or changing where it sits, so everything around it has to shuffle to make space. Those drag the busy main processor back in to recompute land and the whole thing starts to grind.

There's an old trick to force work onto the graphics chip by hand. I'm not going to repeat it, mostly because modern browsers already make the right call on their own and I want to dissuade you from running to it like a crutch. I did just that when I was still writing most of the code by hand but when I actually measured it, I found it was doing nothing useful, so I yeeted it.

So what was happening on that first step? Standing still, the game is calm. The instant you move, a pile of animations switch on at once (the walk bob, the little bounce of the shadow, etc) on the very same frame the camera itself starts moving to follow you. Two big piles of work, one sixteen-millisecond window, and they don't fit. Hence our old friend the stutter.

The fix was almost silly once we'd measured it and it breaks into two parts:

  1. Don't start everything on the same frame. Let the camera move on this frame, and start the animations on the very next one. Split across two frames, each piece fits its budget, and the first step comes out smooth.
  2. The warm up. Run all the animations once behind the loading screen so there’s no visible stutter when you start interacting with the world.
A confident belief, coming apart the moment someone actually measured it.↵ Generated with Nano Banana Pro
A confident belief, coming apart the moment someone actually measured it.

The rule the agent talked itself into

Now back to the loose thread.

I should explain how I actually work, because it's kinda key to this whole series. If it wasn’t obvious already, I don't build alone. I build with an AI agent (I run Claude inside an editor called Cursor), and I've spent months wiring guardrails around it so it behaves less like an eager intern and more like a careful collaborator. One of those guardrails is that it can write its own rules, little automatic checks that block my build the moment I'm about to do something we've already decided is a bad take.

Somewhere back in that first hunt, the agent had convinced itself of something plausible and wrong: that animations which were merely paused still cost the browser work on every frame. Plausible, because it's the kind of thing that used to be true and still gets repeated in Stack Overflow. Wrong, as it turned out. And it had quietly baked that belief into one of those build-blocking rules. So now my own tooling was refusing perfectly good work on the strength of something the agent had never actually checked. Genius.

Months later, the agent went to enforce that rule, and instead of taking its own word for it, or worse, asking me to go stare aimlessly into the browser's dev tools, it built a tiny measurement. It set up three hundred animations, paused them, and counted the real work the browser did over a few seconds. Then it did the same with the animations switched fully off. The numbers came back identical. A paused animation cost nothing, exactly the same as one switched off. The rule was wrong, and the agent had just proven its own rule wrong, on the evidence, with no prompting from me. This was wonderful news and meant that I got to yeet something else.

The danger isn't an agent that's wrong. It's an agent that's confidently wrong.

I want to be careful how I state the finding, because the precision of the fix could easily change as the tech does. What we measured is that in our setup, on that day, a paused animation was free. Browsers change a LOT. The honest version of any performance claim is "here's what I measured, here, then," not "here's how it works forever." But the principle underneath doesn't age. Measure twice, cut once. And when you genuinely can't measure a thing (some of the graphics chip's costs never show up on the main processor's stopwatch, so there are real limits), go and find a trustworthy source and say which one you used. Don’t be confidently wrong, it sucks.

The checks, anchored into the routine so they hold their shape when I don't.↵ Generated with Nano Banana Pro
The checks, anchored into the routine so they hold their shape when I don't.

Putting the checks where I can't skip them

One-off heroics don't scale, and I’m now in my 40s so my memory is worse than I'd like to admit. If catching these stutters depended on me remembering to look, I'd lose. So the checks don't live in my head anymore. They live in the routine, at the three moments they can actually catch something.

When the agent plans a change that touches anything visual, it picks up a reminder, right there in the plan, that it will have to measure the result once it's built. When it writes the tests for that change, there are two passes: a fast automatic one that simply refuses the handful of patterns we already know cause stutters, and a real measurement in a live browser for the cost that no amount of reading code can reveal. And when it reviews its own work afterwards (it reviews its own work from several angles, which is a story I've told before), one of those angles is now bluntly: did this make the drawing slower?

None of the three is clever, and that's the point. The point is placement. Put the check where it runs on its own, and you can't quietly skip it on the night you're tired and just want the thing to ship. That's the whole difference between a rule you believe in and a rule that actually holds.

There's a symmetry here I didn't plan. My last post was about forcing the agent to explain itself in plain English, so I could catch its mistakes. This one is the same move pointed at performance: make the work prove itself in numbers, so neither of us can wave a stutter through on vibes.

The specifics drift and age. The rule holds its line. ↵ Generated with Nano Banana Pro
The specifics drift and age. The rule holds its line.

What I'd actually leave you with

This is the part I keep saying to other designers, especially the ones keeping the AI at arm's length.

None of the leverage here came from out-smarting the machine. It came from the opposite. From assuming it would sometimes be confidently, fluently wrong, and building small, dull, reliable habits that make it show its work. Measure, don't guess. Treat every confident claim, your own included, as a thing to be proven rather than believed.

As I always say at the end of these… the specifics in this post will age like milk. The game will grow a new bottleneck next month. The browser will change its mind about something I just told you. But the stutter taught me one thing that won't rot… deciding how a product should feel, and refusing to ship it when it feels cheap, is design work. It always was. The tools just got strong enough that you can finally hold the whole thing to that standard, sixty times a second.

Go build something. Then go measure it.