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
| Rule | How it works |
|---|---|
| The server creates entities | Every 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 router | The 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 transmits | Entity 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 → client | There is no direct channel between clients. Every message goes through the server, which decides who receives it. |
| Remote entities are server-authoritative | On 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
streamingDistanceand in the same dimension → stream 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 / entityStreamOut | The entity entered/left the observer's stream. Payload is raw data (entityId, entityType). |
gameEntityCreate / gameEntityDestroy | The entity gained/lost its game handle (ready for natives). |
baseObjectCreate / baseObjectRemove | A base object was created/removed. |
worldObjectStreamIn / worldObjectStreamOut | A world object streamed in/out (wrapped entity). |
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:
- Driver — the player at the wheel becomes the owner.
- Hysteresis — with no driver, the current owner is kept while they stay within
migrationDistance(default 150 m). - Nearest — otherwise the nearest player within
migrationDistancebecomes the owner. - 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.
// 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-owner | Non-owner | |
|---|---|---|
| Entity state | Your client is the source; the server relays it to observers | You receive interpolated remote state |
| Mutating a remote entity | — | Blocked: 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.
// 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 entity | Reach for a real entity |
|---|---|
| Points of interest, zones, stream anchors, synced data with a position, custom in-range logic | You need a visible/physical object in the world (ped, vehicle, prop) |
| Thousands of cheap points | Gameplay 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.
// 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
| Member | Description |
|---|---|
get(id) / getByRemoteID(id) | VE by remote ID, or null |
all | Every known VE |
streamedIn | In-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
| Member | Description |
|---|---|
get(id) | Group by ID, or null |
all | Every 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
| Event | When |
|---|---|
virtualEntityStreamIn / virtualEntityStreamOut | The VE entered/left the stream |
virtualEntityPositionChange | The VE position changed in the snapshot |
entityStreamIn / entityStreamOut | Generic contract; for a VE entityType = 9 |
See Also
- Events and RPC — exact signatures for stream, owner, and dimension events.
- server.toml —
streamingDistance,migrationDistance,maxStreamedEntities. - Metadata — synced / stream-synced / local meta.
- Streaming and Assets — streaming resource files, not entities.
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.jsandevents-bootstrap.js(stream, owner, and dimension events)