Skip to content

v3: Host Failover

This scenario continues from the host topology session: a running host + webrtc star loses its host. The server signals the departure with PlayerLeft, re-elects a host over the remaining members, and sends every survivor a fresh SessionPlan with the new host and fresh per-recipient ICE.

Starting state — a finalized host + webrtc session:

  • Alice, id 00000000-0000-0000-0000-00000000000a — the current (elected) host.
  • Carol, id 00000000-0000-0000-0000-00000000000c — a client.
  • Dave, id 00000000-0000-0000-0000-00000000000d — a client.
  • Room 11111111-1111-1111-1111-111111111111, code ABC123, supports_authority: true.

All three negotiated v3 plus the host topology and webrtc transport, so all three are electable.

1. The host departs

Intent: the host disconnects (an explicit LeaveRoom, or a dropped socket). Either way, the server removes Alice from the room and notifies the remaining members.

If Alice left cleanly she sent:

JSON
{
  "type": "LeaveRoom"
}

The server signals the departure to v3 survivors with PlayerLeft plus Alice's terminal relay watermark:

JSON
{
  "type": "PlayerLeft",
  "data": {
    "player_id": "00000000-0000-0000-0000-00000000000a",
    "epoch": 1,
    "final_seq": 0
  }
}

Here Alice sent no relayed game data in this incarnation, so final_seq: 0 lets each client retire her accounting cursor immediately. V2 recipients see the frozen player_id-only projection.

Both Carol and Dave receive this PlayerLeft.

Next: the departed Alice was the session host, so the server detects that the stored host is now invalid and re-elects.

2. The server re-elects a host

Intent: the session's topology and transport are sticky for its lifetime, so the server does not re-run the selection ladder. It only re-elects a host over the members that can still run the session — those that negotiated v3 plus the session's host topology and webrtc transport. Among them the rule is authority preferred, else earliest joiner, with a smaller-UUID tie-break.

With Alice gone, the remaining electable members are Carol (...000c) and Dave (...000d). Neither is the room authority now, so the tie-break falls to earliest joiner; assume Carol joined first, so Carol is re-elected host.

There is no distinct "host changed" message — the re-election is communicated entirely through a fresh SessionPlan to each survivor.

Next: the server mints fresh per-recipient ICE and sends each remaining v3 member a new plan.

3. Survivors receive a fresh SessionPlan

Intent: each remaining member receives a re-issued SessionPlan — same topology and transport, new host, fresh ICE. The plan supersedes the previous one: clients apply the latest plan and reconnect per peers[].initiate.

The new host, Carol, receives a plan listing the remaining client(s) — here just Dave — with initiate: false (the host answers each client):

JSON
{
  "type": "SessionPlan",
  "data": {
    "generation": "00000000-0000-0000-0000-00000000000e",
    "topology": "host",
    "transport": "webrtc",
    "host": "00000000-0000-0000-0000-00000000000c",
    "peers": [
      {
        "player_id": "00000000-0000-0000-0000-00000000000d",
        "player_name": "Dave",
        "is_authority": false,
        "initiate": false
      }
    ],
    "ice_servers": [
      { "urls": ["stun:stun.l.google.com:19302"] },
      {
        "urls": ["turn:turn.example.com:3478"],
        "username": "1700009900:00000000-0000-0000-0000-00000000000c",
        "credential": "Cf9aZ8b7Yc6Xd5We4Vf3Ug2Th1Si0Rj="
      }
    ],
    "fallback": "relay"
  }
}

The remaining client, Dave, receives a plan whose single peer is the new host Carol, initiate: true, is_authority: true:

JSON
{
  "type": "SessionPlan",
  "data": {
    "generation": "00000000-0000-0000-0000-00000000000e",
    "topology": "host",
    "transport": "webrtc",
    "host": "00000000-0000-0000-0000-00000000000c",
    "peers": [
      {
        "player_id": "00000000-0000-0000-0000-00000000000c",
        "player_name": "Carol",
        "is_authority": true,
        "initiate": true
      }
    ],
    "ice_servers": [
      { "urls": ["stun:stun.l.google.com:19302"] },
      {
        "urls": ["turn:turn.example.com:3478"],
        "username": "1700009900:00000000-0000-0000-0000-00000000000d",
        "credential": "Dg0bA9c8Zd7Ye6Xf5Wg4Vh3Ui2Tj1Sk="
      }
    ],
    "fallback": "relay"
  }
}

Next: Dave tears down its dead connection to the old host and offers to the new host Carol (Signal{to: Carol, generation: G, Offer}); Carol answers using the same new generation. The exchange is identical in shape to the host topology step 2, just with Carol as the host. ICE trickle and TransportStatus reporting proceed as before.

Notes

  • The relay floor never closed. While the new WebRTC edges are being re-established, Dave and Carol can keep exchanging GameData over the relay (the universal fallback). Failover never interrupts the session's data path.
  • If no member qualifies, the server sends every remaining v3 member an explicit Relay/Relay reset with a fresh generation and no host, endpoint, peers, or ICE servers. The session ends and the room stays on relay. For example, if the only other member were a relay-only member (possible only through a reconnect with downgraded capabilities — new seat-fills into a running session are rejected with ROOM_SESSION_INCOMPATIBLE), the server would not name it host of a session it cannot run.
  • An ex-host that reconnects is paired as a client of the re-elected host, because the stored host is now the new one. The reconnecting member learns this from the fresh SessionPlan it receives on entry (see the reconnection scenario).