🎯

networking-replication

🎯Skill

from taozhuo/game-dev-skills

VibeIndex|
What it does

Optimizes Roblox multiplayer networking with efficient event batching, delta compression, and rate limiting for smooth gameplay.

networking-replication

Installation

Install skill:
npx skills add https://github.com/taozhuo/game-dev-skills --skill networking-replication
1
AddedJan 25, 2026

Skill Details

SKILL.md

Implements networking and replication systems including RemoteEvent optimization, custom replication, lag compensation, client prediction, and bandwidth optimization. Use when building multiplayer games that need smooth networked gameplay.

Overview

# Roblox Networking & Replication

When implementing networking systems, follow these Roblox-specific patterns for optimal multiplayer experience.

RemoteEvent Optimization

Batch Multiple Events

```lua

-- BAD: Firing many events per frame

for _, enemy in ipairs(enemies) do

UpdateEnemyRemote:FireClient(player, enemy.id, enemy.position, enemy.health)

end

-- GOOD: Batch into single event

local updates = {}

for _, enemy in ipairs(enemies) do

table.insert(updates, {

id = enemy.id,

pos = enemy.position,

hp = enemy.health

})

end

UpdateEnemiesRemote:FireClient(player, updates)

```

Delta Compression

```lua

-- Only send changed values

local lastSentState = {}

local function sendStateUpdate(player, entityId, newState)

local lastState = lastSentState[player.UserId] and lastSentState[player.UserId][entityId] or {}

local delta = {}

for key, value in pairs(newState) do

if lastState[key] ~= value then

delta[key] = value

end

end

if next(delta) then -- Only send if changes exist

StateUpdateRemote:FireClient(player, entityId, delta)

lastSentState[player.UserId] = lastSentState[player.UserId] or {}

lastSentState[player.UserId][entityId] = newState

end

end

```

Rate Limiting

```lua

local RateLimiter = {}

RateLimiter.calls = {}

function RateLimiter.check(player, remoteName, maxCallsPerSecond)

local key = player.UserId .. "_" .. remoteName

local now = os.clock()

RateLimiter.calls[key] = RateLimiter.calls[key] or {}

local calls = RateLimiter.calls[key]

-- Remove old calls (older than 1 second)

for i = #calls, 1, -1 do

if now - calls[i] > 1 then

table.remove(calls, i)

end

end

-- Check limit

if #calls >= maxCallsPerSecond then

return false -- Rate limited

end

table.insert(calls, now)

return true

end

-- Usage in RemoteEvent handler

MyRemote.OnServerEvent:Connect(function(player, data)

if not RateLimiter.check(player, "MyRemote", 10) then

warn("Rate limited:", player.Name)

return

end

-- Process request

end)

```

Unreliable RemoteEvents

```lua

-- Use UnreliableRemoteEvent for frequent non-critical updates

local PositionUpdate = Instance.new("UnreliableRemoteEvent")

PositionUpdate.Name = "PositionUpdate"

PositionUpdate.Parent = ReplicatedStorage

-- Good for: position updates, mouse position, non-critical effects

-- Bad for: damage, purchases, important state changes

```

Custom Replication System

NPC Replication with Interpolation

```lua

-- Server: Send position snapshots

local NPC_UPDATE_RATE = 1/20 -- 20 updates per second

local function broadcastNPCPositions()

local updates = {}

for _, npc in ipairs(activeNPCs) do

table.insert(updates, {

id = npc.id,

pos = npc.PrimaryPart.Position,

rot = npc.PrimaryPart.Orientation.Y,

vel = npc.PrimaryPart.AssemblyLinearVelocity,

state = npc:GetAttribute("State"),

timestamp = workspace:GetServerTimeNow()

})

end

NPCUpdateRemote:FireAllClients(updates)

end

task.spawn(function()

while true do

broadcastNPCPositions()

task.wait(NPC_UPDATE_RATE)

end

end)

```

