Devlog8 min read

How Our Room Codes Work: Supabase Realtime and WebRTC

How a four-character code connects up to four browsers in Last Ember: Supabase Realtime finds the host, WebRTC carries the game, and a relay catches the rest.

By Mutiny Games
How Our Room Codes Work: Supabase Realtime and WebRTC

When you type a room code into Last Ember, Supabase Realtime finds the host, the two browsers set up a direct WebRTC data channel, and from then on the game runs peer to peer. The host's browser runs the whole world and sends snapshots; everyone else sends inputs. If a direct link cannot be made, the same Supabase channel becomes a rate-limited relay. This post walks through each step with the real numbers from the code.

The short version, from the comment at the top of the networking file: Supabase Realtime is only used for players to find each other and as a backup relay. Gameplay traffic goes straight between browsers over WebRTC data channels: fast, and free.

What is in a Last Ember room code?

A room code is four characters picked at random from 31: the letters A to Z and the digits 2 to 9, minus I, L and O. That leaves out every character that looks like another (I and L next to 1, O next to 0), so nobody has to ask what you meant when you read a code aloud on a call. Four characters from 31 gives 923,521 possible codes.

When you join, the game strips spaces and anything that is not a letter or digit and makes it upper case. If what is left is not four characters, it asks you for the 4-character room code instead of trying to connect.

How does a host claim a code?

The host joins a Supabase Realtime channel named after the code (`le:room:` plus the code) and checks who is already there. Supabase Realtime Presence lets every browser in a channel announce a small state. The host waits 400 ms, looks for anyone already marked as host, and if the code is free it marks itself as host.

If the code is taken, the game picks a new code and tries again, up to four times. With 923,521 codes, a collision is rare, so this almost never runs more than once.

How does a player find the host?

The joining player subscribes to the same channel, marks itself as a client, and checks presence every 200 ms looking for the host. If no host shows up within 6 seconds, the game tells you no room has that code.

Once it finds the host, the client sends a hello. Messages in the channel carry a from and a to, so the host and each client can hold private conversations on one shared channel. If the host has not answered after 3 seconds, the client says hello again.

How does the WebRTC connection get set up?

WebRTC lets two browsers talk directly, but they need a go-between to swap connection details first. That go-between is the Supabase channel. The sequence goes like this:

  1. The host makes an offer. It creates a peer connection and a data channel, then sends a session description (an offer) to the client over Supabase.
  2. Both sides gather candidates. Each browser asks public STUN servers (two of Google's and one of Cloudflare's) for its public address, and sends every network candidate it finds to the other side, again over Supabase.
  3. The client answers. It accepts the offer and sends back an answer.
  4. The data channel opens. Once a route through both networks works, game traffic flows straight between the browsers. Supabase is no longer in the path.

Candidates that arrive before the offer or answer are held in a queue and added once the connection is ready for them, so the order messages arrive in does not break the handshake.

Why an unreliable data channel?

The game channel is set to unordered with zero retransmits. That makes it behave like UDP: a lost packet is simply gone. For a game that is what you want. The host sends a fresh snapshot many times a second, so waiting to resend an old one would only make the picture later. The next snapshot replaces it.

What does host-authoritative mean in Last Ember?

Host-authoritative means one browser, the host's, runs the real game and everyone else trusts it. Clients never change the world themselves. They send what they are pressing, and the host decides what happened.

  • Snapshots: the host sends a snapshot of the world at most every 50 ms, so up to 20 per second. Each one packs every enemy into a few numbers: an id, a type, a position, health as a percentage, and a set of flags for states like elite, frozen, stunned or enraged.
  • Prediction: the client predicts its own survivor's movement, so your character answers your keys right away instead of waiting for the host. Everything else follows the host's version of the world.
  • One source of truth: two players can never disagree about whether a boss died or who picked up a drop.

The cost is that the host's connection matters most. If your party has a choice, let the player with the steadiest internet host.

What happens when a direct connection fails?

Some networks block direct connections. When that happens, the game falls back to relaying through the same Supabase channel. A player switches to relay when:

  • the data channel has not opened after 8 seconds,
  • the peer connection reports that it failed,
  • the data channel closes while the game is still running, or
  • the browser has no WebRTC support at all.

