OneSync scope and entity ownership
OneSync adds server-side synchronization state and entity routing to the GTA networking model. It does not turn every game native into a guaranteed server operation, and a higher slot limit does not make an unbounded resource efficient.
For a normal state-aware server, select onesync on in configuration. onesync off retains the relay/P2P model without state awareness. onesync legacy is a compatibility mode for scripts expecting every player to exist on every client, with performance and graphical drawbacks. See the complete server command reference for startup-only controls and their documented defaults.
The pinned OneSync manual describes Infinity scaling up to 2048 slots, with subscription requirements above 48. Those are platform limits and entitlements, not a capacity estimate for your CPU, scripts or database. Verify current entitlements in the Cfx Portal ; sv_maxClients does not need to be a power of two.
Infinity, culling and iteration
The documented Legacy Infinity implementation expands network-object IDs from a 13-bit pool to a 16-bit pool and culls entities outside a focus zone of approximately 424 units. Player peds, vehicles and other players outside scope may not exist locally. Enhanced changes the network model further; do not hardcode a Legacy ID width into persistent application data.
A client uses GetActivePlayers() to iterate locally available players. A server uses GetPlayers() to obtain connected server player IDs. These are different APIs for different views; the server API is not deprecated merely because client-side iteration should avoid scanning every possible local index. A local entity handle is also not a portable network ID. Read ID conversions and scope.
The official guide warns that SetEntityDistanceCullingRadius and SetPlayerCullingRadius are deprecated and have known unfixable issues. Do not use a large culling radius as the default fix for an architecture that assumes all entities exist everywhere. Reduce reliance on global client state instead.
How sync nodes become server state
In the documented Legacy design, a reserved local player slot 31 carries synchronization data and game events to the server. This internal slot is not an application player ID to target with gameplay messages.
Network entities carry synchronization nodes. The manual uses CSectorPositionDataNode as an educational example: its parser reads three 12-bit floating values, using scale factors 54 for X/Y and 69 for Z, stores them and asks the sync tree to calculate position. The server’s GET_ENTITY_COORDS implementation retrieves position through syncTree->GetPosition, combining sector and sector-position nodes. This explains why the server can expose a getter without executing that getter on a client.
These details are implementation examples, not a stable packet format for custom parsers. Platform contributors should inspect the corresponding SyncTrees_Five.h and ServerGameState_Scripting.cpp at their chosen platform revision. Resource authors should call the native rather than reproducing the virtual-method-table or memory-offset analysis.
Server-created entities and fallible RPC
Prefer a genuine server creation path when gameplay requires the server to own the decision. CreateVehicleServerSetter(model, vehicleType, x, y, z, heading) creates the vehicle without first depending on a nearby client’s CreateVehicle call. CreatePed also has a server creation path despite historical native-reference labeling. CreateVehicle and many task setters are RPC operations: they may execute on a relevant client and can fail to execute.
This server-only, console-only diagnostic creates one owned vehicle and deletes it on request or resource stop. It requires state awareness and is not an unrestricted player spawning command.
-- server.lua; declare server_script in fxmanifest.lua.
local ownedVehicle = nil
local function removeVehicle()
if ownedVehicle and DoesEntityExist(ownedVehicle) then
DeleteEntity(ownedVehicle)
end
ownedVehicle = nil
end
RegisterCommand('docentity', function(src, args)
if src ~= 0 then return end
if args[1] == 'remove' then removeVehicle() return end
if args[1] ~= 'create' then return end
if ownedVehicle and DoesEntityExist(ownedVehicle) then return end
local vehicle = CreateVehicleServerSetter(
GetHashKey('blista'), 'automobile', 215.8, -810.1, 30.7, 160.0
)
if vehicle == 0 or not DoesEntityExist(vehicle) then
print('Server entity creation failed.')
return
end
ownedVehicle = vehicle
SetEntityOrphanMode(vehicle, 2)
print('Created one diagnostic vehicle. Use docentity remove to clean up.')
end, false)
AddEventHandler('onResourceStop', function(name)
if name == GetCurrentResourceName() then removeVehicle() end
end)SetEntityOrphanMode(entity, 2) selects KeepEntity: the server does not delete the entity merely because it becomes orphaned. It does not make it immune to a client’s deletion request, persist it across a process restart or save it to a database. Ownership may migrate or become absent as players leave scope. Apply appearance/state changes idempotently and verify important outcomes; an RPC call returning is not proof every client displayed the change.
Scope events and state bags
playerEnteredScope and playerLeftScope execute on the server. Their payload contains player and for; the latter is a Lua keyword, so use data['for'] rather than dot syntax. A diagnostic should record only the relevant pair:
AddEventHandler('playerEnteredScope', function(data)
print(('Player %s entered scope for %s'):format(data.player, data['for']))
end)Do not leave an unbounded scope logger running on a busy server. These events multiply with players in scope and can create substantial work. For a feature that reacts to an entity attribute, prefer state bags and change handlers rather than sending a full roster on every scope transition. Replication does not remove the need to validate any client-authored data.
Routing buckets
Buckets isolate groups of players and entities. The relevant server natives are GetPlayerRoutingBucket, GetEntityRoutingBucket, SetPlayerRoutingBucket, SetEntityRoutingBucket, SetRoutingBucketEntityLockdownMode and SetRoutingBucketPopulationEnabled.
Use buckets for separate game modes, a party/session or a character-selection scene. A bucket has its own population world grid; it is not a free CPU shard. Randomly moving players between buckets to balance load breaks their shared world. A normal interior with visible outside surroundings is not the intended bucket use case; the official guide instead points to concealment and appropriate scoped policies.
| Lockdown mode | Meaning |
|---|---|
inactive | Client entity creation is allowed. |
relaxed | Block script-owned client-created entities. |
strict | Block client-created entities. |
full | Enhanced-only extension that also disables dummy-object creation. |
For an isolated test session, configure the bucket before admitting players:
-- server-side initialization of a bucket this resource owns.
local testBucket = 120
SetRoutingBucketEntityLockdownMode(testBucket, 'strict')
SetRoutingBucketPopulationEnabled(testBucket, false)Move an authorized player with SetPlayerRoutingBucket(playerSource, testBucket) and their intended server-created entities with SetEntityRoutingBucket(entity, testBucket). Validate the session transition on the server, remember the prior bucket and restore it when the session ends. Do not accept an arbitrary target player or bucket number from a client. Avoid changing a shared bucket’s policy without coordinating with its owner.
Diagnose ownership migration without confusing IDs
A network ID identifies a networked entity; a local handle identifies an entity in one process. A handle cached before leaving scope or deleting an entity may be invalid later, even if an old number still exists in your application. Ownership can move between clients. Neither a client-reported owner nor a successful ID conversion authorizes a privileged server action.
| Reproduction | Record | Interpretation / next step |
|---|---|---|
| Owner leaves or disconnects | Server entity existence, current owner, both clients’ scope and timestamps | Distinguish deletion/lifetime policy from an ownership handoff |
| Client has a network ID but no local entity | Whether it is in scope and the local conversion/existence result | Do not call natives on an invalid local handle; use bounded retry only while the operation is still relevant |
| RPC appears ineffective after migration | Target identity, current owner, track/artifact and native API set | An RPC is not guaranteed merely because the server issued it; re-evaluate state instead of assuming success |
| A resource restarts during a delayed action | Resource/session generation and current entity identity | Drop stale work rather than applying it to a reused handle or different session |
Reproduce with two controlled clients: create one authorized test entity, record the first owner’s observations, move the second client into/out of scope, then disconnect the owner. Repeat with deletion and resource restart as separate cases. Restore test buckets and remove only entities owned by the test resource. This is a verification procedure, not a claimed successful runtime capture. Use the ID conversion guide for failure values and state bags for observer lifetime.
Verification and migration failures
Test nearby and distant clients, ownership transfer, owner disconnect, entity deletion, resource restart and bucket entry/exit. Record the game track and artifact. Use real gameplay tests for population and RPC outcomes; documentation tests cannot simulate OneSync.
There are no general-purpose settings named sv_maxEntities, sv_maxVehicles, sv_maxPeds or sv_maxRate in this pinned command manual. Do not copy fabricated tuning presets. Likewise, a cleanup rule must use timestamps your resource actually records; GetVehicleLastOccupiedTime is not a documented native. Keep a resource-owned registry and delete only owned entities under an explicit lifecycle policy.
Continue with migration from other platforms, network-event security, server configuration and state bags.