---
title: "Server authority techniques"
url: /docs/en-us/projects/server-authority/techniques
last_updated: 2026-06-10T23:09:13Z
description: "Techniques for creating high-quality, smooth, multiplayer experiences using the server authority model."
---

# Server authority techniques

This guide outlines various techniques for creating high-quality, smooth, multiplayer experiences using the [server authority model](/docs/en-us/projects/server-authority.md).

## Predictive instance creation (instance stitching)

Instance **stitching** lets client scripts predict `Datatype.Instance.new()` calls made inside `Class.RunService:BindToSimulation()` callbacks. The client creates the `Class.Instance` immediately without waiting for a server round‑trip; when the server's authoritative copy arrives, the client‑created instance and the server's authoritative copy are merged into one. From your script's perspective, the `Class.Instance` exists immediately and is consistent with the server.

Instance stitching is useful in cases where an instance must be visible and active on the client as soon as possible. While the server will eventually replicate any instance the client needs (along with any effects they had on the world), this process incurs at least one round‑trip of latency due to server communication. Examples include firing a rocket launcher and creating physics constraints — without stitching, the client will see the rocket pop in far away from them, or some jitter when the new constraints replicate to them.

### Technical behavior

Instance stitching works by generating the same deterministic GUID on both the client and the server for the same `Datatype.Instance.new()` call. The GUID is derived from four inputs: the type of the `Class.Instance` being created, the calling script's identity (two scripts with the same text are considered different), the current simulation frame, and a per‑script call counter that resets each frame. If client and server agree on all four inputs, they produce matching GUIDs and the stitch succeeds.

### Implementation

To utilize instance stitching, simply call `Datatype.Instance.new()` inside a `Class.RunService:BindToSimulation()|BindToSimulation()` callback from a `Class.ModuleScript` that gets required on both the client **and** the server. Nothing else is required on your end; the system handles GUID assignment and reconciliation automatically.

```lua
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local Simulation = {}

Simulation.Initialize = function()
	RunService:BindToSimulation(function(deltaTime)
		local part = Instance.new("Part", workspace)
		-- Part exists immediately on the client and will be reconciled with the server
	end)
end

return Simulation
```

### Limitations

Currently, instance stitching has the following limitations (to be resolved in the future):

> **Warning:** Setting `Class.Instance.Parent|Parent` separately from `Datatype.Instance.new()` is not supported inside a simulation callback. Instead, pass the parent as the second argument to `Datatype.Instance.new()`:```lua
-- Correct
local part = Instance.new("Part", workspace)

-- Not supported (will fail)
local part = Instance.new("Part")
part.Parent = workspace
```
> **Warning:** Setting other properties such as `Class.Instance.Name|Name`, `Class.BasePart.Size|Size`, or `Class.BasePart.CFrame|CFrame` must be deferred to the next frame using `Library.task.defer()`:```lua
local part = Instance.new("Part", workspace)
task.defer(function()
	part.Name = "PredictedPart"
	part.Size = Vector3.new(2, 2, 2)
end)
```
> **Warning:** Stitching only applies to `Datatype.Instance.new()`. Instances created via `Class.Instance:Clone()` or `Datatype.Instance.fromExisting()` inside a simulation callback will not be reconciled with the server and will remain client‑local.
## Position smoothing

You can visually smooth out the position of mispredicted synchronized objects by rendering a different object than what is being simulated.

1. Make the **simulated** object invisible.
2. Make a **renderer** object as a massless, non‑collidable, visual‑only clone to track the simulated object.
3. Attach a script to the renderer object that smoothly tracks the position of the invisible, simulated object. This separation between rendering and simulation lets you alter the position of the renderer object to create a visually smooth experience.

In the following sample `Class.Script`, the rendered object (parent) smoothly tracks the simulated object. The rendered object is always slightly "behind" the simulated object which is typically fine but may be undesirable in certain situations.

```lua
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")

-- Object to smoothly track
local smoothTarget:BasePart = Workspace.SimulatedPart
-- Visual object that will be smoothed
local renderer:BasePart = script.Parent
-- Time to smooth over; smaller means faster
local smoothTime = 0.07
-- Store data needed to compute the smooth position
local smoothVelocity = Vector3.new()

-- Disable the renderer object's physics
renderer.Massless = true
renderer.Anchored = true
renderer.CanCollide = false

RunService.RenderStepped:Connect(function(deltaTime: number)
	-- Smoothly track the target object
	local smoothPosition, smoothVelocity = TweenService:SmoothDamp(
		renderer.Position,
		smoothTarget.Position,
		smoothVelocity,
		smoothTime,
		math.huge,
		deltaTime)
	renderer.Position = smoothPosition
end)
```