The relay is capped at one message every 125 ms, so 8 per second, to protect the Supabase message quota. That is slower than the direct link, but the game stays playable.

Backpressure

If a connection gets congested, sending more only makes it worse. Before sending, the host checks how much data is still waiting in each player's buffer and skips that player for this snapshot if more than 256 KB is queued. A client does the same above 64 KB. The next snapshot catches them up.

How are full rooms and leaving handled?

A Last Ember room holds the host plus three, so four survivors. A fifth player who knocks gets a full message. When anyone leaves on purpose, their browser sends a goodbye so the others can react at once. Presence also catches the ungraceful cases: if a player's browser disappears from the channel, the host drops them, and if the host disappears, the clients are told the host left.

Friends can still join a run that has already started, and after a run the host takes everyone back to the lobby. The Last Ember co-op guide covers how lobbies, revives and spectating play out at the table.

How do public games show up?

The Open games list on the Play tab uses a second, shared presence channel (`le:lobby`). Each public host advertises a small card with the code, host name, mode, map, difficulty, how full it is and how far along the run is. The host refreshes that card at most every 12 seconds, so the list stays current without flooding the channel. A private lobby never goes on the list, and a private run is taken off it, so only people with the code or an invite can get in.

How does Mimic connect players differently?

Mimic solves a different problem, so it uses a different setup. It is a social deduction game for up to ten players, and every player needs to see everyone else move around the station, so every player gets a direct line to every other player, not just to the host. It carries no audio: positions and game events only.

  • A full mesh: every pair of players has its own WebRTC data channel, with STUN and a public TURN relay as a fallback. Positions go out about 15 times a second while a player is moving.
  • No backend for finding each other: every player holds one PeerJS peer with a random id. The room code is the id of a second peer, the doorman, held by whoever got there first. Newcomers knock on the doorman, get the roster, and dial everyone.
  • The room survives the host leaving: if the host leaves, the others race to claim the doorman id so the room stays findable. If that happens mid-game, the game ends, because the new host has no roles to work from, and everyone goes back to the Green Room.
  • Dead links get cleaned up: every link carries a ping every 4 seconds, and anything silent for 14 seconds is dropped, so a closed tab or dead phone leaves the room in about 15 seconds.

Like Last Ember, Mimic is host-authoritative for the game itself. Players send what they want to do (silence, report, vote, finish a task, sabotage) and the host checks it against distance, cooldowns and the phase of the game before it counts. Roles and task lists only ever go to their owners. Read how to play Mimic and the Mimic hosting tips for what that means when you run a room.

Last EmberMimic
Finding the roomSupabase Realtime presencePeerJS doorman peer
Game trafficWebRTC data channels to the hostWebRTC mesh between every pair
FallbackSupabase relay, 8 messages per secondPublic TURN relay
Players per roomUp to 4Up to 10
If the host leavesThe session endsAnother player takes over the room

Why build it this way?

  • Cheap to run. Supabase only carries a few handshake messages per player. The heavy traffic goes straight between browsers.
  • Fast. A direct, unreliable channel is the shortest path between two players.
  • Still works on hard networks. The relay is slow, but it keeps the game going.
  • Easy to share. A four-character code is short enough to read out on a call or paste in a chat.

For the other half of how Last Ember is built, see building Last Ember without asset files.

Frequently asked questions

Does Last Ember use dedicated servers?

No. The host's browser runs the game, and other players connect directly to it over WebRTC. Supabase Realtime is used to find each other and as a backup relay.

Why can't I connect to my friend's Last Ember room?

Check the code first: it is four characters, and the game never uses I, L, O, 0 or 1. If no host answers within 6 seconds you get a not found message. If your network blocks direct links, the game switches to a slower relay on its own.

What happens if the host leaves?

In Last Ember the host runs the world, so the session ends and the others are told the host left. In Mimic the remaining players race to take over the room. A game in progress ends, and the new host takes everyone back to the Green Room.

How many players can join a room?

Last Ember allows up to four (the host plus three). Mimic holds up to ten.