Operations
Recommended waits
| Event | Minimum | Recommended |
|---|---|---|
After serverWithLeastClients | 1.0 s | 1.5 s |
| After first actor registration | 0.2 s | 0.5 s |
| Actor heartbeat | — | every 4 s |
| Any client traffic | — | every 60 s |
Common error codes
Delivered as GENERIC_ERROR_MESSAGE (type 3) with echoed sequence. Names below
match the current wire enum; the MAP_* forms are legacy aliases.
| Code | Name | Typical cause |
|---|---|---|
| 5 | INVALID_TOKEN | Malformed token material, or not a valid app-scoped token |
| 6 | APP_NOT_FOUND | Unknown app (formerly MAP_NOT_FOUND) |
| 7 | UNAUTHORIZED | Permission denied — no active app access / tier permission, or no grid permission for the target chunk (see Permissions below) |
| 13 | GAME_TOKEN_WRONG_SIZE | Malformed token material |
| 15 | INVALID_REQUEST | Wire layout or field error |
| 18 | INVALID_APP_ID | Not authorized for this app — unknown app or the token is scoped to a different app (app-scope mismatch) |
| 20 | USER_NOT_AUTHENTICATED | Session not ready (or not an app-scoped token) — mintAppToken, call serverWithLeastClients, wait ~1.5 s, retry |
| 32 | TOKEN_EXPIRED | The app-scoped token passed its expiresAt — Buddy evicted the session. Mint a fresh token (refreshAppToken for the same app) and re-assign |
| 33 | APP_PAUSED | The app is paused (its organization has no funds, reached a spend cap, or its subscription lapsed): nothing it sends replicates. Keep the session and tell the player; the server re-checks with backoff and serves it again once the app is active, without a reconnect (server v0.37.0) |
Permissions (always enforced)
Every spatial message is authorized on the server. The sender must have active
app access whose tier includes the relevant permission key (access to
move/send events and text, update_voxel_data to edit voxels, use_voice_chat for audio,
use_video_chat for webcam video — opcode 143, see
Wire formats → Video payload)
and the target chunk must be inside a grid where the sender holds that
key. A message failing either check is dropped and answered with
GENERIC_ERROR_MESSAGE / UNAUTHORIZED (code 7).
A voxel update is held to a stricter rule (replication server v0.34.0): the grid that decides is the most specific one covering the chunk, so a plot or a safe zone nested in the world grid decides its own chunks, the world grid decides only the wilderness, and a chunk no grid covers is refused. While the app has closed its wilderness to building, every voxel update there is refused too. See Which grid decides a voxel write.
New apps are open by default — they ship with a default tier granting the
baseline permissions (access, teleport, update_voxel_data, use_voice_chat;
not use_video_chat, which an owner grants on a tier deliberately because
every receiver pays the egress) and a world-spanning default grid, and granting a player app access
automatically grants them all of its permissions — so out of the box any entitled
player can move, talk and build anywhere no narrower grid covers. App owners opt
into restrictions (safe zones, plot ownership) via the Game API. See
Game API → Permissions overview and
Grids and permissions.
The first message to a brand-new chunk region may briefly return
UNAUTHORIZEDwhile the server loads that region's grid permissions; retry and it succeeds.Permission denials are transient and self-healing in general: the server periodically re-pulls a denied session's cached permission state (a few seconds of backoff between refreshes), so an entitlement granted after you connected — or one that reached the game database late — starts working without re-authenticating or reassigning. If you receive a stream of
UNAUTHORIZEDreplies, keep sending at a reduced rate for ~10-15 seconds before falling back to a freshserverWithLeastClientsassignment.
App suspended or over budget
If an app is paused — its organization is out of wallet funds, over a spend cap
on the shared environment, or its subscription lapsed — sessions still connect, and
nothing the app's clients send replicates. Since server v0.37.0 the server says so: a
refused spatial or channel send is answered GENERIC_ERROR_MESSAGE with code 33
(APP_PAUSED), at most once per session every 5 seconds, and the rest are dropped
silently. Heartbeats are not refused. The server re-checks with backoff and serves the
session again once the app is active, with no reconnect. mintAppToken and gameClientBootstrap
report the reason in runtimeGate, so tell the player the world is paused. This is a
billing/runtime state, not a client bug: the owner resolves it on the management API
(fund the wallet, raise the cap, or renew). See
What "access denied" means.
(Before v0.37.0 every send was answered INVALID_APP_ID and a session did not
recover when the app was re-activated.)
A player whose own access to the app is revoked or suspended is different: their
tokens for the app are revoked and the session ends at once; signing in again is
refused with ACCESS_REVOKED or ACCESS_SUSPENDED until it is lifted.
Server reassignment
If Buddy restarts, evicts your session, or you need a different host:
- Call
serverWithLeastClientson the Game API again (refreshAppTokenfirst if the app token is nearexpiresAt). - Wait ~1.5 s for the session to be ready.
- Resume UDP with the same app-scoped token on the new host and
clientPort.
Load shedding — COMMAND_RECONNECT (type 22)
A server protects itself when it runs hot. If a CPU core or system memory stays
high for several seconds, the server reports NearCapacity (so the Game API
stops sending it new clients) and, if pressure continues, Full — at which
point it sheds some existing clients to recover. A shed client receives a small
server→client datagram:
[1B type=22][32B HMAC]
The 32-byte HMAC is HMAC-SHA256 keyed on your 64-octet app-scoped token (the
same token you use to sign your spatial messages) over the single type byte. Because
only you and the server hold that token, a matching HMAC proves the command came
from the server — verify it and ignore the datagram if it does not match. When
you receive a valid COMMAND_RECONNECT, follow the Server reassignment steps
above: re-query serverWithLeastClients (it will not return the overloaded
server) and move your session to the returned host. The losing server keeps
serving you for a short grace period (≈5 s) and then deletes your session and
ignores further packets, so reconnect promptly. There is no reply to send, no
sequence number to echo, and no reason field; it is a one-shot directive.
Native (direct-UDP) clients should verify the HMAC and handle type
22. Browser clients using the GraphQL UDP proxy are moved automatically by the proxy (which verifies the HMAC) and never see this message.
Browser clients
Use the GraphQL UDP proxy instead of raw UDP.