No transport, no NAT traversal, no relay, no host migration, no public matchmaking. Arcane carries an opaque
payload for each member — an address, a ticket from your own netcode, whatever you want, up to 4 KiB — and never reads it. Connecting is yours.The scenario
The lobby
create_lobby and both join calls return the same object, filled in as of that moment. It is a snapshot: whoever arrives afterwards comes through poll_events() as a MemberJoined.
Arcane never lists lobbies publicly — there is no browser of open games.
Join codes
Six characters fromA–Z without I and O, and 2–9 — no character a player can mistype into another. Uppercase them or don’t; the SDK does it before checking:
invalid_argument, raised before any call — so a player pasting a whole URL costs you nothing.
join_code is None on a friends-only lobby, and for a member who is not the host.
Payloads
payload is your connection blob: up to MAX_LOBBY_PAYLOAD_LEN (4096) raw bytes, base64 on the wire, never interpreted by Arcane. Over that limit is invalid_argument before any call.
Events
poll_events() drains a queue the arcane-session thread fills. It reads memory only: no callback, no extra thread, no I/O, no failure. Each event is delivered exactly once, oldest first.
Resync means Arcane dropped events before this client fetched them, so the queue has a hole in it. When you get one, stop trusting what the earlier events built up and ask instead — get_lobby returns the same object as create_lobby and join, without joining or leaving anything:
LobbyEvent::lobby_id() answers None for it — it is about every lobby you are in, not one.
The polling behind it is armed by your first call to client.p2p() and not before — a game that never touches lobbies never pays for any of this. Once armed, the session thread asks the Arcane desktop app for events on every tick: every 5 seconds while you are in an open lobby, every 60 seconds otherwise. Heartbeats keep their own 60-second schedule regardless.
Unavailable means the Arcane desktop app predates the lobby routes: polling stopped silently and will not restart for this client. It is reported there rather than raised, because nothing the game did caused it.
If you arm polling and then never call poll_events(), the queue keeps the 256 most recent events and drops the oldest.
Launching from “Join”
When a player hits “Join” on a friend in the launcher, Arcane starts the game and stashes the join code for that launch.launch_join_code() reads it on the first call and caches it for the client’s lifetime — the desktop app clears it once served, so it belongs to this launch and no other.
None covers “started normally”, an older desktop app, and offline-only mode. If the friend is already playing, they get an Invite event instead of a launch.
Ending a lobby
LobbyClosed, and somebody opens a new lobby.
Errors
Full table and what to do about each: Errors.
In C
arcane_sdk_lobby_events_json drains the queue only once the JSON is safely in your buffer — a -3 leaves every event where it was, so you can retry with a bigger buffer. Details: C ABI.