```lua

-- Client: Interpolate between snapshots

local InterpolationBuffer = {}

InterpolationBuffer.buffers = {}

InterpolationBuffer.BUFFER_TIME = 0.1 -- 100ms interpolation delay

function InterpolationBuffer.addSnapshot(entityId, snapshot)

InterpolationBuffer.buffers[entityId] = InterpolationBuffer.buffers[entityId] or {}

local buffer = InterpolationBuffer.buffers[entityId]

table.insert(buffer, snapshot)

-- Keep only recent snapshots

while #buffer > 10 do

table.remove(buffer, 1)

end

end

function InterpolationBuffer.getInterpolatedState(entityId)

local buffer = InterpolationBuffer.buffers[entityId]

if not buffer or #buffer < 2 then return nil end

local renderTime = workspace:GetServerTimeNow() - InterpolationBuffer.BUFFER_TIME

-- Find surrounding snapshots

local prev, next

for i = #buffer, 1, -1 do

if buffer[i].timestamp <= renderTime then

prev = buffer[i]

next = buffer[i + 1]

break

end

end

if not prev then

return buffer[1] -- Extrapolate if too far behind

end

if not next then

-- Extrapolate forward

local dt = renderTime - prev.timestamp

return {

pos = prev.pos + prev.vel * dt,

rot = prev.rot,

state = prev.state

}

end

-- Interpolate between snapshots

local t = (renderTime - prev.timestamp) / (next.timestamp - prev.timestamp)

return {

pos = prev.pos:Lerp(next.pos, t),

rot = prev.rot + (next.rot - prev.rot) * t,

state = next.state -- Use newer state

}

end

```

Late Joiner Synchronization

```lua

-- Server: Send full state to joining players

Players.PlayerAdded:Connect(function(player)

-- Wait for character to load

player.CharacterAdded:Wait()

-- Send current game state

local fullState = {

enemies = {},

players = {},

worldState = {}

}

for _, enemy in ipairs(activeEnemies) do

table.insert(fullState.enemies, {

id = enemy.id,

type = enemy.Type,

pos = enemy.PrimaryPart.Position,

health = enemy.Health,

maxHealth = enemy.MaxHealth

})

end

for _, otherPlayer in ipairs(Players:GetPlayers()) do

if otherPlayer ~= player then

table.insert(fullState.players, {

userId = otherPlayer.UserId,

position = otherPlayer.Character and otherPlayer.Character.PrimaryPart.Position,

stats = getPlayerStats(otherPlayer)

})

end

end

fullState.worldState = {

timeOfDay = Lighting.ClockTime,

weather = currentWeather,

eventFlags = activeEvents

}

FullStateSyncRemote:FireClient(player, fullState)

end)

```

Server Authority

Authoritative Movement Validation

```lua

-- Server: Validate client-claimed positions

local MAX_SPEED = 50 -- studs per second

local TOLERANCE = 1.5 -- Multiplier for latency tolerance

local lastValidPositions = {}

local function validateMovement(player, claimedPosition)

local character = player.Character

if not character then return false end

local lastPos = lastValidPositions[player.UserId]

if not lastPos then

lastValidPositions[player.UserId] = {

position = claimedPosition,

time = os.clock()

}

return true

end

local deltaTime = os.clock() - lastPos.time

local distance = (claimedPosition - lastPos.position).Magnitude

local maxDistance = MAX_SPEED deltaTime TOLERANCE

if distance > maxDistance then

-- Potential speed hack - reject and correct

warn("Movement validation failed for", player.Name)

character:PivotTo(CFrame.new(lastPos.position))

return false

end

lastValidPositions[player.UserId] = {

position = claimedPosition,

time = os.clock()

}

return true

end

```

Server-Side Hit Registration

