Skip to main content
Arcane gives you the meeting point. Your game keeps its netcode. Steam, Epic Online Services and Discord all start from the same idea: the platform hosts a lobby — a party object with a host, members and a capacity — players get in by friend invitation, by code, or by hitting “Join” on a friend, and the platform acts as a mailbox so members can swap connection details. The heavy half is the transport (Steam Datagram Relay, EOS P2P with relays), and it is optional: plenty of shipped games use a Steam lobby with their own netcode. Arcane ships that first half only.
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

And the other side, including the player who launched the game from a friend’s “Join”:

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 from A–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:
Anything that is not six of those characters is 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.
Put a reference in it — an address, a session ticket your own netcode understands — not game data.

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:
It arrives before the events of the same poll, so acting on it first and then applying the rest is correct. 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.
It never fails: 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

A lobby also ends when the host’s play session expires. There is no host migration — the members get LobbyClosed, and somebody opens a new lobby.
Every call here except poll_events() and a cached launch_join_code() is synchronous: one loopback round trip to the Arcane desktop app, on your thread, around a millisecond. Call them from menus and lobby flows, never per frame.

Errors

Full table and what to do about each: Errors.

In C

Payloads are base64 in both directions in C: you pass one in, you get them back in the JSON. 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.

Next steps

  • Friends — who to invite, and who is in your game right now
  • Session — the thread that polls events, and what else it does
  • Errors — every code, and its fix
  • Rust API · C ABI