How Object Sync Works

How objects are synced in sync and stateMachine rooms, and who can modify what

This page explains how objects are synced in sync and stateMachine rooms, and who can modify what. Its content is the same for every game engine. For each engine’s settings and APIs, see the Unity Client and the Godot Client.

Overview

TermMeaningExample with 10 enemies
Shared objectAn object created by the room whose state all participants see identicallyThe 10 enemies
Player objectAn object a player spawns and owns. The owner modifies its valuesEach player’s character
Object IDAn ID that identifies a single entity in the room. Issued by the server10 different IDs, one per enemy
Prefab IDAn ID that represents the kind of object. Used to choose which Prefab (a scene in Godot) the client displaysThe same ID for all 10
Sync property (field)A value delivered to participantsPosition, HP

The kind of object decides who can modify its values.

KindWho modifies valuesExample
Player objectThe ownerEach player’s character
Shared object that lends controlThe participant who has control (control)A box you carry, a paddle
Server-managed shared objectOnly the rules. Participants send operationsEnemies, doors
Simulated objectOnly the rules. The owner sends inputs (ticks)A character moved by inputs

Data arrives in the following order.

  1. When you join the room, all objects and values at that point (a snapshot) arrive, and the client creates the instances to display.
  2. After that, only changed values arrive, and the display is updated.

Send Order

RPCs, spawns, events, properties and other commands may be called at any time after connecting. Those called before the snapshot is applied, or inside spawn notifications, are sent as soon as they can be, in call order. The order of commands is kept across all objects and room-level calls.

Received messages carry the room server’s clock. The client uses it to keep a time shared by all participants, and moves other players’ objects smoothly according to the time they were received.

Position and Rotation

Position and rotation have standard fields, so you can sync them without a sync schema. On the client, select only the axes the server allows to sync.

Server declarationAxes you can syncStep size (default)
RoomSync.PositionPosition X / Y / Z0.01 (game distance units)
RoomSync.RotationRotation X / Y / Z360 / 4096 degrees
RoomSync.TransformBoth of the aboveSame as above
  • Position is handled in world coordinates, and rotation as Euler angles. Scale is not synced.
  • Other clients show the pose from slightly in the past (0.1 seconds by default), interpolating between the updates received before and after. Positions farther than a set distance can be reached without interpolation (for respawns, etc.).
  • When updates stop, the object stops at the last received pose. It does not predict future positions.
  • Values are rounded to the step size before sending. Jitter smaller than the step size is not sent.

Custom Values

For custom values such as HP or an open/closed state, declare them as a sync schema in the rules and use the generated .realtimeschema on the client. For how to write the declaration, see Sync Schema.

  • On the writing side, reading a value returns the value you last wrote (even before it is sent). For example, adding 1 to HP twice between sends adds 2.
  • You can also read only the values received from the room (confirmed).
  • Passing received values to the display (Property bindings in Unity, property_bindings in Godot) only changes the display. The server’s value does not change. Change server-managed values by sending operations.

Control

For objects the server declares as “lending control”, only the participant who has obtained control (lease) can send values. Use this for carrying a box, moving a paddle, and so on.

  • Acquire requests are processed asynchronously, so you cannot necessarily operate the object right after requesting.
  • When another participant has control, the request is rejected with LeaseBusy. A rejected request is not retried automatically.
  • Control has an expiry, and the client can renew it automatically before it expires.
  • You can enable movement and input handling only while you can operate the object: while you have control of a shared object, or while you own a player object or simulated object.
  • A request that waits for control ends in one of the following results.
ResultMeaning
SucceededYou have control
RefusedLeaseBusy: another participant has control. Forbidden: an object whose control cannot be acquired (a server-managed object, or another player’s object)
AbandonedThe request was withdrawn (control released, the display instance disabled, the object removed)
  • When the display instance is disabled, control is released automatically.
  • There are also APIs to acquire, renew and release control manually (Acquire, Renew, Release). They only return the room’s answer; they neither enable the handling above nor renew automatically.
  • Each change of controller (acquire, release or expiry) starts a new period. Values sent in an earlier period (arriving late over UDP) are not applied, and do not cause an error.

Send Operations to the Server

Clients cannot rewrite the values of server-managed objects (such as an enemy’s HP). Instead, send a request saying “perform operation method on this object” (Interact). The request reaches the rules’ Interact (on_interact in GDScript), and the rules validate it and update the values.

