Skip to content

Sync Model

Varion uses a server-authoritative model. The server creates every networked entity, assigns its network ID, and stays the single source of truth. Clients only render state and transmit their own player's input. Clients never exchange state directly — all traffic goes through the server.

This page explains the model to a resource author: how the server streams entities, what net ownership and its migration are, how to isolate worlds with dimensions, and when to use virtual entities instead of real game entities.

The Server Is the Source of Truth

RuleHow it works
The server creates entitiesEvery networked entity (player, vehicle, ped, object, virtual entity) and its network ID are created on the server. A client cannot create a networked entity.
The server is the routerThe server accepts state from an entity's owner and relays it to the right observers. It also drives stream-in/out and owner migration.
Only the net-owner transmitsEntity state is accepted only from its net-owner (or from anyone if the entity is ownerless). Any other sender's packet is dropped.
No client → clientThere is no direct channel between clients. Every message goes through the server, which decides who receives it.
Remote entities are server-authoritativeOn the client, remote entities are read-only. entity.setPos/setRot/destroy on another player return false.

Validate authoritative logic (gameplay, inventory, economy, permissions, movement) on the server — see Platform Overview, the Trust model section.

Entity Streaming

The server decides which entities each player "sees" through a spatial hash grid:

  • The world is divided into square cells whose side equals streamingDistance (default 300 m).
  • For each observer the server checks its own cell and the eight neighbours (3×3).
  • An entity within streamingDistance and in the same dimensionstream in.
  • An entity out of range or in a different dimension → stream out.

What an Entity Author Receives

On stream-in the server sends a keyframe (full state) plus stream-synced metadata; on stream-out it sends a notice. On the client this surfaces as events:

Event (client)When
entityStreamIn / entityStreamOutThe entity entered/left the observer's stream. Payload is raw data (entityId, entityType).
gameEntityCreate / gameEntityDestroyThe entity gained/lost its game handle (ready for natives).
baseObjectCreate / baseObjectRemoveA base object was created/removed.
worldObjectStreamIn / worldObjectStreamOutA world object streamed in/out (wrapped entity).
js
varion.on('entityStreamIn', (data) => {
  varion.log(`stream in type=${data.entityType} id=${data.entityId}`);
});

varion.on('gameEntityCreate', (entity) => {
  // handle is ready — natives can run on entity.scriptID
});

entityStreamIn tells you the server started replicating the entity; gameEntityCreate arrives later — once the game handed out a local handle. Call natives after gameEntityCreate, not straight away on entityStreamIn.

Why Deltas Only Reach Streamed-In Observers

Delta updates (position, rotation, health, weapon, and so on) are sent only to the observers currently streaming the entity. This is the core of scalability: a player across the map spends no bandwidth on entities they cannot see. Until an entity is in-stream for an observer, that observer receives no deltas for it — only the keyframe at stream-in.

Streaming limits are streamingDistance and maxStreamedEntities (see server.toml).

Net Ownership and Migration

The net-owner is the client that simulates an entity and sends its state to the server. The server accepts state only from the net-owner (or from anyone if the entity is ownerless) and relays it to streamed-in observers.

  • Every player is the net-owner of their own local player.
  • Vehicle: the net-owner is whoever drives it. An empty vehicle is either ownerless or bound to the nearest player.

When It Migrates (Vehicle)

The server recomputes vehicle owners several times per second (~6–10 Hz) by priority:

  1. Driver — the player at the wheel becomes the owner.
  2. Hysteresis — with no driver, the current owner is kept while they stay within migrationDistance (default 150 m).
  3. Nearest — otherwise the nearest player within migrationDistance becomes the owner.
  4. Ownerless — with nobody nearby, the vehicle is "frozen" (no owner).

On an owner change:

  • The server fires netOwnerChange(entity, owner, oldOwner) — see Events and RPC.
  • The client receives netOwnerChange(entity, owner, previousOwner), but only when the local player gains or loses ownership.