```lua

-- Server: Validate all combat hits

HitClaimRemote.OnServerEvent:Connect(function(player, hitData)

local attacker = player.Character

local target = getCharacterById(hitData.targetId)

if not attacker or not target then return end

-- Validate distance

local distance = (attacker.PrimaryPart.Position - target.PrimaryPart.Position).Magnitude

local maxRange = getAttackRange(hitData.attackType) * 1.2 -- 20% tolerance

if distance > maxRange then

warn("Hit rejected: out of range")

return

end

-- Validate timing (attack cooldown)

local lastAttack = attacker:GetAttribute("LastAttackTime") or 0

local cooldown = getAttackCooldown(hitData.attackType)

if os.clock() - lastAttack < cooldown * 0.9 then

warn("Hit rejected: attack on cooldown")

return

end

-- Validate line of sight

local rayParams = RaycastParams.new()

rayParams.FilterDescendantsInstances = {attacker}

local ray = workspace:Raycast(

attacker.PrimaryPart.Position,

(target.PrimaryPart.Position - attacker.PrimaryPart.Position),

rayParams

)

if ray and ray.Instance:IsDescendantOf(target) then

-- Valid hit - apply damage

local damage = calculateDamage(hitData.attackType, attacker, target)

applyDamage(target, damage, attacker)

attacker:SetAttribute("LastAttackTime", os.clock())

end

end)

```

Client Prediction

Input Prediction with Reconciliation

```lua

-- Client: Apply inputs immediately, reconcile with server

local InputHistory = {}

local MAX_HISTORY = 60 -- 1 second at 60fps

local function processInput(input)

local inputId = #InputHistory + 1

local predictedState = applyInput(localCharacter, input)

-- Store for reconciliation

table.insert(InputHistory, {

id = inputId,

input = input,

predictedState = predictedState,

timestamp = os.clock()

})

-- Trim old history

while #InputHistory > MAX_HISTORY do

table.remove(InputHistory, 1)

end

-- Send to server

InputRemote:FireServer(inputId, input)

end

-- Client: Reconcile with server state

ServerStateRemote.OnClientEvent:Connect(function(serverState)

-- Find the input this state corresponds to

local reconciledIndex

for i, entry in ipairs(InputHistory) do

if entry.id == serverState.lastProcessedInput then

reconciledIndex = i

break

end

end

if not reconciledIndex then return end

-- Check if prediction was wrong

local predicted = InputHistory[reconciledIndex].predictedState

local error = (serverState.position - predicted.position).Magnitude

if error > 0.1 then -- Significant error

-- Snap to server position

localCharacter:PivotTo(CFrame.new(serverState.position))

-- Re-apply unacknowledged inputs

for i = reconciledIndex + 1, #InputHistory do

applyInput(localCharacter, InputHistory[i].input)

end

end

-- Remove acknowledged inputs

for i = reconciledIndex, 1, -1 do

table.remove(InputHistory, i)

end

end)

```

Lag Compensation

Server-Side Lag Compensation

```lua

-- Store position history for all players

local PositionHistory = {}

local HISTORY_DURATION = 1 -- 1 second of history

local function recordPosition(character)

local userId = Players:GetPlayerFromCharacter(character).UserId

PositionHistory[userId] = PositionHistory[userId] or {}

table.insert(PositionHistory[userId], {

position = character.PrimaryPart.Position,

timestamp = workspace:GetServerTimeNow()

})

-- Trim old entries

local cutoff = workspace:GetServerTimeNow() - HISTORY_DURATION

while #PositionHistory[userId] > 0 and PositionHistory[userId][1].timestamp < cutoff do

table.remove(PositionHistory[userId], 1)

end

end

local function getPositionAtTime(userId, timestamp)

local history = PositionHistory[userId]

if not history or #history == 0 then return nil end

-- Find surrounding entries

for i = #history, 1, -1 do

if history[i].timestamp <= timestamp then

local prev = history[i]

local next = history[i + 1]

if not next then

return prev.position

end

-- Interpolate

local t = (timestamp - prev.timestamp) / (next.timestamp - prev.timestamp)

return prev.position:Lerp(next.position, t)

end

end

return history[1].position

end

-- Use in hit detection

local function lagCompensatedHitCheck(shooter, targetUserId, shooterTimestamp)

-- Rewind target to shooter's view time

local ping = shooter:GetNetworkPing()

local viewTime = shooterTimestamp - ping / 2

local targetPastPosition = getPositionAtTime(targetUserId, viewTime)

if not targetPastPosition then return false end

-- Check if shot would have hit at that position

-- ... perform hit detection with past position

end

```

