Complete reference for the SpawnWeaver Godot SDK (v1.0.0). The entire client API lives
on the SpawnWeaver autoload; every request method is awaitable and returns an
SWResult. Guides: Rooms,
State sync, Events,
Storage.
The SpawnWeaver autoload
Properties
| Property | Type | Meaning |
|---|---|---|
status |
Status enum |
OFFLINE, CONNECTING, or ONLINE |
player |
SWPlayer |
Your identity; null until start() succeeds |
room |
SWRoom |
Your current room, maintained automatically; null when not in a room |
is_host |
bool (read-only) |
true when you host your current room |
latency_ms |
float |
Heartbeat round-trip in milliseconds |
identity_scope |
String |
Advanced: isolates identity persistence per client instance (tools/tests). Leave empty in games |
Constants
| Constant | Value | Meaning |
|---|---|---|
SDK_VERSION |
"1.0.0" |
Reported to the server and in debug reports |
CONFIG_PATH |
res://spawnweaver.cfg |
Project config written by the editor dock |
OVERRIDE_CONFIG_PATH |
user://spawnweaver_override.cfg |
Per-machine override (local tooling; never ship) |
IDENTITY_PATH |
user://spawnweaver/identity.cfg |
Where the player token persists between runs |
DEFAULT_SERVER_URL |
wss://spawnweaver.dev/connect |
Fallback server URL |
Lifecycle
configure(project_key: String, server_url: String = "") -> void
Sets credentials in code instead of using res://spawnweaver.cfg. Call before
start(). server_url is optional (defaults to production).
SpawnWeaver.configure("pk_your_key", "ws://127.0.0.1:5159/connect")
start() -> SWResult
Connects using the configured project key. Resolves once the session is welcomed
(value: your SWPlayer) or the first connection attempt fails. After a successful
start, the SDK auto-reconnects forever until stop(). Calling start() while already
online resolves immediately; while connecting, it awaits the in-flight attempt.
var result := await SpawnWeaver.start()
if not result.ok:
show_error(result.error.message) # e.g. code "not-configured", "connect-failed"
stop() -> void
Disconnects and stops auto-reconnect. In-flight requests fail with code stopped;
room_left("left") fires if you were in a room. Safe to call at any time.
is_online() -> bool
true when status == Status.ONLINE.
set_display_name(display_name: String) -> void
Sets the name other players see. Applied on your next create/join/match.
set_debug_enabled(enabled: bool) -> void
Toggles SDK console logging of every message and error.
Rooms
`create_room(options: Dictionary =
Creates a room and puts you in it as host. Value: the SWRoom (result.room).
| Option | Type | Default | Notes |
|---|---|---|---|
name |
String |
none | Display name |
visibility |
String |
"private" |
"private" (code-only) or "public" (listable) |
max_players |
int |
unlimited | In-grace members count toward the cap |
metadata |
Dictionary |
{} |
Your data, visible to joiners and listings |
var result := await SpawnWeaver.create_room({"visibility": "public", "max_players": 4})
if result.ok:
print(result.room.code)
Errors: already-in-room, not-connected.
join_room(code: String) -> SWResult
Joins by share code, or by a public room's id from list_rooms() (strings starting
with room_/match_ are treated as ids). Value: the SWRoom.
var result := await SpawnWeaver.join_room("4V8772")
Errors: room-not-found, room-full, already-in-room, not-connected.
leave_room() -> SWResult
Leaves your current room. Value: null. Fires room_left("left") on success.
Errors: not-in-room, not-connected.
list_rooms(limit: int = 20) -> SWResult
Lists joinable public rooms of your project, most recently active first.
Value: Array of SWRoomSummary (result.rooms).
var result := await SpawnWeaver.list_rooms()
for summary in result.rooms:
print(summary.code, " ", summary.player_count)
update_room(changes: Dictionary) -> SWResult
Host only. Updates room attributes; only provided fields change, but metadata
replaces the whole map. Accepts the same keys as create_room options.
Value: the updated SWRoom. All members receive room_updated.
Errors: not-host, not-in-room.
Matchmaking
`find_match(mode: String = "default", options: Dictionary =
Queues for a match and resolves when one is found (value: the SWRoom), the
server times the ticket out, or you cancel. Options: region (String, default
"global"), size (int, default 2, range 2–64). A second call replaces the first
ticket.
var result := await SpawnWeaver.find_match("duel", {"region": "eu"})
if result.ok:
start_game(result.room)
elif result.error.code == "match-timeout":
offer_retry()
Errors: match-timeout (retryable), cancelled, already-in-room, invalid-payload
(bad size, or | in mode/region), not-connected. Client-side await ceiling: 120 s.
cancel_matchmaking() -> SWResult
Cancels matchmaking: any awaited find_match() resolves with code cancelled, and
the server-side ticket is removed. Value: {"hadTicket": bool}.
Events
`send_event(event_name: String, data: Dictionary =
Fire-and-forget relay to the other members of your room (you never receive your
own event). Not awaitable; failures arrive on the error signal. Queued automatically
while reconnecting (up to 128 messages) and flushed on resume; dropped with a warning
when fully offline.
SpawnWeaver.send_event("fired", {"dir": [0, 1]})
State sync
All four calls resolve with value: the confirmed change payload (a raw
Dictionary — e.g. {roomId, entityId, ownerId, patch, state}); the matching signal
(room_state_changed / entity_changed / entity_removed) fires for everyone,
including you. Patches are shallow merges; a null value removes the key.
set_room_state(patch: Dictionary) -> SWResult
Host only: patches the room-level shared state.
await SpawnWeaver.set_room_state({"phase": "combat", "old_key": null})
Errors: not-host, not-in-room, state-too-large, rate-limited.
set_entity(entity_id: String, data: Dictionary) -> SWResult
Creates or replaces an entity you own.
Errors: not-in-room, state-forbidden (someone else owns it),
state-limit-exceeded, state-too-large, rate-limited.
patch_entity(entity_id: String, patch: Dictionary) -> SWResult
Patches an entity you own.
Errors: as set_entity, plus entity-not-found.
delete_entity(entity_id: String) -> SWResult
Deletes an entity you own. Everyone receives entity_removed(id, "deleted").
Errors: entity-not-found, state-forbidden, not-in-room.
Player storage
storage_get(key: String) -> SWResult
Value: the stored Variant, or null when the key was never set.
storage_set(key: String, value: Variant) -> SWResult
Persists any JSON-serializable Variant. Survives restarts. Value: null.
Errors: storage-invalid-key, storage-value-too-large, storage-quota-exceeded.
storage_delete(key: String) -> SWResult
Value: bool — true when the key existed.
storage_list(prefix: String = "") -> SWResult
Value: Array[String] of this player's keys, optionally filtered by prefix.
await SpawnWeaver.storage_set("save", {"level": 3})
var save := await SpawnWeaver.storage_get("save") # save.value == {"level": 3}
var keys := await SpawnWeaver.storage_list() # keys.value == ["save"]
var gone := await SpawnWeaver.storage_delete("save") # gone.value == true
Leaderboards
Project-scoped ranked score boards (guide). A board
springs into existence on first submit; a player holds at most one entry per board.
Board names match ^[a-z0-9_.\-]{1,64}$.
leaderboard_submit(board: String, score: int, mode: String = "max") -> SWResult
Submits a score. mode: "max" (default — keep the higher score), "min" (keep the
lower; ranks read ascending), "latest" (always overwrite).
Value: {best, rank, updated} — updated is false when the submit didn't beat
your stored score.
var result := await SpawnWeaver.leaderboard_submit("highscore", 4200)
if result.ok:
print("rank #", result.value["rank"])
Errors: leaderboard-invalid-board, leaderboard-invalid-score, not-connected.
leaderboard_top(board: String, limit: int = 10) -> SWResult
The board's best entries, best first. limit 1–100.
Value: Array[SWLeaderboardEntry].
Errors: leaderboard-invalid-board, not-connected.
leaderboard_around(board: String, entry_range: int = 5) -> SWResult
Your entry ± entry_range neighbors (1–50).
Value: {entries: Array[SWLeaderboardEntry], player_rank: int, total: int} —
player_rank is -1 when you have no entry (the top of the board is returned instead).
Errors: leaderboard-invalid-board, not-connected.
Diagnostics
get_ping_ms() -> float
The heartbeat round-trip latency (same as latency_ms).
create_debug_report() -> Dictionary
A diagnostic snapshot: SDK/engine versions, status, server URL, player/room ids, latency, reconnect attempts, and ring buffers of the last 50 messages and 10 errors.
create_debug_report_string() -> String
The report as pretty-printed JSON — paste into the dashboard's Debug Bundle viewer.
simulate_connection_loss() -> void
Testing helper: drops the socket so auto-reconnect and room resume run exactly as after a real network blip.
Signals
Broadcasts only — request outcomes come back via await, never signals.
| Signal | Arguments | When |
|---|---|---|
connected |
— | Session welcomed; requests will flow (also after each reconnect) |
disconnected |
err: SWError |
The connection dropped; auto-reconnect follows unless stop() was called |
reconnecting |
attempt: int, delay: float |
A reconnect attempt will run after delay seconds |
status_changed |
new_status: Status |
status changed |
room_joined |
room: SWRoom |
You entered a room — create, join, matchmaking, or automatic resume |
room_left |
reason: String |
You left: "left", "disconnected", or "expired" |
player_joined |
player: SWPlayer |
Someone joined your room (never echoes you) |
player_left |
player: SWPlayer, reason: String |
Someone left: "left" or "disconnected" (grace expired) |
player_disconnected |
player: SWPlayer |
A member's connection blipped; they may return within the grace window |
player_reconnected |
player: SWPlayer |
That member returned |
host_changed |
player: SWPlayer |
Host migration promoted a new host |
room_updated |
room: SWRoom |
Room attributes changed (name/visibility/max_players/metadata) |
event_received |
event_name: String, data: Dictionary, sender: SWPlayer |
Another player sent an event |
room_state_changed |
state: Dictionary, patch: Dictionary |
Room state changed (includes your own confirmed changes) |
entity_changed |
entity_id: String, state: Dictionary, patch: Dictionary, owner_id: String |
An entity changed (includes your own confirmed changes) |
entity_removed |
entity_id: String, reason: String |
An entity was removed: "deleted" or "owner-left" |
error |
err: SWError |
An unsolicited server error (awaited calls carry their errors in the SWResult) |
Models
SWResult
The outcome of every awaited call.
| Member | Type | Meaning |
|---|---|---|
ok |
bool |
true on success |
value |
Variant |
The success value (call-specific; see each method above) |
error |
SWError |
The failure, or null on success |
room |
SWRoom (accessor) |
value when it is a room (create/join/find_match/update), else null |
rooms |
Array (accessor) |
value when it is a list (list_rooms), else [] |
player |
SWPlayer (accessor) |
value when it is a player (start), else null |
SWError
| Member | Type | Meaning |
|---|---|---|
code |
String |
Machine-readable kebab-case code (see Error codes) |
message |
String |
Human-readable explanation, safe to log |
retryable |
bool |
true when the same call may succeed after a short backoff |
details |
Dictionary |
Optional extra context (e.g. {"entityId": "p1"}) |
SWPlayer
| Member | Type | Meaning |
|---|---|---|
id |
String |
Stable player id (survives reconnects and restarts) |
name |
String |
Display name given at create/join (may be empty) |
connected |
bool |
false while inside the disconnect grace window |
is_local |
bool |
true when the entry is you |
display_name() |
String |
UI-ready name: name, or a short id fallback |
SWRoom
| Member | Type | Meaning |
|---|---|---|
id |
String |
Server id (room_… / match_…) |
code |
String |
Short join code (e.g. "4V8772") |
visibility |
String |
"public" or "private" |
name |
String |
Optional display name |
max_players |
int |
Player cap; 0 = unlimited |
metadata |
Dictionary |
Developer metadata |
host_id |
String |
Current host's player id (kept valid by host migration) |
players |
Array[SWPlayer] |
The roster, maintained automatically |
state |
Dictionary |
Room-level shared state (host-writable) |
entities |
Dictionary |
entity_id -> {"owner_id": String, "state": Dictionary} |
get_player(player_id) |
SWPlayer |
Roster lookup, or null |
host() |
SWPlayer |
The host's roster entry, or null |
other_players() |
Array[SWPlayer] |
Everyone except you |
SWLeaderboardEntry
One row from leaderboard_top() / leaderboard_around().
| Member | Type | Meaning |
|---|---|---|
rank |
int |
1-based rank; ties share a rank |
player_id |
String |
The entry owner's stable player id |
player_name |
String |
Display name sent with the submit (may be empty) |
score |
int |
The stored score |
updated_at |
String |
ISO-8601 UTC timestamp of the last accepted submit |
is_local |
bool |
true when the entry is you |
display_name() |
String |
UI-ready name: player_name, or a short id fallback |
SWRoomSummary
One entry from list_rooms().
| Member | Type | Meaning |
|---|---|---|
id |
String |
Room id (can be passed to join_room()) |
code |
String |
Join code |
name |
String |
Optional display name |
player_count |
int |
Current members |
max_players |
int |
Cap; 0 = unlimited |
metadata |
Dictionary |
Developer metadata |
has_space() |
bool |
true when the room can still be joined |
Nodes
LobbyBrowser
A complete lobby UI with zero code (extends Control): player name field, Create
room, Quick match, join by code, and a live public-room list. Built from standard
Controls, so your project Theme restyles it. Connect entered_room and switch to
your game scene — that's the whole integration.
| Export | Type | Default | Meaning |
|---|---|---|---|
auto_start |
bool |
true |
Call SpawnWeaver.start() when entering the tree |
show_name_field |
bool |
true |
Show the player-name field (persisted per device) |
show_quick_match |
bool |
true |
Show the Quick match button |
quick_match_size |
int |
2 |
Players per quick match (2–64) |
default_max_players |
int |
8 |
Cap for rooms created here (0 = unlimited) |
hide_on_join |
bool |
true |
Hide on room entry; reappear when the room is left |
refresh_seconds |
float |
3.0 |
Public-room list refresh interval |
title |
String |
"Multiplayer" |
Heading text (empty hides it) |
| Signal | Arguments | When |
|---|---|---|
entered_room |
room: SWRoom |
A room was entered (create, join, code, or match) |
errored |
err: SWError |
An action failed (also shown in the status line) |
SpawnSync
Drop-in network sync for a Node2D or Node3D — add as a child of the node to
replicate. Local copies send (dirty-checked, at send_rate); remote copies
interpolate. Re-registers after reconnects/room changes; deletes its entity on exit.
| Export | Type | Default | Meaning |
|---|---|---|---|
entity_id |
String |
"" |
Stable id; empty on a local node = your player id |
is_local |
bool |
false |
true on the single copy this client owns |
sync_position |
bool |
true |
Replicate position |
sync_rotation |
bool |
true |
Replicate rotation |
sync_scale |
bool |
false |
Replicate scale |
synced_properties |
PackedStringArray |
[] |
Extra parent properties (primitives only) |
send_rate |
float |
8.0 |
Local updates per second (1–10) |
interpolate |
bool |
true |
Smooth remote copies toward incoming state |
interpolation_speed |
float |
16.0 |
Higher = snappier (1–40, frame-rate independent) |
free_parent_on_remove |
bool |
true |
Free the parent when the entity is removed server-side |
Method: set_local(local: bool) — switches the controlling client and (re)registers
(called by PlayerSpawner when spawning).
State keys sent: 2D — x, y, rot, sx, sy; 3D — x, y, z, rx, ry,
rz, sx, sy, sz; plus each synced_properties name verbatim.
PlayerSpawner
Spawns one instance of your player scene per player in the room; despawns on leave;
survives reconnects (idempotent reconcile); auto-wires any SpawnSync inside the
scene (entity id + local authority) before it enters the tree.
| Export | Type | Default | Meaning |
|---|---|---|---|
player_scene |
PackedScene |
— | The scene instantiated per player (required) |
spawn_root |
NodePath |
empty | Where instances are added; empty = the spawner's parent |
spawn_points |
NodePath |
empty | Node whose children are used as spawn positions, round-robin by roster order |
spawn_local_player |
bool |
true |
Also spawn an avatar for you |
| Signal | Arguments | When |
|---|---|---|
player_spawned |
player: SWPlayer, node: Node |
After a player's avatar entered the tree |
player_despawned |
player_id: String, node: Node |
After a player's avatar was removed |
Optional hook on the player scene's root script, called right before add_child:
func setup(player: SWPlayer, is_local: bool) -> void:
pass
Error codes
SDK-local codes (raised without a server round-trip)
| Code | Retryable | When |
|---|---|---|
not-configured |
no | start() with no project key configured |
connect-failed |
no | The first connection attempt failed (start() resolves with this; no auto-retry) |
connection-lost |
yes | A mid-session drop (carried by the disconnected signal) |
stopped |
no | stop() was called; in-flight requests fail with this |
not-connected |
no | A request made while offline — call start() first |
timeout |
yes | No reply within 10 s (120 s for find_match) |
disconnected |
yes | The connection dropped mid-request |
cancelled |
no | cancel_matchmaking() resolved a pending find_match() |
match-timeout |
yes | The server found no match within its window |
Server codes
| Code | Retryable | Meaning |
|---|---|---|
malformed-message |
no | Frame wasn't valid JSON or had no type |
unknown-message-type |
no | No handler for the type (SDK/server version mismatch) |
invalid-payload |
no | A required field is missing or invalid |
room-not-found |
no | No such room code, or the room expired |
room-full |
no | The room is at max_players |
already-in-room |
no | Already in a room — leave it first |
not-in-room |
no | The operation needs a current room |
not-host |
no | Host-only operation (update_room, room state) |
payload-too-large |
no | Message over the 16 KB limit |
rate-limited |
yes | Sending too fast — back off and retry |
entity-not-found |
no | No such entity in this room |
state-forbidden |
no | Only the entity's owner may change it |
state-limit-exceeded |
no | Entity count cap (50) reached |
state-too-large |
no | Entity (4 KB) or room state (16 KB) cap exceeded |
leaderboard-invalid-board |
no | Board name doesn't match ^[a-z0-9_.\-]{1,64}$ |
leaderboard-invalid-score |
no | Score missing or outside ±2^53-1 |
storage-invalid-key |
no | Storage key empty or over 128 chars |
storage-value-too-large |
no | Stored value over 64 KB |
storage-quota-exceeded |
no | At the 100-key quota |
Limits and remedies: Limits. Wire-level details: Protocol reference.