Skip to content

devlog

Chrome kept shrinking the game into a corner

2026-06-06

An integrated GPU was rendering the whole scene into the top-left quarter of the tab. I spent an hour blaming the GPU. It was one CSS call, and Firefox had been hiding it the whole time.

On an integrated Intel laptop, Chrome kept collapsing the game into the top-left corner of the tab — a quarter-size render floating in dead space. Firefox, same machine, same scene, filled the window like it should. When one browser is fine and the other isn't on the same hardware, the easy story is that the broken one is choking. I went with the easy story for too long.

First guess was device loss. I'd fought a real one before: a weak GPU runs out of memory, Windows resets the driver, the canvas tears down and remounts. I'd already capped the detail ladder and the render resolution on low-end hardware to stop that exact failure, so I went to the logs to confirm it. The cap was firing perfectly, and the corner was still there. Wrong tree.

Second guess was the renderer. The splat sort runs as a GPU compute pass, which a low-end integrated chip has no business doing well, and Firefox falls back to a lighter path — maybe that was the gap. I started drafting a fix to switch renderers per device tier. Also wrong, and it would have shipped a worse-looking render on the weakest machines for nothing.

What I kept skipping past was why Firefox was actually fine. Not better hardware. Firefox just doesn't report its GPU's name, and my tier detector reads that name to decide how hard to push. With nothing to read, it shrugs and calls the machine "mid." Chrome reports "intel gen-9" honestly, drops into the floor tier, and the floor tier renders at 0.75× resolution to save fill rate. That 0.75 was the whole bug.

Here's the mechanism. To render at 0.75×, the resize code sets the canvas's internal buffer to three-quarters of the display size — correct, that's the point of a resolution scale. Then it calls the engine's resizeCanvas to register the new size. Under the fill mode I use, that one function also writes the number straight into the element's CSS width and height. So it shrank the visible element to 75% and pinned it to the top-left, when shrinking the buffer was the only part I'd asked for. One call quietly doing two jobs, and only the second job was wrong.

The fix was to stop routing resolution changes through resizeCanvas and call the graphics device's setResolution instead, which resizes the buffer and resyncs the camera without ever touching CSS. The buffer stays small, the element fills the tab, the browser scales the smaller buffer up. The floor-tier savings survive and the corner is gone.

What slowed me down wasn't the bug, it was the scar. The real GPU crash from earlier in the week made a GPU explanation feel right, so I kept feeding it evidence. Firefox working wasn't proof the hardware could cope — it was proof the bug lived above the hardware, in code the two browsers happen to run differently, and I read that backwards for an hour. The cheap lesson: when two browsers disagree, work out why the working one works before you start fixing the broken one.

The rest of the afternoon was smaller. The mobile start button had been wrapping onto two lines — an absolutely-centred element with no set width gets capped at half the viewport, so the label broke in half; one nowrap and it sat on a line again. The production build was still shipping a console full of debug logging, now stripped at build time. And the Ludlow car finally spawns facing down the square instead of into a wall. None of it worth its own post. The corner was the one that taught me something.