The [Soccer](https://www.roblox.com/games/110687099504272/Soccer-Server-Authority-Template) example experience uses a variation of this technique to more intelligently turn on and off position smoothing for the soccer ball. Specifically, the soccer ball only smooths its position when the simulated ball has "jumped" far enough away from the rendered ball. This approach provides the best of both worlds: the soccer ball has no visual latency under normal conditions, and the experience smoothly interpolates its position only after the simulated ball has unexpectedly jumped to a new location, likely due to a network artifact or server‑side change.

## Animations, sounds, and visual effects

In a predicted simulation, it's possible to trigger effects, sounds, or animations for events that the client predicted would happen but which never occurred on the server. The rendering system should be prepared to "undo" any mispredicted effects. For example, a client might predict that a grenade exploded and trigger a particle effect, but if another player diffused the grenade, the client should hide the particle effect.

A good strategy for rendering a predicted simulation is to synchronize a state machine pattern within the simulation loop and render changes to the state in a render step function. The following example simulates a grenade with a state machine pattern:

```lua
local Workspace = game:GetService("Workspace")

local module = {}

module.GrenadeStates = {
	Idle = 0,
	Lit = 1,
	Exploded = 2,
	Defused = 3,
}
module.GrenadeExplodeTime = 3.0

module.Initialize = function(grenade)
	RunService:BindToSimulation(function(deltaTime)
		-- Initialize empty grenade state
		local grenadeState = grenade:GetAttribute("State")
		if grenadeState == nil then
			grenadeState = module.GrenadeStates.Idle
			grenade:SetAttribute("State", grenadeState)
			grenade:SetAttribute("Timer", 0.0)
		end

		-- Increment grenade timer
		local timer = grenade:GetAttribute("Timer")
		timer = timer + deltaTime
		grenade:SetAttribute("Timer", timer)

		-- Explode lit grenades
		if grenadeState == module.GrenadeStates.Lit then
			if timer >= module.GrenadeExplodeTime then
				grenadeState = module.GrenadeStates.Exploded
				grenade:SetAttribute("State", grenadeState)
				grenade:SetAttribute("Timer", 0.0)
			end
		end
	end)
end

return module
```

With the previous state machine in place, you can render grenade effects in a `Class.RunService.RenderStepped` connection within a separate script based on the synchronized grenade state:

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Simulation = require(ReplicatedStorage.Simulation)

local grenade = script.Parent
local previousGrenadeState = nil

-- Highlight instance to indicate grenade state
local highlight = Instance.new("Highlight")
highlight.Parent = grenade
highlight.FillTransparency = 1
highlight.OutlineTransparency = 1
highlight.DepthMode = Enum.HighlightDepthMode.Occluded

RunService.RenderStepped:Connect(function(deltaTime: number)
	local grenadeState = grenade:GetAttribute("State")
	local grenadeTimer = grenade:GetAttribute("Timer")

	-- Emit the lit particles if the grenade is lit
	grenade.LitEmitter.Enabled = grenadeState == Simulation.GrenadeStates.Lit
	-- Play the explosion emitter if the grenade just exploded
	if previousGrenadeState ~= grenadeState then
		if grenadeState == Simulation.GrenadeStates.Exploded and grenadeTimer < 0.2 then
			grenade.ExplosionEmitter:Emit(100)
			grenade.ExplosionSound:Play()
		end
		previousGrenadeState = grenadeState
	end 

	-- Change the grenade's highlight color based on the state and time
	if grenadeState == Simulation.GrenadeStates.Lit then
		highlight.FillColor = Color3.fromRGB(255, 0, 0)
		highlight.FillTransparency = 1 - (grenadeTimer / Simulation.GrenadeExplodeTime)
	elseif grenadeState == Simulation.GrenadeStates.Idle then
		highlight.FillTransparency = 1
	elseif grenadeState == Simulation.GrenadeStates.Exploded then
		highlight.FillTransparency = 1
	elseif grenadeState == Simulation.GrenadeStates.Defused then
		highlight.FillColor = Color3.fromRGB(0, 255, 125)
		highlight.FillTransparency = 0.5
	end
end)
```

## Designing around network latency

Certain gameplay mechanics lend themselves better to networked multiplayer than other mechanics. Players will always have some delay between when another player performs an action and when they receive that player's input. The best way to create a super smooth multiplayer experience is to design your experience with these limitations in mind.

For example, an experience with slower acceleration on player movement will appear smoother than one with higher acceleration because the difference in position caused by the network latency of player input will be less than in an experience with higher acceleration.

As another example, a gameplay mechanic where players can **instantly** trigger a large explosion by pressing an input will have more network artifacts than if the explosion is delayed after the input, as if by lighting a fuse. This puts the resimulation on the fuse effect instead of on the explosion effect which is a less noticeable network artifact.

## Predicting other player inputs

By default, Roblox does not forward the inputs from each client to every other client. Whether this is right for your experience depends on its design:

- For basic humanoid movement, the default behavior means that other player characters' movements are not extrapolated from the authoritative server state and, as a result, other player characters will not mispredict but will render slightly in the past.
- In a racing game, by contrast, the default behavior means that clients will not know whether other players are applying the throttle or other inputs, so other cars may appear behind the local player even if they're actually ahead. To alleviate this, you can store player inputs in [attributes](/docs/en-us/studio/properties.md#instance-attributes) on the server and operate on those synchronized attributes client‑side using `Class.RunService:BindToSimulation()` as demonstrated in the following code sample and the [Racing](https://www.roblox.com/games/134686834388911/Racing-Server-Authority-Template) template. This approach lets you use attributes as inputs to your simulation to have fully replicated player inputs.

```lua
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local module = {}

module.storePlayerInput = function(player:Player, humanoidRootPart:BasePart)
	local inputContext:InputContext = player.PlayerGui.InputContext
	local throttle = inputContext.DefuseAction:GetState()
	humanoidRootPart:SetAttribute("Throttle", throttle)
	-- Write any other inputs into attributes...

end

module.Initialize = function()
	RunService:BindToSimulation(function(deltaTime)
		if RunService:IsServer() then
			-- Forward inputs from server to all clients
			for _, player in Players:GetPlayers() do
				local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
				local inputContext:InputContext = player.PlayerGui.InputContext
				module.storePlayerInput(player, humanoidRootPart)
			end
		else
			-- Write local player inputs as attributes
			local player = Players.LocalPlayer
			local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
			local inputContext:InputContext = player.PlayerGui.InputContext
			module.storePlayerInput(player, humanoidRootPart)
		end

		-- Use the attributes as inputs to the game
		for _, player in Players:GetPlayers() do
			local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
			local throttle = humanoidRootPart:GetAttribute("Throttle")
			if throttle then
				-- Apply the throttle to the player's vehicle
			end
		end
	end)
end)

