Lighting the fire

How the campfire on this site works, and why it is drawn at a fraction of the resolution you are looking at.

  • graphics
  • meta

The fire behind this page is not a video, a GIF, or a shader. It is the same trick Doom used in 1993 to draw the flames on its title screen, running in a canvas about a quarter the width of your browser window.

A field of heat

Keep a grid of numbers. Each one is how hot that cell is. Seed the bottom row at maximum heat, then, working upward, give every cell the heat of the cell below it, minus a small random amount:

for (let y = height - 1; y >= 1; y--) {
  for (let x = 0; x < width; x++) {
    const heat = field[y * width + x];
    const drift = (Math.random() * 3) | 0; // lean left, straight, or right
    const decay = (Math.random() * 2) | 0; // cooling is stochastic
    field[(y - 1) * width + clamp(x + drift - 1)] = Math.max(0, heat - decay);
  }
}

That is the whole simulation. The randomness in decay is what makes the flame taper as it rises, and the randomness in drift is what makes it lick sideways. Run the two together and you get something that never repeats and never looks mechanical.

Colour comes last: each heat value indexes a palette that runs from black through deep red, coral and orange to a white core. Change the palette and the same field becomes smoke, or water, or a portal.

Why low resolution matters

The buffer here is roughly one art pixel per four screen pixels, blitted up with smoothing off. That is not nostalgia for its own sake — it is doing real work:

  • Banding becomes a feature. A radial gradient rendered at full resolution is a smooth wash. Rendered small and scaled up, it breaks into discrete rings, which is exactly what firelight looks like in pixel art.
  • It is cheap. The flame is about 1,700 cells. Everything else in the scene is a handful of rectangles. The whole thing runs at 24 frames a second and stops entirely when the tab is hidden.
  • Everything lines up. Trees, embers and shadows all land on the same grid, so nothing looks half-pixel blurry against anything else.

The rest of the clearing

The flame drives everything around it. Each frame the total heat in the field gets normalised into a single number, and that number becomes the radius of the light pool on the ground, the length of the shadows the trees throw outward, the brightness of the rim light on the trunks nearest the fire, and a CSS custom property that a few elements on the page read so their glow breathes in time with it.

The treeline is generated once from a fixed seed, so it is the same clearing every time you visit.

If you have asked your system for reduced motion, the simulation is stepped 120 times to let the flame settle, drawn once, and then left alone.