method is a number the rules use to tell kinds of operations apart. When the rules name it with a [RoomAction] enum (## @room_action in GDScript), pass the generated constant. You cannot send additional arguments to a room run by rules.

An operation request (and acquiring, renewing and releasing control) completes once. The completion status is one of:

StatusMeaning
SucceededAccepted
RefusedRefused. The reason is the result code (table below)
NotSentCould not be sent
AbandonedNo answer will come (disconnected, or rejoined the room on reconnection)

An interaction that removes its object (picking up an item) still completes as Succeeded: the room removes the object and then answers.

Result codeValueMeaning
Success0Accepted, and the change was delivered to everyone
MissingObject1The target object does not exist
Forbidden2The operation is not allowed for this object
LeaseBusy3Another participant has control
StaleEpoch4An old request from before the control state changed
InvalidOperation5An operation not accepted in the current state, or a rules execution error
AlreadyApplied6The rules reported it as already applied. The client treats it as a success
StaleRequest7The same request ID has already been processed
Rejection reason from the rules100 or moreThe value the rules returned with Step.Ignore

When the rules reject with Step.Ignore, the numeric reason arrives as is. To distinguish it from the room’s results, use values of 100 or more for your rules’ own rejection reasons (error enum).

Player Objects

Objects owned by a player, such as the character the player controls, are created by a spawn request from the client. There are two ways to spawn.

MethodBehavior
Create immediately (world.Instantiate in Unity, world.instantiate in Godot)Returns the instance immediately, usable as your own. Movement and RPCs are held until the room confirms the spawn and are sent after confirmation. Positions you move to before confirmation are kept. Synced values are available after confirmation
Create after confirmation (world.Spawn / world.spawn)Waits for the room’s confirmation, then creates the instance

If the room rejects the spawn (the ownership limit of 64 is exceeded, or the Prefab is not registered in a room that has an object catalog), the rejection is reported, and an instance created immediately is destroyed.

  • Ownership can be transferred to another participant (player objects only). Anyone can take ownership of objects with "ownership": "takeover" in the object catalog.
  • When the owner leaves, their player objects are destroyed. Objects with "keepOnLeave": true in the object catalog are handed over to the master client.
  • Each change of owner starts a new ownership period. Values sent in an earlier period (arriving late over UDP) are not applied, even after the same participant gets ownership back.

RPC

Calls a method of an object on other clients.

TargetWho receives it
AllEveryone, including the calling player
OthersEveryone except the calling player
OwnerThe object’s owner
AllBuffered / OthersBufferedSame as above, plus players who join later. Discarded when the object is destroyed or the calling player leaves
One playerThe participant with the specified connection ID
  • The RPC ID is determined from the method name. Renaming the method changes the ID, so update the object catalog and the client together.
  • To restrict who may call which RPC, set an object catalog. With one, only the owner may call the IDs in methods, and RPCs called on other players’ objects go in publicMethods.
  • Arguments are up to 4096 bytes in total. The last argument can receive the caller’s connection ID and similar information.
  • Received RPCs are called in arrival order, after receive processing finishes. You can send RPCs and modify values from inside an RPC.
  • You cannot send RPCs to shared objects. Use Send Operations to the Server instead.

Room Events

Send notifications not tied to an object with room events. Any participant can send them. Targets are All / Others, their Buffered variants, or a single player. Buffered events are discarded when the sending player leaves.

Custom Properties

Values attached to the room or to players. They also reach players who join later.

  • Supported value types are the same as RPC arguments. Writing null deletes the property.
  • A write takes effect when it has gone through the room and reached everyone. The same applies on the writing player’s side, so everyone sees values in the same order.
  • You can write only your own player properties. When a player leaves, that player’s properties are deleted.
  • Keys are up to 64 bytes, with up to 1024 entries for the whole room.

Master Client

The room makes one of the participants the master client. The first player to join becomes the master, and when the master leaves, the role moves to the earliest-joined remaining player. The master can appoint another participant as the master.

If you want the server to decide game progression, use stateMachine rules instead of the master client so that modified clients cannot affect it.

Interest Group

Divide objects into groups and deliver updates only to players subscribed to them. The owner sets the group, and group 0 reaches everyone.

  • Groups filter value updates and All / Others RPCs to that object. Spawns, destroys, and changes of owner or group reach everyone.
  • When you subscribe, the current values of the group’s objects arrive immediately.
  • Subscriptions carry over across reconnections. Up to 256 groups per connection.

Ticks and Prediction

When the room rules define a tick and simulated objects, the server moves objects of that kind from inputs (ticks). The client sends inputs instead of writing positions.

  • The input is the “current state” of 32 integer slots. It is sent every room tick. The room reads slots with a range clamped to it.
  • Inputs are sent only while you own a simulated object. Each send carries the inputs of the last 3 ticks, so a lost UDP datagram is covered.
  • The client advances the tick number attached to inputs ahead of the room, so that inputs arrive slightly (2 ticks by default) before the room processes them. It adjusts its pace automatically within ±10% from the clock the room sends.
  • Even the owner can neither write values of a simulated object nor destroy it. It is removed by the rules’ Despawn or when the owner leaves.

When the rules have prediction code (prediction), the compiler emits prediction code for the client. Once registered, it works as follows.

  • Simulated objects you own show the result of each input tick immediately, without waiting for a round trip.
  • Each time the room’s results arrive, the client recomputes from them up to the current tick (rollback). When a prediction was off, the gap is closed over about 0.1 seconds so that it is hard to notice.
  • Other players’ characters are shown by interpolating the room’s results.
  • Objects spawned in a prediction (predicted spawns) are shown first, and the same instance takes over when the room’s object arrives. Objects despawned by the prediction stop rendering until the room despawns them.
  • Each input records how far behind other objects were shown on screen at that moment. The room uses it to test hits as the shooter saw them (lag compensation). For an accurate value, pass the server time to the client, and keep the view delay equal to the position interpolation delay (0.1 seconds by default).

UDP

Sending periodic updates such as positions over UDP reduces latency when the connection is congested. Things that must arrive, such as spawns, RPCs, events, and properties, stay on WebSocket.

  • The client registers with the room and keeps the registration alive. When the room responds, UDP is used; after 10 seconds without a response, the client returns to WebSocket.
  • Values sent over UDP, by the room and by clients alike, are resent over WebSocket every second in case of loss. Even if UDP stops getting through for a while, values become current within about one second once communication recovers.
  • Before ownership is handed over or control is released, the values sent over UDP are resent over WebSocket. The new writer receives the latest state over WebSocket before it starts writing.
  • State updates carry numbers in the order they were sent. Even if UDP and WebSocket deliver them out of order, an older value never overwrites a newer one. The room state used for prediction is also assembled by these numbers, not by arrival order.
  • Values sent right before an RPC or similar are sent over WebSocket, so they arrive before the RPC.
  • UDP is not available in web browsers (Unity WebGL, Godot web exports). They work over WebSocket only.

Object Catalog

If you want the room to check player object types, fields, RPCs and ownership requests, set the criteria as JSON in syncSetting.objectCatalog of the Realtime namespace settings. The keys are Prefab IDs.

Without it, the room checks none of these: any Prefab can be spawned as a player object with the fields declared at spawn, anyone may call any RPC, and anyone may take ownership. With it, only the registered Prefabs, fields and RPCs get through.

{
  "1": {
    "fields": 63,
    "mode": "player",
    "methods": [1],
    "publicMethods": [2],
    "ownership": "takeover",
    "keepOnLeave": true
  }
}
ItemDescription
fieldsA bitmask of the synced fields. Position X/Y/Z are 1/2/4, rotation X/Y/Z are 8/16/32
mode"player" for player objects
methodsIDs of RPCs the owner can call
publicMethodsIDs of RPCs any participant can call on other players’ objects
ownership"fixed" (default) or "takeover" (anyone can take ownership)
keepOnLeaveIf true, the object is not destroyed when the owner leaves but is handed over to the master client. Available only for Prefabs registered in the catalog

Instead of writing it by hand, you can generate the catalog JSON from the editor (Unity, Godot). Prefabs the rules define (shared objects and simulated objects) are not included in the generated JSON. If the catalog and the rules both define the same Prefab ID, the room fails to initialize.

Object Lifetime and Reconnection

TargetOn disconnectOn reconnect
Shared objects created by the serverRemain in the roomRedisplayed from the snapshot with their current values
Your player objectsDestroyed (handed over to the master if keepOnLeave)Recreate them if needed
Your player properties, Buffered RPCs and eventsDeletedSet them again if needed
Room propertiesRemain in the roomArrive in the snapshot
Control you heldReleasedAcquire it again if needed
Your connection IDBecomes invalidA new ID is issued

On every reconnection, the display is rebuilt from the snapshot you receive. The client does not need to replay rules or operation history. Requests that were waiting before the reconnection complete as Abandoned.

When you reconnect with the same userId, the old connection is treated as having left.

Relation to Room Termination Conditions

A room terminates when no participant has communicated for 1 minute. In sync and stateMachine rooms, Pings that keep the connection alive do not count as communication for this purpose.

The client sends only when values change. Keeping the UDP registration alive does not count either. So in games with scenes where everyone waits without operating, design the game so that the room continues during that time, for example by sending operations periodically.

Estimated Send Volume

Sync data does not send field names; it sends only the numbers and integer values of changed fields. The client sends changes in batches at a fixed interval (default 20 times per second). Writing the same value repeatedly, or returning to the previously sent value, does not increase the send volume.

Narrowing the position step size and synced axes to what you need reduces sends caused by small jitter.

Troubleshooting

These checks apply to every engine. For engine-specific checks, see the Unity Client and the Godot Client.

SymptomWhat to check
Objects are not displayedWhether you received the snapshot after connecting. Whether the arriving Prefab IDs are registered in the client’s catalog
Value magnitudes or positions are offWhether the rules (GSL) and the sync schema were generated together from the same rule definition. If not, the client logs a “generated from other rules” warning
Operations are refusedCheck the rejection notice for the refused command, the target ID and the reason (Forbidden: no permission, MissingObject: no such object, LimitReached: a limit, MethodNotAllowed: an RPC the catalog does not list, and so on)
Operations such as attacks are not reflectedCheck the request’s result. See the list of result codes
RPCs don’t arriveIf you set an object catalog, whether the RPC ID is in its methods (or publicMethods when called from other players). Renaming the method changes the ID
Spawned objects disappearCheck the spawn rejection result. Whether you exceeded the ownership limit (64). If you set an object catalog, whether the Prefab is registered with "mode": "player"
UDP is not usedWhether the room’s port is open for UDP. UDP is not available in web browsers