return module
```

## Debugging

There are some new tools and techniques you can use to debug a server‑authoritative experience.

### Server authority visualizer

Pressing `Ctrl``Shift``F6` (Windows) or `⌘``Shift``F6` (Mac) opens Studio's **server authority visualizer** which shows several key pieces of information:

| Details | Description |
| --- | --- |
| **Instance prediction success rate** | The percentage of correctly predicted instances over the last 8 seconds. |
| **Input accept rate** | The percentage of all players' inputs that arrived on time on the server. Late inputs will lower this number. |
| **Client-server step delta** | The number of frames between the client and the server, including the join time of the client. The stability of this number represents the stability of your connection to the server. |
| **RCC heartbeat FPS** | The frame rate of the simulation on the server. If this number drops below 59, the server cannot keep up with the simulation and the experience will degrade in quality. |
| **Predicted instance count** | The number of instances your client is [predicting](/docs/en-us/projects/server-authority.md#client-prediction). |
| **Input drop reason counts** | The number of times the server has dropped an input for each reason:<ul><li>**[x] too old** — The inputs arrived late, meaning your network worsened or the client could not keep up with the simulation.</li><li>**[x] out of order** — A networking bug occurred that caused your inputs to be rearranged and discarded.</li><li>**[x] buffer full** — The server could not buffer your input. Either your network suddenly improved or the server could not keep up with the simulation.</li></ul> |

### Simulation radius

When relying on automatic prediction (`Enum.PredictionMode.Automatic`), you can visualize the prediction radius around your player character by enabling **Are Regions Enabled** in Studio's settings (`Alt``S` on Windows; `⌥``S` on Mac). The green cylinder indicates the range around your character in which instances are predicted, and its radius grows and shrinks based on the performance characteristics of the device.

![Simulation radius around player character with server authority running](../../assets/studio/debugging/Server-Authority-Simulation-Radius.jpg)