UYLLII

Blog › Engineering

Engineering

My puzzle game used more battery than YouTube: an idle screen should draw zero frames

By David3 min read
Moonlit Garden feature graphic

Moonlit Garden is a calm pipe puzzle. You rotate tiles so lantern light flows through pipes to every flower. It's untimed, quiet and slow, the kind of game you play in bed.

Then I looked at my phone's battery screen. Moonlit Garden was the third-biggest drain on the phone: 7% in 38 minutes on screen, plus 26 minutes of drain in the background. Ahead of YouTube. For a puzzle where nothing happens until you tap.

Cause 1: endless animation inside blurred layers

The board looked lovely: a light layer with three drop-shadow filters and a screen blend mode, a soft flicker on the lanterns, and every lantern halo (a 9-pixel blur) gently pulsing. Underneath, a full-screen ambient drift.

Every one of those animations ran forever, and they ran inside filtered layers. When anything inside a blurred or filtered layer changes, the browser has to repaint the whole filtered area. So an idle puzzle, with no input and nothing changing, was redrawing the entire blurred board 60 times a second.

Cause 2: an audio context that never slept

The game creates a Web Audio AudioContext on the first sound. It never suspended it. An active AudioContext keeps the phone's audio output open, which keeps the device from fully sleeping, including in the background after you've switched apps. That explained the 26 background minutes.

The fix

Why 30 seconds and not 0.3? Waking an AudioContext takes about 0.3 seconds. Suspend too eagerly and the next tap's sound plays late, which feels broken. Thirty quiet seconds is the compromise: no drain while you think, instant sound while you play. (The first fix used 0.3 s, and I had to put it back.)

Measuring every game

If one game had this, others might. So I built a measuring tool. It loads a game's browser build in headless Chrome, emulating a phone, optionally taps into a screen, then sits idle and reports:

The results were humbling:

Before the sweep, six games idled at 48–60 frames per second on their home screens.

The rules now

  1. An idle puzzle screen draws 0 frames.
  2. Never put an endless animation inside, or on, a filtered or blurred layer.
  3. Suspend every AudioContext when quiet, and always in the background.
  4. Motion that makes a game good can stay, but cheaply: transform and opacity only, on a small unfiltered element, paused when hidden. Genesis keeps its bobbing icon, because the movement is part of its charm.

Each game's browser test now fails on an endless animation or a loop running on a static screen, and each check was shown to fail on the old code before it was trusted.

Rolling it out

Fifteen games needed a release. Rather than flood Google's review queue, I wrote a small release queue: a cron job that ships scheduled updates each morning (tests, build, publish at 100%, commit), and sends a notification to my phone. I'll write about it separately. I haven't yet measured the real-world effect on a phone's battery properly. That's next, now that every update is out.

Moonlit Garden screenshot 1Moonlit Garden screenshot 2Moonlit Garden screenshot 3
Moonlit Garden on Android.