
I've been making a small god game for the browser and phones: you are a hand over an island, you scoop and pour sand, water and lava, and a tribe has to survive the wave. A few things I learned building the terrain, in case they help someone:
- The whole land is one 96x96 height field with four layers (rock, sand, water, lava). Water and lava flow by virtual pipes: each cell pushes along four pipes by the height difference, damped. Sand slumps past its angle of repose and rides fast water as silt; lava sets into rock, far faster under water, so pouring lava into the sea builds a wall that does not erode.
- The sim runs in a web worker, and each message carries the time that passed and what the hand did; the page only draws. A step costs about 4 ms on a mid-range Android phone.
- The draw is one WebGL2 pass: a single n x n grid mesh with no vertex buffer (a vertex is its index), heights read from a texture in the vertex shader, the ink-and-pigment washes in the fragment shader.
- The phone taught the hard lesson: a cubic B-spline read per pixel (four bilinear taps, so shores curve instead of stepping per cell) drew 18 fps on a 2020 Android phone. Doing the cubic once a frame into a texture twice as fine, then reading that bilinear, brought it back to 59; phones now take the plain read and fewer pixels, which holds above 54 as the game grew.
Everything (land, tribe, look) is drawn in its own code. It plays free here, no account: https://overflow.games/stillwet/
Happy to go deeper on any part; what would you have done differently?
The phone-side cubic-vs-prefilter lesson is the kind of thing that only shows up once you ship to mid-range Android. Doing the expensive filter once into a denser texture, then reading bilinear, is a pattern I keep rediscovering for any WebGL terrain that has to stay above ~50 fps.
Running the sim in a worker with a message that only carries elapsed time + hand input is also clean. Keeps the page to draw work and makes it obvious when a frame is late because the worker stepped too long, not because the fragment shader got greedy.
Curious whether the virtual-pipe water/lava ever needs a sub-step when someone dumps a big pour, or if the 4 ms budget absorbs that on the devices you care about.