Matchmaking pairs players who queue with the same mode, region, and match size, then drops them all into a freshly created room. One awaitable call takes you from "Find match" to "in a room with opponents".

Find a match

var result := await SpawnWeaver.find_match("duel")
if result.ok:
    start_game(result.room)     # a normal SWRoom — roster, code, host, state
else:
    match result.error.code:
        "match-timeout": say("Nobody around right now — try again.")
        "cancelled":     pass    # the player backed out
        _:               say(result.error.message)

find_match(mode, options) queues a ticket and resolves when the match is found — which can take seconds, or as long as the server-side timeout. The await handles the whole wait; there is no polling.

Modes, regions, and sizes

Matching is exact: players are bucketed by project + mode + region + size, and a room is created the moment a bucket fills. First-come, first-served.

await SpawnWeaver.find_match("duel")                                   # size 2, global
await SpawnWeaver.find_match("ffa", {"size": 4})                       # 4-player bucket
await SpawnWeaver.find_match("ranked", {"region": "eu", "size": 2})    # region-scoped
Option Default Notes
mode "default" Any string without \|. Your own vocabulary: "duel", "coop", …
region "global" Any string without \|. Only players in the same region match.
size 2 2–64 players. The room is created when exactly this many are queued.

Players with different modes, regions, or sizes never match each other — a player searching {"size": 2} and one searching {"size": 3} sit in separate buckets. Keep your option matrix small, or players will wait.

Cancelling

SpawnWeaver.cancel_matchmaking()

Cancelling makes any awaited find_match() resolve immediately with error code cancelled, and removes the server-side ticket. Disconnecting also removes the ticket. Calling find_match() again while searching simply replaces the previous ticket (the earlier await resolves with the newer outcome pathway — don't run two searches in parallel; there is one ticket per connection).

Timeouts

If no bucket fills within the server's matchmaking timeout (default 30 seconds, deployment-configurable), the ticket expires and find_match() resolves with match-timeout. The error is marked retryable — offer a "Search again" button, or retry automatically with a message:

func search_forever() -> void:
    while true:
        var result := await SpawnWeaver.find_match("duel")
        if result.ok:
            start_game(result.room)
            return
        if result.error.code != "match-timeout":
            return   # cancelled, disconnected, …
        say("Still searching…")

The SDK also applies its own client-side guard (120s) so an await can never hang forever, even if the server reply is lost — that surfaces as a retryable timeout.

A matched room is a normal room

The result of find_match() is a regular SWRoom:

  • It has a host — the first matched player — with all the usual host powers (room state, update_room()), and normal host migration.
  • It has a join code, so a disconnected player can rejoin within the grace window.
  • room_joined fires, PlayerSpawner spawns avatars, SpawnSync starts syncing — everything from the Rooms guide applies unchanged.

You cannot search while in a room — find_match() fails with already-in-room. leave_room() first (e.g. for a "requeue" button after a match).

UI pattern: searching spinner with cancel

func _on_find_match_pressed() -> void:
    _spinner.visible = true
    _cancel_button.visible = true

    var result := await SpawnWeaver.find_match("duel")

    _spinner.visible = false
    _cancel_button.visible = false

    if result.ok:
        get_tree().change_scene_to_file("res://levels/arena.tscn")
    elif result.error.code == "match-timeout":
        _status.text = "No opponents found — try again?"
    elif result.error.code != "cancelled":
        _status.text = "Matchmaking failed: %s" % result.error.message


func _on_cancel_pressed() -> void:
    SpawnWeaver.cancel_matchmaking()   # the await above resolves with "cancelled"

Because the outcome comes back through the same await, the spinner logic lives in one function — no signal bookkeeping.

Testing matchmaking locally

Run two instances (Debug → Run Multiple Instances → 2) and press "Find match" in both. With the default size of 2, they match each other within a second. For larger sizes, run that many instances — every instance is its own player identity.

Common mistakes

Mistake What happens Fix
Searching while in a room already-in-room await SpawnWeaver.leave_room() before find_match()
Using \| in mode or region invalid-payload The \| character is reserved; pick another separator
size of 1 or above 64 invalid-payload Sizes are 2–64
Too many mode/region/size combinations Players wait forever in separate buckets Start with one mode and "global"; add options when you have the player base
No timeout handling Players stare at an endless spinner Handle match-timeout — it is retryable by design
Forgetting the matched room has a host Nobody writes room state; game never starts The first matched player is host — check SpawnWeaver.is_host and act

Next

  • Rooms — everything the matched room can do
  • State sync — start the game with host-written room state