Bandwidth Optimization

Quantization

```lua

-- Reduce precision for network transmission

local function quantizePosition(position)

return Vector3.new(

math.floor(position.X * 10) / 10, -- 0.1 stud precision

math.floor(position.Y * 10) / 10,

math.floor(position.Z * 10) / 10

)

end

local function quantizeRotation(rotation)

return math.floor(rotation * 100) / 100 -- ~0.6 degree precision

end

-- Pack multiple small values into single number

local function packHealth(current, max)

-- Assumes max health <= 65535 and current <= max

return current * 65536 + max

end

local function unpackHealth(packed)

local max = packed % 65536

local current = math.floor(packed / 65536)

return current, max

end

```

String Table for Identifiers

```lua

-- Instead of sending full strings, use numeric IDs

local StringTable = {

["FireProjectile"] = 1,

["TakeDamage"] = 2,

["UseAbility"] = 3,

-- ... etc

}

local ReverseTable = {}

for str, id in pairs(StringTable) do

ReverseTable[id] = str

end

-- Send: ActionRemote:FireServer(StringTable["FireProjectile"], data)

-- Receive: local action = ReverseTable[actionId]

```

Entity Relevancy

```lua

-- Only replicate entities relevant to each player

local RELEVANCY_DISTANCE = 200

local function getRelevantEntities(player)

local character = player.Character

if not character then return {} end

local playerPos = character.PrimaryPart.Position

local relevant = {}

for _, entity in ipairs(allEntities) do

local distance = (entity.Position - playerPos).Magnitude

if distance <= RELEVANCY_DISTANCE then

-- Close entities get full updates

table.insert(relevant, {entity = entity, priority = 1})

elseif distance <= RELEVANCY_DISTANCE * 2 then

-- Medium distance gets reduced updates

table.insert(relevant, {entity = entity, priority = 0.5})

end

-- Far entities not replicated

end

return relevant

end

```

More from this repository10

🎯
audio-system🎯Skill

Manages audio systems with advanced sound pooling, priority management, and performance optimization for immersive game sound experiences.

🎯
animation-system🎯Skill

animation-system skill from taozhuo/game-dev-skills

🎯
game-systems🎯Skill

Implements robust game systems like inventory, shops, trading, and progression mechanics for Roblox RPGs with secure, stackable item management.

🎯
optimization🎯Skill

optimization skill from taozhuo/game-dev-skills

🎯
vfx-effects🎯Skill

Generates dynamic visual effects like particle systems, camera shakes, and impact animations for enhancing game experiences in Roblox.

🎯
security-anticheat🎯Skill

security-anticheat skill from taozhuo/game-dev-skills

🎯
roblox-api-patterns🎯Skill

Provides expert guidance on navigating Roblox-specific API behaviors, performance optimizations, and common coding pitfalls in Lua scripting.

🎯
gemini-image-prompting🎯Skill

Generates conceptual game art images using Gemini AI, providing precise style-specific prompts for character, environment, and UI design across various game aesthetics.

🎯
combat-system🎯Skill

Enables comprehensive combat mechanics with advanced hitbox detection, damage calculation, and hit validation for creating dynamic player-vs-player and player-vs-NPC combat interactions.

🎯
ui-ux🎯Skill

Designs responsive, adaptive UI systems with dynamic screen sizing, safe area handling, and smooth animations for game interfaces.