Rooms are where players meet. Every room has a short join code, a roster, and a host. This guide covers the full room lifecycle and the events that keep your UI in sync.

The zero-code option: LobbyBrowser

Before writing any lobby code, know that you may not have to: drop a LobbyBrowser node (Create Node dialog → LobbyBrowser) into a scene and you get a complete lobby — player name, Create room, Quick match, join by code, and a live list of public rooms. Connect one signal and it's done:

$LobbyBrowser.entered_room.connect(func(room):
    get_tree().change_scene_to_file("res://arena.tscn"))

It's built from standard Controls, so your project's Theme restyles it, and the Inspector exports cover the common tweaks (quick-match size, default room size, hide-on-join, which widgets to show). When your game outgrows it, everything it does is public SDK API — the rest of this page.

Create a room

await SpawnWeaver.start()

var result := await SpawnWeaver.create_room()
if result.ok:
    show_code(result.room.code)      # e.g. "4V8772" — share with friends

All options are optional:

var result := await SpawnWeaver.create_room({
    "name": "Arena",             # display name
    "visibility": "public",      # "private" (default) or "public"
    "max_players": 4,            # omitted = unlimited
    "metadata": {"mode": "ffa"}, # your own data, visible to joiners and in listings
})

The creator becomes the host. Creating fails with already-in-room if you are already in a room — leave it first.

Join and leave

var result := await SpawnWeaver.join_room("4V8772")
if not result.ok:
    match result.error.code:
        "room-not-found": say("No such room — check the code.")
        "room-full":      say("That room is full.")
        _:                say(result.error.message)

await SpawnWeaver.leave_room()

join_room() accepts a share code (works for private and public rooms) or a public room's id from list_rooms(). Note: max_players counts briefly disconnected members too — a seat inside the grace window is still a seat.

Private vs. public, and listing

  • Private (default): joinable only by someone who has the code.
  • Public: also appears in listings and can be joined by id — this is your lobby browser.
var result := await SpawnWeaver.list_rooms(20)
if result.ok:
    for summary in result.rooms:   # Array of SWRoomSummary
        print("%s  %d/%s players" % [
            summary.name if not summary.name.is_empty() else summary.code,
            summary.player_count,
            "∞" if summary.max_players == 0 else str(summary.max_players)])
        if summary.has_space():
            add_join_button(summary.id)   # join_room(summary.id) works too

Listings return the project's public rooms, most recently active first.

The roster: SpawnWeaver.room

While you are in a room, SpawnWeaver.room is an SWRoom kept up to date automatically — you never mutate it yourself:

var room := SpawnWeaver.room
room.code                 # "4V8772"
room.players              # Array[SWPlayer] — the full roster, including you
room.host()               # the host's SWPlayer (or null)
room.other_players()      # everyone except you
room.get_player(id)       # lookup by player id
room.metadata             # your developer metadata
SpawnWeaver.is_host       # true when you are the host

React to roster changes with signals (they never echo yourself):

SpawnWeaver.player_joined.connect(func(p): add_row(p))
SpawnWeaver.player_left.connect(func(p, reason): remove_row(p.id))
SpawnWeaver.room_joined.connect(func(room): rebuild_ui(room))   # you entered a room
SpawnWeaver.room_left.connect(func(reason): show_menu())        # you left ("left"/"disconnected"/"expired")

room_joined fires on create, join, matchmaking, and automatic resume after a reconnect — treat it as "rebuild everything from room", not "the join button was clicked". Rebuild idempotently from room.players.

Host and host migration

Every room always has a host — room.host_id — including matchmade rooms (the first matched player). Only the host may call update_room() and set_room_state().

Migration is automatic: when the host leaves or disconnects, the earliest-joined connected player is promoted immediately (the game never waits out the grace window to write room state). If the ex-host returns, they come back as a regular member.

SpawnWeaver.host_changed.connect(func(new_host):
    if SpawnWeaver.is_host:
        take_over_game_logic())   # you were promoted

Updating a room (host only)

var result := await SpawnWeaver.update_room({"max_players": 6, "name": "Arena II"})

Only the fields you pass change — except metadata, which replaces the whole map. Non-hosts get not-host. All members receive room_updated(room).

Room metadata

metadata is a free-form dictionary for anything joiners should see before or after joining: game mode, map name, difficulty. It appears in SWRoomSummary entries too, so your lobby browser can filter on it. It is not gameplay state — use room state for values that change during play.

The disconnect grace window

When a member's connection drops, they are not removed immediately:

  1. Their roster entry stays, with connected = false. You receive player_disconnected(p) — grey out their row, pause their turn.
  2. If they return within the grace window (default 60s) — by token reconnect or by rejoining with the code — their seat and their entities are restored. You receive player_reconnected(p).
  3. If the window expires, you receive player_left(p, "disconnected") and their entities are removed (entity_removed(id, "owner-left")).
SpawnWeaver.player_disconnected.connect(func(p): mark_row_ghosted(p.id))
SpawnWeaver.player_reconnected.connect(func(p): mark_row_active(p.id))

Your own side of this is symmetric: if your connection blips, the SDK reconnects and the server puts you straight back into the room — room_joined fires again with the fresh roster and state. Rooms with no connected members expire after a short TTL (default 60s); members still in grace then receive room_left("expired").

Common mistakes

Mistake What happens Fix
Calling create_room()/join_room() before start() SWResult fails with not-connected await SpawnWeaver.start() first (once, at boot)
Creating/joining while already in a room already-in-room One room per connection — await SpawnWeaver.leave_room() first
Treating room_joined as "user clicked join" Duplicate UI/avatars after a reconnect resume Rebuild idempotently from SpawnWeaver.room each time it fires
Removing players on player_disconnected Players vanish during a 2-second Wi-Fi blip Grey them out; only remove on player_left
Assuming the host never changes Host-only calls start failing with not-host after migration Watch host_changed and check SpawnWeaver.is_host before host-only calls
Passing partial metadata to update_room() The rest of the metadata is erased metadata replaces the whole map — send the complete dictionary

Next