Rill Valley: a multiplayer WebGPU valley in the browser, computed by a functional language
A valley at dusk you walk in the browser: forest, lake, crystals, weapons, voice chat and bots. The terrain, physics and game logic are written in Rill; the same file compiles to WebAssembly for the browser and to a native program for the server, and WebGPU draws the picture.

Rill Valley is a valley at dusk that you walk in the browser: pine and poplar forests, a lake in the middle, crystals glowing on the slopes and Saturn in the sky. Several people can walk the same valley, talk to each other and shoot at each other. The valley itself, meaning the terrain, where the trees and crystals stand, walking, falling, the flight of every round and the camera's matrices, is computed in Rill, the functional language I am writing. WebGPU draws the picture.
Play: rill.baltavista.com/rillwebgpu. You need a browser with WebGPU (Chrome or Edge). Click to capture the mouse; WASD to walk, space to jump, space twice for the jetpack, shift to sprint, C to crouch, left click to fire, right click to aim, 1-4 to switch weapons, R to reload, M for music.

One file, compiled twice
Everything about what the valley is lives in one file: valley.rill, about 8,800 lines. That file is compiled twice:
world.rillexposes 56 functions over it and is compiled to WebAssembly. That is the game engine in the browser, 232 KB.server.rillwraps it in a WebSocket server and is compiled to a native Linux program.
Neither of them decides anything about what the valley is. That is the only reason the server and the browser can agree about where a round went: both run the same code.
In the browser, JavaScript only does what a WebAssembly module cannot: setting up the WebGPU device, loading the shaders and running the render passes in order. Sound, the microphone and the network connection are its job too.
How the valley is made
The valley is not downloaded, it is computed. The terrain is a sum of twelve sines. Where every pine, poplar, crystal and tuft of grass stands is chosen by a deterministic walk over a hash: a candidate, a hash, a rule about the ground, and whatever survives stands where it fell. The server tells each client only three numbers (how many pines, crystals and tufts) and everybody builds the same valley themselves.
The whole valley is rebuilt in about 4 milliseconds. That is what lets the game size itself to the machine: it starts at a quality tier picked from the graphics card, then looks at measured frame times and rebuilds the valley denser or sparser (trees, grass, fireflies, shadow map, wave count, pixel ratio) until the display's frame budget is spent. A slow machine draws a coarser terrain mesh but stands on exactly the same ground, because every physical question is asked of the height function rather than of the mesh.

Drawing
With WebGPU, as a series of passes, each one its own WGSL file:
- A shadow pass from a fixed golden-hour sun.
- A 4x MSAA forward pass with procedural sky and fog.
- Instanced pines, poplars, crystals and grass swaying in the wind. The wind moves the water, the pines and the grass with the same two numbers, because all three read the same values Rill computes.
- A lake of Gerstner waves with shoreline and crest foam. What really makes it read as water is a slow noise field drifting across it and scaling every wave at once: there are patches of chop and patches nearly glassy.
- A swarm of fireflies in a compute shader.
On the machine the screenshots were taken on (Apple M series, top quality tier), the scene is 342 thousand triangles at 60 fps and 4-9 ms of GPU time per frame.
All the textures, models, sounds, shaders and the page's own code travel in one file, valley.rpak: 103 files, 6.1 MB. The format is this project's own. What shrinks is LZ-compressed, and it is the engine in WebAssembly that undoes it, not the browser. A visitor downloads two things: index.html and valley.rpak.
The game
The game opens with a briefing: you have been sent to an unknown planet, your mission on behalf of the company is to destroy crystals believed to be a threat to Earth, and you may run into agents of rival companies given the same mission.
- Crystals: there are three colours and the colour says everything. Amber is the most common and heals the player 3% when broken, cyan 6%, violet (the rarest) 12%. Rarity and value run in the same order, so a colour seen at two hundred metres becomes a reason to cross the valley. The crystals also throw fire: meteors that fly, slow down, fall and leave a crater that burns whoever is near it.
- Weapons: a bolt-action rifle, an RPG-7, an AK and an MG3. A rifle round leaves at 420 m/s; gravity and drag slow it the whole way, so its drop grows faster than the schoolbook parabola. Aiming zooms the view five times and the scope fills the screen. The bolt is worked in four separate movements after every shot, and the spent case flies over your shoulder.
- Launcher: a rocket goes off on the first solid thing it meets and takes the crystals within six metres with it. The loaded rocket shows in the tube; it is gone once fired and does not come back until the next one is loaded.
- Gravity: a server setting, not a constant. The valley's default is 26 m/s², about two and a half times Earth's. The legs push with the same force on every planet; a weaker planet does not make them stronger, it lets the same push carry further.


With other people
The server is one strand per connection and one hub strand that owns the world. Only the hub touches the world and everything else is a channel, so there is no lock anywhere in the server. A tick is a message on the same channel as the key presses, so an input lands either before a tick or after it, never inside one.
- 20 ticks a second, a binary protocol. A snapshot is 31 bytes plus 37 per player. The protocol was text while it was being got right; when it went binary it kept the letters as opcodes, so a hexdump still reads: a snapshot still starts with an
s. - The client walks too. The browser walks itself with the same
walkfunction the server runs, so turning and moving answer the key rather than the round trip. The position from the server is eased in over a few frames. Aim is never corrected. - Nothing a client sends is believed. Every number is checked finite (a player at a
NaNposition is hit by nothing), a shot's direction is made a unit vector, and a round can only go within 20 degrees of where its owner is looking. - Limits: 150 messages a second per connection, 64 connections at once, at most 16 from one address.
- Names: every player gets a token on their first visit; the browser keeps it, and the name it took is refused to everybody else, even while its owner is away.
- Voice: players can talk to each other and the sound does not go through the server. Two browsers set up a WebRTC connection of their own; the server only introduces them.
- Guardians: armoured bots that are not people walk the valley too; they walk the slopes, glow where the light hits them and leap only to strike.
Four valleys are open right now (kuzey, güney, doğu and batı: north, south, east and west); the page asks which one you want when it opens. Each runs in its own Docker container and uses about 5 MB of memory. With ?solo the page opens a valley of your own with nobody else in it; there is no server and everything runs in the browser.

In numbers
valley.rill (the valley itself) | ~8,800 lines of Rill |
server.rill (the multiplayer server) | ~2,300 lines of Rill |
host.js (the WebGPU scene) | ~4,800 lines of JavaScript |
| Shaders | one WGSL file per pass |
world.wasm | 232 KB |
valley.rpak | 103 files, 6.1 MB |
| Memory per server | ~5 MB |
Credits
The textures, models and sounds come from CC0 sources: textures from Poly Haven and ambientCG; the rifle from Poly Haven and the RPG-7 from OpenGameArt; the human figure and its animations from Quaternius' Universal Animation Library. The music is pauliuw's The Field of Dreams (OpenGameArt, CC0). None of it is used as it came: the textures are daylight photographs and the valley is at dusk, so each was reworked for the valley's light.
Rill Valley is still a demo. It runs without trouble, but it has not been through anything harsher than an afternoon of being shot at. If you drop by and something breaks, let me know.