Lua for Game Development — Chapter 16: Multiplayer, Netcode, Sync Models & Lag Compensation
Leeting Yan
Multiplayer is the hardest part of game development.
This chapter covers the exact models used by:
- Fortnite
- Apex Legends
- Overwatch
- Rocket League
- Factorio
- StarCraft II
- Roblox
- Unity Netcode / Unreal replication
You will learn the following sync models:
- Lockstep (deterministic strategy / SLG)
- Snapshot interpolation (action games)
- Client-side prediction & reconciliation
- Entity replication (position/rotation/state)
- Combat sync
- Bullet/projectile sync
- Lag compensation (server rewind, hit-scan)
- Anti-cheat considerations
Lua is excellent for netcode:
- lightweight simulation
- deterministic math (if careful)
- fast serialization
- cross-platform
- embeddable
- extremely flexible
1. Networking Architecture Overview
NEVER trust the client.
Server is always the authority.
A typical architecture:
Client 1 →
→ Dedicated Server (Lua Simulation) → Game State
Client 2 →
Clients send inputs, not authoritative states.
Server sends snapshots or replicated entity data.
2. Netcode Models Overview
| Model | Use Cases | Pros | Cons |
|---|---|---|---|
| Lockstep | RTS, SLG, turn-based | Deterministic, low bandwidth | Requires 100% determinism |
| Snapshot+Interp | Action games, shooters, platformers | Smooth, flexible | Higher bandwidth |
| Client Prediction + Reconciliation | Fast action / shooter | Very responsive | More complex |
| Authoritative Server w/ Replication | General MP design | Secure, scalable | Requires server tick |
We will implement all of them.
3. Lockstep Simulation (SLG / RTS)
Used by:
- StarCraft
- Dota
- Factorio
- CoH
- Many SLGs
Idea:
- All clients run the same deterministic simulation.
- Every frame, clients send commands, not states.
- Server distributes the authoritative command list.
- Every machine simulates the same world → perfect sync.
3.1 Tick Structure
-- lockstep tick:
function tick()
apply_commands(current_frame_commands)
simulate_world(FRAME_TIME)
end
3.2 Command Package
{
frame = 120,
player_id = 1,
command = {type="move_to", x=200, y=160}
}
Server collects all commands per frame:
pending_commands[frame][player_id] = command
When all players submit commands → advance tick.
3.3 Determinism Rules
To keep all clients synced:
- NO floating-point randomness (use seeded RNG)
- Use integer or fixed-point math
- No iteration over unordered tables
- No using
pairs()for critical loops (use indexed arrays) - Avoid OS time
- No
math.random()unless seeded identically
Lua is deterministic if you follow strict rules.
4. Snapshot Interpolation (Action Games)
Used by:
- Overwatch
- Fortnite
- Call of Duty
- Apex Legends
- Most realtime games with large movement
Server sends world snapshots at N Hz:
20 snapshots/sec
60 Hz client interpolation
4.1 Snapshot Example
snapshot = {
time = 1234.56,
entities = {
{id=1, x=200, y=150, vx=10, vy=0, hp=80},
{id=2, x=450, y=300, vx=0, vy=0, hp=100},
}
}
Serialized and sent by UDP (ENet, RakNet, custom socket).
4.2 Client Interpolation Buffer
Clients keep last N snapshots:
buffers[id] = {
{time=t0, x=..., y=...},
{time=t1, x=..., y=...},
}
4.3 Interpolation
function interpolate(a, b, ratio)
return a + (b - a) * ratio
end
entity.x = interpolate(s0.x, s1.x, t)
entity.y = interpolate(s0.y, s1.y, t)
Smooth gameplay even with 100ms ping.
5. Client Prediction & Server Reconciliation
Used by fast games:
- shooters
- beat-em-ups
- platformers
- physics-based games
Principle:
- Client predicts movement immediately.
- Sends inputs → server simulates authoritative version.
- Server sends back correct state.
- Client rewinds to server state, replays unacknowledged inputs.
5.1 Client Prediction Code
function client_update(dt)
apply_input(player, input, dt)
record_input(input)
send_to_server(input)
end
5.2 Server Response
server_state = {
tick = 1052,
player_state = {x=100, y=200}
}
5.3 Reconciliation
-- rewind to server state
player.x = server_state.x
player.y = server_state.y
-- replay unacknowledged inputs
for _, inp in ipairs(unconfirmed_inputs) do
apply_input(player, inp, dt)
end
This is how Apex, Rocket League, and CS:GO work.
6. Entity Replication (Server → Client)
For each entity:
- id
- position
- velocity
- state (jumping, shooting, reloading)
- animation
- health
Server sends minimal changes via delta compression.
6.1 Replication Structure
replicate = {
id=12,
pos={x=100,y=150},
vel={x=0,y=0},
state="run",
hp=90,
}
Clients update their local entity copies.
7. Lag Compensation (Server Rewind)
Used in hit-scan shooters:
- server rewinds to historical position
- checks if shot hit in the past
Supports fair combat with latency.
7.1 Server Stores N Frames of History
history[tick][entity_id] = {x,y}
7.2 Hit-Scan Check
function server_hit_scan(client_time, origin, dir)
local snapshot = history[client_time]
check_line_intersection(snapshot, origin, dir)
end
8. Projectile Sync (RTS, RPG, Shooter)
Two models:
- Server-simulated projectiles (bullet has physics)
- Client-only view projectile (visual only, hit registered by server)
For fast shooters, bullets are not real objects — only hit-scan.
For RPGs and SLGs, projectiles are server-side entities:
projectile = {
id=pid,
x,y,vx,vy,
owner=player_id,
}
9. Combat Sync
Combat actions:
- melee
- ranged attack
- cast ability
- spell effects
- area damage
- buffs/debuffs
Server must verify:
if not is_in_range(attacker, target) then return end
if target.hp <= 0 then return end
10. ANTI-CHEAT Essentials
Do NOT trust:
- client HP
- client inventory
- position changes without inputs
- attack commands without cooldowns
- client damage numbers
Server must check:
- physics
- movement constraints
- cooldowns
- hit validity
- item ownership
- range/distance checks
11. Lobby, Matchmaking & Rooms
Room server structure:
rooms = {
[room_id] = {
players = {},
world = {},
tick = 0,
}
}
Server updates room:
function room_tick(room, dt)
update_world(room.world, dt)
send_snapshots(room.players)
end
12. Putting It All Together — Full Multiplayer Loop
Client Loop
function client:update(dt)
collect_input()
predict_position(dt)
send_input_to_server()
process_server_snapshots()
reconcile()
interpolate_other_players(dt)
end
Server Loop
function server:update(dt)
receive_inputs()
simulate_world(dt)
send_snapshots()
update_history()
end
13. Summary of Chapter 16
You now understand:
- Lockstep deterministic simulation
- Snapshot interpolation
- Client prediction & reconciliation
- Delta-based replication
- Lag compensation
- Projectile and combat sync
- Anti-cheat fundamentals
- Server architecture for rooms/lobbies
- Full client and server update loops
This is AAA-level netcode, implemented in Lua.