The server runs the game as a fixed-rate simulation: gather inputs, advance the world one tick, broadcast the new state. Learn the tick rate, the fixed timestep, and the input→simulate→send cycle.
An authoritative server advances the world in discrete steps called ticks, at a fixed tick rate (say 30 or 64 per second). Each tick it applies the inputs received since the last tick, steps the simulation by a fixed delta, and sends the new state to clients. This fixed-rate loop — the server’s heartbeat — is the same fixed-timestep idea from game physics, and everything else in netcode is timed relative to it.
import time
TICK_RATE = 30 # ticks per second
DT = 1.0 / TICK_RATE # fixed timestep
def server_loop(world):
next_tick = time.monotonic()
while True:
inputs = collect_inputs_since_last_tick() # from all clients
for player_id, cmd in inputs.items():
world.apply_input(player_id, cmd, DT) # simulate with FIXED dt
world.step_physics(DT)
broadcast_state(world.snapshot()) # send truth to clients
next_tick += DT # keep a steady rate
sleep = next_tick - time.monotonic()
if sleep > 0: time.sleep(sleep)
# fixed-rate ticks = the server's heartbeat. Everything else is timed to it.Tick rate is a core tuning knob. A higher rate (64, 128 Hz) means the server simulates and updates more often — crisper hit detection and lower input-to-effect delay — but costs more CPU and bandwidth per player, limiting how many fit on a server. A lower rate is cheaper but feels less responsive. Competitive shooters push tick rate high; games with many entities or tight budgets go lower.
Tick rate = how many times/second the server simulates + can update clients.
higher (64 / 128 Hz) crisper hit detection, lower input delay
BUT more CPU + bandwidth per player -> fewer players/server
lower (10 / 20 Hz) cheap, more players/server, but feels laggier/imprecise
input-to-server delay ~ up to 1 tick; update freshness ~ 1 tick.
Competitive shooters: 64-128 Hz. MMOs / many entities: 10-30 Hz (+ interpolation
hides it). Note: SEND rate to clients can be lower than SIM rate to save bandwidth
(simulate at 60, snapshot at 20).Clients send input commands (move forward, look here, fire) tagged with the client’s tick number, and the server queues and applies them in order on the matching tick. Crucially, the server validates every input — clamping movement to legal speeds, checking cooldowns, rejecting impossible actions — before it affects the simulation. This input-command model plus validation is the practical form of "never trust the client."
class InputCommand:
def __init__(self, tick, move, aim, buttons):
self.tick, self.move, self.aim, self.buttons = tick, move, aim, buttons
def apply_input(world, player, cmd, dt):
# VALIDATE before simulating — never trust the client's numbers
move = clamp_length(cmd.move, 1.0) # can't request >1.0 magnitude
speed = player.max_speed # server owns the speed, not the client
player.pos += move * speed * dt # server computes the position
if cmd.buttons.fire and world.now - player.last_shot >= player.cooldown:
world.spawn_projectile(player) # respect the server's cooldown
player.last_shot = world.now
# clients send INPUTS (tagged with their tick); the server validates + simulates.
# reject/clamp anything illegal -> a hacked client gains nothing.To save bandwidth, the server often simulates faster than it broadcasts — it might tick physics at 60 Hz but send state snapshots at 20 Hz. Clients hide the gap with interpolation (a later lesson). Separating the simulation rate from the snapshot rate lets you keep a smooth, accurate simulation while controlling the biggest cost, bandwidth, which is essential for fitting many players on a server.
Two independent rates:
SIMULATION rate how often the server steps the world (e.g. 60 Hz)
SNAPSHOT rate how often it SENDS state to clients (e.g. 20 Hz)
simulate at 60 for accuracy, but send only 20 snapshots/sec -> ~3x less
bandwidth. clients INTERPOLATE between the snapshots for smooth visuals
(next lessons).
Bandwidth is usually the limiting cost for player count, so lowering the SEND
rate (not the sim rate) is a key scaling lever. Send important events reliably;
send world state as periodic snapshots.