Every SpawnWeaver limit, its value, the error you get when you hit it, and the fix. Nothing here is a silent cap — every limit answers with a named error code.
The table
| Limit | Value | Error raised | What to do |
|---|---|---|---|
| Message size (any realtime message) | 16 KB | payload-too-large |
Shrink the payload; move big data to storage |
| Message rate per connection | 20/s sustained, burst 40 | rate-limited (retryable) |
Back off briefly and retry; batch sends |
| State updates per client | 10/s sustained, burst 20 | rate-limited (retryable) |
Send at ≤10/s; SpawnSync's default send_rate of 8 is safe |
| Entities per room | 50 | state-limit-exceeded |
Delete finished entities; pool projectiles into events |
| State per entity | 4 KB | state-too-large |
Keep entities small (transform + a few fields) |
| Room state size | 16 KB | state-too-large |
Trim room state; remove keys with null patches |
| Storage value size | 64 KB per key | storage-value-too-large (HTTP 413) |
Split or compress the value |
| Storage keys per player | 100 | storage-quota-exceeded (HTTP 409) |
Delete unused keys; consolidate into fewer dictionaries |
| Storage key length | 128 chars, non-empty | storage-invalid-key (HTTP 400) |
Use short, stable key names |
| Matchmaking size | 2–64 players | invalid-payload |
Pick a size in range |
| Matchmaking wait | 30 s per ticket | match-timeout (retryable) |
Offer "search again"; see Matchmaking |
| Connections per project | generous per-tier cap | Handshake rejected with HTTP 429 | Retry with backoff; contact us if your launch needs more |
| Disconnect grace | 60 s | after expiry: player_left with reason disconnected; entities removed (owner-left) |
Design reconnect UX around ~1 minute |
| Empty-room TTL | 60 s | room expires: joiners get room-not-found; in-grace members get room_left("expired") |
Rejoin/recreate; don't park empty rooms |
| Room metadata | no dedicated cap | bounded by the 16 KB message limit | Keep metadata to a handful of small strings |
| Auth endpoint rate (sign-in/up) | per-IP throttle | HTTP 429 | Wait a minute; don't loop sign-in attempts |
How limit errors behave
- Retryable errors (
rate-limited,match-timeout, plus the SDK-localtimeoutanddisconnected) carryretryable: trueon theSWError— the same call may succeed after a short backoff. - Hard errors (
payload-too-large,state-too-large,state-limit-exceeded, storage quota errors) are not retryable: the same call will fail again until you change what you send. - Rate and size rejections still echo your request id, so an awaited call resolves with the error rather than timing out:
var result := await SpawnWeaver.set_entity("boss", huge_dictionary)
if not result.ok and result.error.code == "state-too-large":
push_warning("Entity too big: %s" % result.error.message)
send_event()has no await; its limit errors arrive on theerrorsignal instead.
Budgeting within the rate limits
The 20 messages/s connection budget covers everything you send: events, state updates, storage calls, and the SDK's heartbeat (one ping every 15 s — negligible). A practical split for an action game:
| Traffic | Budget |
|---|---|
SpawnSync transform updates |
8/s (the default send_rate) |
| Gameplay events | ≤ 5/s sustained |
| Manual state patches (host, room state) | ≤ 2/s |
| Storage saves | occasional (on change, not on a timer) |
Bursts up to 40 messages are absorbed by the token bucket, so a spiky moment (three
events in one frame) is fine — sustained overrun is what triggers rate-limited.
Timing windows at a glance
| Window | Default | Meaning |
|---|---|---|
| Disconnect grace | 60 s | A dropped player's seat and entities are kept; reconnect resumes them |
| Empty-room TTL | 60 s | A room with no connected members is removed |
| Matchmaking timeout | 30 s | A ticket that finds no match resolves with match-timeout |
| SDK request timeout | 10 s | An awaited call with no reply fails with local timeout (retryable) |
| SDK matchmaking guard | 120 s | Client-side ceiling on find_match() awaits |
| Heartbeat | 15 s interval | Connection declared dead after ~45 s of silence; auto-reconnect kicks in |
| Reconnect backoff | 0.5 s → 30 s cap | Exponential with jitter, forever until stop() |
Need a higher limit?
The values above are tuned so a normal game never meets them; they exist to keep one project's bug from degrading everyone else's service. If your game legitimately needs more — a big playtest, a launch spike, bigger rooms — contact us and we'll raise the right knob for your project.