SdkError. It carries four things, and each answers a different question:
Display renders all four, so a single log line is usually enough:
is_retryable() is true when retrying the same call could succeed once something outside your code changes — the network returns, Arcane opens, the player signs in. It is false when nothing will change without a code, account, or machine change.
Error codes
By function
ArcaneClient::init / arcane_sdk_init
Can return any code in the table except not_initialized.
missing_game_id and invalid_game_id come first, before any filesystem or network work: init reads the game id from ARCANE_GAME_ID, which Arcane Powered sets when it launches your game. If the cached drm_enabled flag is false, init succeeds with DrmDisabled and never reaches the ticket codes. When the offline check returns ticket_missing or ticket_expired, init contacts Arcane desktop (and may open it), which adds not_owned, network_required, not_authenticated and arcane_unavailable to the possible outcomes.
The play session opened by init never surfaces an error. When the desktop app is missing, older than the session routes (feature_unavailable), or simply unhappy, tracking stays Pending and the SDK retries every 60 seconds. Read client.session().tracking if you want to know.
ArcaneClient::refresh / arcane_sdk_refresh
Same codes as init, minus missing_game_id and invalid_game_id (the id was already read and validated). Always contacts the desktop app, so it never returns a purely offline result. On failure the client keeps its previous state.
Under ARCANE_OFFLINE_ONLY it returns network_required immediately.
Achievements::list / unlock — arcane_sdk_achievements_json, arcane_sdk_achievement_unlock
invalid_argument (bad key, before any call), unknown_achievement, not_owned, not_authenticated, network_required (also under ARCANE_OFFLINE_ONLY), arcane_unavailable, feature_unavailable.
An already-unlocked or queued answer is a success, not an error — see Achievements.
Achievements::is_unlocked / arcane_sdk_achievement_is_unlocked never fail: they read the cache and answer None / -4 when list has never succeeded or the key was not in it.
Friends::list — arcane_sdk_friends_json
not_authenticated, network_required (also under ARCANE_OFFLINE_ONLY, raised before any call), arcane_unavailable, feature_unavailable.
A stale answer — the desktop app served its own cache because it is offline — is a success, not an error. See Friends.
P2p::create_lobby / join_by_code / join / get_lobby / invite / leave / close — the arcane_sdk_lobby_* functions
invalid_argument (oversized payload, malformed join code or id — all raised before any call), lobby_not_found, lobby_full, lobby_closed, not_friends, not_owned, not_authenticated, network_required (also under ARCANE_OFFLINE_ONLY, raised before any call), arcane_unavailable, feature_unavailable.
P2p::poll_events / arcane_sdk_lobby_events_json and P2p::launch_join_code / arcane_sdk_launch_join_code never fail. A lobby object Arcane sends without an id, or carrying a payload over 4096 bytes, is refused as arcane_unavailable rather than half-read. A desktop app that predates the lobby routes makes launch_join_code answer None and stops event polling silently — read client.session().lobby_events for that, it is Unavailable. See Lobbies.
ArcaneClient::frame, set_graphics, session, shutdown
Never fail and never return a Result. Before the desktop app acknowledges the session they are simply no-ops that keep counting locally.
C ABI getters
not_initialized before arcane_sdk_init succeeds. See the return-code tables below.
Status vs error
A successful check returnsOwnershipStatus:
Both mean launch the game.
is_owned() distinguishes them if you need to.
C ABI return values
Two conventions, by function kind.Actions — arcane_sdk_init, arcane_sdk_refresh, arcane_sdk_set_graphics, arcane_sdk_achievement_unlock, arcane_sdk_lobby_invite, arcane_sdk_lobby_leave, arcane_sdk_lobby_close
arcane_sdk_init takes no string argument and is never called before init, so it only ever returns 0 or 2. arcane_sdk_frame and arcane_sdk_shutdown return nothing and cannot fail.
Getters — arcane_sdk_user_id, arcane_sdk_game_id, arcane_sdk_device_hash, arcane_sdk_session_json, arcane_sdk_achievements_json, arcane_sdk_friends_json, arcane_sdk_lobby_create, arcane_sdk_lobby_join_code, arcane_sdk_lobby_join, arcane_sdk_launch_join_code, arcane_sdk_lobby_events_json, arcane_sdk_last_error_json
arcane_sdk_lobby_events_json drains its queue only once the JSON is in your buffer: a -3 leaves every event queued for a retry with a bigger buffer.
arcane_sdk_ownership returns 0 (owned), 1 (DRM disabled) or -1 (not initialised). arcane_sdk_ticket_expires_at and arcane_sdk_checked_at return a Unix timestamp or -1. arcane_sdk_achievement_is_unlocked returns 1, 0, -1 (not initialised), -2 (null / non-UTF-8 key) or -4 (nothing cached for that key).
Structured errors in C
arcane_sdk_last_error_json gives the full error for engines that want to render it: