Lantern Valley: a cozy WebGPU game, and the cuts that finished it
· 4 min read · game-dev, three.js, webgpu, game-design
Lantern Valley is a free cozy adventure that runs in the browser, on desktop and on phones, and installs as an app. You play Tina, a young apprentice knight who hunts the Sparks that scattered before the Night of a Thousand Lights, relights the Great Lanterns of the villages and carries the flame back to the lighthouse.
It runs on Three.js with WebGPU (with an automatic WebGL2 fallback) and TSL, and it has more than 16 hours of missions and side quests. I built it in about 17 days: the first commit is from September 26 and the latest from October 13, 251 commits in total.

This post is not about shaders. It is about the two things that mattered most for finishing a game that size in that time: cutting the plan, and being able to test the story without playing it.
From ten islands to five #
The first design was an archipelago of ten islands and fifteen tools, with a roadmap of many phases. At some point the game had already grown larger than my pace of delivery.
So I wrote a short document with the decision, and the decision was to close the story instead of stretching it:
| Before | After |
|---|---|
| Ten islands | Five: the Valley, the Tide Atoll, the Golden Orchard, the Mushroom Grove and the Lighthouse Fair |
| Fifteen tools | Eleven |
| Open-ended roadmap | A closing "Final" screen that ends the story |
Two things made the cuts cheap.
Every cut tool already had an equivalent in the game. The cloth wings were redundant because the helmet's wings already allowed the short flight and the triple jump. The flute was redundant because the musical mushrooms in the Grove are played by stepping on them. The spyglass and the camera were redundant because the photo mode already existed. The forge bellows was redundant because the final Lantern comes from the Fair, not from a forge.
The story became a line. With five islands the route is linear: the Valley opens the Atoll and the Orchard, the Orchard opens the Grove, and when three lights are lit the mist opens for the Fair. The Fair is the finale, and the Night of the Feast is where all the lights come together.
I did not delete the notes for the cut islands. They stayed in the repository with a "CUT" label at the top, as a reference. And nothing in the cut changed the Valley, the Atoll or the Orchard, so old saves kept working with no migration.

A virtual player that plays the story #
At around 40 quests (the plan said about 130) I could no longer test "mission by mission" by hand. Every new thing depended on a previous one: the boat needs the fishing quest, the planks need the boat accepted, and so on. A game like this is a graph, not a line.
The answer was a development tool I call the Quest Lab:
- A virtual player that plays the story through the real quest engine, not through a copy of it.
- A checkpoint before every step of the intended walkthrough, loaded as a real save.
?cp=vale.boat@s3opens the game with the boat repaired and not yet launched, with everything before it done. - A panel to jump to any step, finish a step live with its ceremony (the lantern lighting, the gate opening), see why a quest is still locked and keep named snapshots.
The intended order is data: a list of steps per island. A test plays that whole list through the real engine, so a quest missing from the list, listed twice or impossible to reach in that order breaks npm test.
The Quest Lab only exists in development builds. A release test proves that the production build has none of it.
The small things that make it feel good #
Most of the "feel" is in a few small places, each with its numbers at the top of the file:
| Detail | What it does |
|---|---|
| Coyote time | You can still jump for 0.11 s after walking off a ledge |
| Jump buffer | A jump pressed 0.12 s before landing fires on touchdown |
| Hit stop | The world freezes for 45 ms on a blow and 35 ms on the mallet |
| Slow motion | 0.3× speed for a rare catch |
| Camera shake | Based on "trauma": the offset follows trauma squared, with a touch of roll, and landings scale dust, squash and shake with the fall |
| Sound variation | Every effect varies ±4% in pitch and ±1.5 dB in volume |
| Pickup ping | Climbs a step for each pickup in a row |
One rule makes hit stop and slow motion work: they only slow the world's clock. The interface, the camera and the audio stay on real time, so the game feels punchy instead of laggy.

Play it #
Lantern Valley is free at lanternvalley.vercel.app, and the project page here has the video: Lantern Valley.
The lesson I keep from it is that a deadline is a design tool. Deciding what the game would not be, early and on paper, did more for the finished game than any effect I added.
Related projects
- Lantern Valley — Cozy adventure game for the browser