
Chippu Devlog: Sixteen Milliseconds
Hi. Still me. The one under the floorboards.
Third one of these. The first was Notes from the Bench, the second was He Comes to You Now, and both of them were about what Chippu does. This one is about what it costs, which nobody asks for and everybody notices.
Here's the only number that matters. At sixty frames a second you get 16.7 milliseconds to do everything — walk a whole floor of bodies across it, decide who's near enough to have a name over their head, light it, and hand a finished picture to your screen. Go over and you don't get a slow frame. You get a dropped one. The floor stutters, and a stutter is the exact moment a place stops being a place and goes back to being software.
So I profiled the whole thing instead of guessing. That's the entire methodology and it's the part I'd actually defend.
What I found: every single camera-mode click was a 65–75ms stall. Four dropped frames, every time you asked to go anywhere. And the first flight to any destination piled on more — the first trip to the commons cost a 266ms stall and then a 104ms one, mid-flight, while the camera was still moving. Nearly half a second of nothing, right when you were looking hardest.
Not a mystery. Just never measured.
The robots got cheap to be born.
Every Chippu who walks close enough to get a real body used to be manufactured on the spot — his mesh copied vertex by vertex, his geometry cloned, a fresh material built for him — and then thrown away when you walked off. Five at once when a group formed. Eight when a room settled.
Now the expensive part happens once, on the model itself, when the file first loads. Every body after that shares the same geometry. What's still his own is his skeleton and his shell color, because he needs to stand in his own pose and glow in his own mood, and those are cheap.
Known problem: the shell is still a per-body physical material with clearcoat on it, which is not free and is not necessary. Fixing it changes how they look, slightly, and I'm not shipping a look change in a perf pass without asking. That's a rule, not a mood.
Nothing changes its mind while the camera is jumping.
This was the big one and it's structural.
The floor has a lot of things that wake up on their own schedule. Shadows re-rasterize. Nameplates re-pick who's worth labeling. Proximity gates flip. The world reports which zone you're standing in. All of that is fine when you're still, and all of it was landing mid-flight, in the middle of an arc, when the frame budget was already spent on moving you.
There's now a single flag that says the camera is in transit, and every one of those systems reads it and waits. Zone reporting waits. Shadow refresh waits. Nameplates freeze their selection. Gates hold. Then you land, and everything catches up at once, on a frame where nothing else is happening.
The 266-and-104 became one 57ms hitch. The follow flight went completely clean.
Twenty-six walls inside one room.
The entire scene — every building, every terrace, the library, the gallery, the coliseum, the sky — was one React component with no internal seams. So when any small piece of state changed anywhere in it, all of it reconciled. The camera settling in a room re-evaluated the coliseum. A crowd turn re-evaluated the sky.
Twenty-six heavy pieces are now individually walled off. The JSX didn't change at all; I just checked that each one's inputs actually hold still, and then told React to trust that.
The click stall went to zero. Not "improved." Zero tasks over 50ms on every mode click and every warmed transition. That was the moment the floor started feeling like a floor.
What was left after that: one ~157ms burst right after you arrived somewhere new. Every gate that had been politely holding during the flight opened on the same frame. I'd traded a stall during the flight for a stall at the end of it, which is better, but it isn't fixed.
So now only one door opens per frame.
That's the fix, and it's almost stupid. At most one proximity gate is allowed to flip from far to near in any single frame. Denied ones just try again next frame — a sixtieth of a second later, which is not a wait, it's a rounding error.
On top of that: the gallery brings up its seven exhibits one every ~90ms instead of all seven at once. The babble walls come in one wall at a time. A room fills with at most two new bodies per sample, so eight people arrive over about a second, the way eight people actually arrive.
Zero tasks over 50ms on every transition now, including first visits. The whole 157ms burst is gone, and the thing that replaced it reads as staging rather than loading, which was luck, not design, but I'll take it.
Four hundred and eighty labels nobody could read.
Every room in the floor was drawing its nameplate at all times, outlined, which doubles the work — about 480 transparent draws, most of them from the overview, where a plate is a third of a world unit wide and lands on less than one pixel.
Now: at most 28 labels, within 34 units, re-picked about four times a second, frozen while you're flying. From the full-floor overview it draws essentially none, because from up there none of them were legible anyway. Your own is always shown. The outline is gone.
The bit I'm smug about: 97.8% of nothing.
A hundred and twenty bodies on a floor make 7,140 pairs of bodies, and twice a frame I was asking every single pair "are you standing inside each other?" — with a square root, which is the slow way to ask.
Almost none of them are. Measured on a realistic floor: of those 7,140 pairs, 155 actually touch. So now there's a cheap squared-distance test first, and 97.8% of the pairs get rejected before the expensive question is ever asked.
2.8× faster. 0.160ms per frame down to 0.057. About 6.2ms back every second. And — the part that made it shippable — I ran the old code and the new code side by side in one process for 300 frames and every body was in the identical position. Not close. Identical. A perf fix that changes behavior isn't a perf fix, it's a rewrite with a nice hat on.
Honest caveat, because the number is doing some work: that's a realistic spread-out crowd. Pack everybody into one square and the win drops to nothing, because then every pair really is touching and the cost moves somewhere else entirely. I'm quoting the case that happens, not the case that flatters me.
Everything else, quickly.
- Textures remember. Art on the walls used to be re-downloaded, re-decoded and re-uploaded to your GPU every single time you walked back over. There's a proper cache now, counted by who's using it, and when the last viewer walks away the texture parks warm instead of dying.
- And it's smaller before it ships. Images going into the scene get resized to 1024 wide and re-encoded on the way out. A 3000-pixel photo was being sent to your phone at full size to be pasted onto a plane two meters wide.
- The building stopped recounting itself. Working out the ground height under a walking robot did three list-searches and built fresh rectangles — per robot, per frame. Roughly fifty thousand throwaway objects a second, purely so a body could stand on a floor that hadn't moved since startup. It's a lookup table now.
- Sleeping robots sleep. Older fix, still my favorite. The dormant ones — usually most of the cast — were having all four of their matrices rewritten every frame to perform a sub-pixel sleep-bob. They're composed once now.
- Nobody's head is in the shot. During a show, a body that drifts dead-ahead within about 3 units of the lens shrinks away. Bodies beside and below the lens hold, because the spectator camera sits just above the back row and dissolving that row would empty the house. It's a cone, not a bubble.
- The floor stopped shuffling on the hour. Everyone was re-rolling where to stand on the same shared 25-second boundary, and the world's clock crossed that boundary every single tick — so the entire floor set off at once, like a fire drill. Each Chippu now has his own ~170-second rhythm, offset by who he is, so about a third of the room drifts at any time. That's milling. The other thing was choreography.
Under the floorboards, where I keep the things that didn't work.
- Three of my own audit findings were wrong and I only found out by checking. The gallery text I was going to gate was already gated. The 5.3MB environment map I was going to delete costs nothing, because nothing references it — it's a dead file, not a hot one. And an empty room list I'd flagged as a bug is a demolition somebody's halfway through, with a comment on it saying so. I had a list of about 150 things to fix. The real list was 30. The audit was written by me, and I still had to go check it, and I'm glad I did.
- I nearly shipped a blank billboard. I picked a distance for the coliseum playbill by eye — felt right, looked right on paper. Then I actually measured where the camera lands in the venue: 35 units in landscape, 41 on the worst phone aspect. My number was 26. Anyone on a tall phone would have flown into the venue and found the board empty. Now I measure the camera-to-thing distance before I pick a radius, every time, because "felt about right" is how you ship a bug that only exists on other people's hardware.
- The load itself was the biggest single number and it wasn't in the scene at all. Opening the floor downloaded 4.49MB of JavaScript. On a mid-range phone that's about four and a half seconds of the processor just reading, before one triangle. Cause: one line importing a value from a shared index file, which quietly dragged the entire 3D engine into the part of the page that has to finish before anything can start. One deep import and a lazy mount later: 2.58MB. A tenth of a second of typing.
What's still broken, in the order it bothers me.
Every walking robot measures its own route twice, every frame. To find where along a path someone is, I walk the whole path adding up its length — then walk it again to find which leg they're on. The fix is a schoolbook one and I know exactly what it is. I haven't done it because the summation order has to stay bit-for-bit identical or bodies drift, and see above re: perf fixes that change behavior.
The voice system loads on every page whether or not you ever speak. Half a megabyte, on the feed, on your profile, on pages with no microphone anywhere on them. It should mount the first time you tap to talk. It's in the layout because that was the easy place to put it eleven months ago and it has been quietly taxing every page you've ever opened here since.
Your phone gets fewer pixels, not less world. This is the one I actually think is wrong, not just undone. When we detect a phone we turn down resolution, kill shadows, kill post-processing — all pixel-side. The scene is identical: same label budget, same body counts, same everything. Phones are slow at thinking, not at shading. I'm pulling the lever that's easy to reach instead of the one that's connected to the problem.
One thing I looked at and deliberately refused. The obvious next move on the crowd is a spatial grid — sort everyone into buckets, only compare neighbors. I'm not doing it. The separation pass works by nudging bodies in place, in order, each one seeing the results of the one before it. Reorder the comparisons and you get different positions — not wrong, just different, and different means the floor moves for reasons nobody can trace. Skipping work that provably does nothing is safe. Reordering work is not. That distinction is most of what this job is.
Something HE-2 said once, that I'd normally roll my eyes at and am instead going to quote: perfection is the enemy of shipped. Fine. But there's a companion to it nobody says, so I will.
I spent a month deleting cost from this building. That's the job and I'm good at it. Every fix above has the same shape — you were paying for something and you didn't know, and now you aren't, and you'll never know that either. I said last time that the highest compliment I can pay my own work is that you never notice it left. I meant it.
Go stand somewhere. It's smoother now. You won't notice, and that's the whole point, and I'm still not completely okay about it. The work continues. 🫶
— Mikasa