js
// client
varion.on('netOwnerChange', (entity, owner, previousOwner) => {
  if (owner && owner.id === varion.Player.local.id) {
    // we now simulate this entity
  }
});

Owner vs Non-Owner

Net-ownerNon-owner
Entity stateYour client is the source; the server relays it to observersYou receive interpolated remote state
Mutating a remote entityBlocked: setPos/setRot/destroy on another player → false (server-authoritative)

Dimensions

A dimension is a numeric isolation layer. Entities and players in different dimensions cannot see each other: the server filters both streaming and relay by dimension. Use them for instances, interiors, minigames, and separate lobbies.

  • Server: changing a player's dimension triggers an immediate streaming recompute (streaming out entities of other dimensions). Event playerDimensionChange(player, oldDimension, newDimension).
  • Client: localDimension — the local player's dimension changed; playerDimensionChange — a dimension-change event. Exact signatures are in Events and RPC.
js
// client
varion.on('playerDimensionChange', (player, oldDimension, newDimension) => {
  varion.log(`${player.id}: ${oldDimension} -> ${newDimension}`);
});

Virtual Entities

A virtual entity is a lightweight synced object with a position but no game entity: no model, no collision, no natives. The server streams them by the same spatial rules as real entities, but without the cost of creating a game object.

Reach for a virtual entityReach for a real entity
Points of interest, zones, stream anchors, synced data with a position, custom in-range logicYou need a visible/physical object in the world (ped, vehicle, prop)
Thousands of cheap pointsGameplay interaction through natives

Virtual entities are created and streamed by the server (server authority) — the client only reads them; there is no client-side constructor.

Groups and Cell Key

Virtual entities are grouped into a VirtualEntityGroup. A group sets the default streaming radius (maxStreamingDistance) and the cap of concurrently streamed entities (maxEntitiesInStream). The cellKey helper lets an author bucket positions into grid cells.

js
// client
varion.on('virtualEntityStreamIn', (ve) => {
  varion.log(`VE ${ve.id} group=${ve.groupId} dist=${ve.streamingDistance}`);
});

varion.on('virtualEntityPositionChange', (ve) => {
  varion.log(`VE ${ve.id} -> ${ve.pos.x}, ${ve.pos.y}`);
});

const ve = varion.VirtualEntity.get(remoteId);
if (ve) varion.log(ve.isStreamedIn, ve.visible, ve.dimension);

const key = varion.VirtualEntityGroup.cellKey(0.0, 0.0, 100);

API (Client, Read)

varion.VirtualEntity

MemberDescription
get(id) / getByRemoteID(id)VE by remote ID, or null
allEvery known VE
streamedInIn-stream VE only

Instance properties: id / remoteId, groupId, group, isStreamedIn / streamed, streamingDistance (per-entity override or the group radius), visible, dimension, pos, rot, plus metadata methods like any entity (see Metadata).

varion.VirtualEntityGroup

MemberDescription
get(id)Group by ID, or null
allEvery group
cellKey(x, y, cellSize = 100)Grid key "cx:cy" for bucketing positions

Group properties: id, maxStreamingDistance (default 400), maxEntitiesInStream (default 128), streamedIn (in-stream VE of this group).

Virtual Entity Events

EventWhen
virtualEntityStreamIn / virtualEntityStreamOutThe VE entered/left the stream
virtualEntityPositionChangeThe VE position changed in the snapshot
entityStreamIn / entityStreamOutGeneric contract; for a VE entityType = 9

See Also

Source Backing

This page is based on:

  • server/src/CServer.cpp (UpdateStreaming, UpdateVehicleOwnership, ApplyVehicleOwnerChange, RelayPlayerPresentation)
  • include/varion.h (streamingDistance, migrationDistance, maxStreamedEntities)
  • sdk/client-js-runtime/injected/world-bootstrap.js (VirtualEntity, VirtualEntityGroup)
  • sdk/client-js-runtime/injected/entities-bootstrap.js and events-bootstrap.js (stream, owner, and dimension events)

Varion Multiplayer Platform