outlol is a free multiplayer football game that runs in the browser, in a top-down 2D view or a 3D view, with a foosball table mode as well. You open a link and you are in a live match with real people. This post is about the part that took the longest to get right: moving a physics match between browsers without running the match on a server.
One browser is the referee
Every room has a host, which is simply one of the players' browsers. The host runs the physics and everyone else sends it their inputs. Players connect to the host directly over WebRTC in a star, so match traffic never goes through a server.
The server, a Cloudflare Worker with a Durable Object per field, does three things only: it tracks who is in the room, relays the WebRTC signaling, and picks a new host if the current one leaves. A game tick never touches the Worker or the database.
Each connection has two data channels:
-
control: reliable and ordered, for joins, chat and match events. -
state: unordered, withmaxRetransmits: 0. A late snapshot is worse than a lost one, because a newer one is already on its way.
When a direct connection fails, it falls back to Cloudflare's TURN relay on UDP, TCP and TLS, including ports 53, 80 and 443 for restrictive networks.
Small packets
- An input is 4 bytes.
- A snapshot is binary. Positions are quantized to 1/8 of a world unit, and a 20-player snapshot comes to 288–348 bytes.
The host steps physics at a fixed 60 Hz. It sends snapshots at 30 Hz in big rooms and 60 Hz in rooms of 8 or fewer. In rooms of 6 or fewer, every snapshot goes out twice, the copy six publishes later, so one dropped packet is hidden. With 20 peers the host uploads roughly 160–194 KB/s; with 6 players at full rate it is about 74 KB/s.
Hiding the network
Guests do two things at once:
- Interpolation for everyone else. Remote players and the ball are drawn from a buffer of the last 12 snapshots, a little in the past, so their motion is smooth even when packets arrive unevenly.
- Prediction for yourself. Your own player moves the moment you press a key. When a snapshot arrives, the guest rolls back to it and replays every input the host has not acknowledged yet, at most 24 frames.
Jitter is estimated the way RFC 3550 does for RTP. Three smoothing profiles move the interpolation delay between roughly 50–100 ms and 110–220 ms, depending on how bumpy the connection is.
I considered deterministic lockstep and decided against it. It needs bit-identical physics on every device, and it stalls everyone when one player lags. With one authoritative host, a slow guest only hurts itself.
Physics that feels familiar
The physics aims at the feel of HaxBall's classic stadium: one world unit per unit, every rate per 60 Hz tick, acceleration 0.1 (0.07 while kicking), damping 0.96, kick strength 5. The simulation is written from scratch; only the numbers are matched. The fixed-step clock catches up at most 6 steps per wake, so a background tab never spirals.
Foosball reuses the same pipeline and the same inputs. There are eight rods and eleven men a side. Holding kick draws the men back and charges the shot, and releasing it strikes. Rods are shared out by how many people are on a side, from one person holding all four rods to one rod each.
Drawing it
- 2D: Canvas 2D. The stadium is painted once into a cached layer and only the players and the ball are redrawn each frame.
- 3D: three.js. You play from behind your own player, and spectators watch from the side like a TV broadcast. The lobby is a low-poly city on a globe where each field is a stadium.
Every model is built in Blender or generated by script. Replacing third-party packs cut the map from 1.36M to 0.45M triangles per frame, and the model download from 890 KB to 144 KB gzipped. Phones render at pixel ratio 2 with MSAA: the rule is to draw less, not to draw worse.
Try it
It is free, with no install and no account needed: outlol.bid. Rooms hold up to 24 people, and a link is all a friend needs to join. I'd love to hear how the controls feel on your device, and whether the netcode holds up on your connection.
Top comments (0)