hermeticormus/claude-code-game-development/multiplayer-networking

Claude Code and agent skill by hermeticormus/claude-code-game-development

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Netcode reference patterns: a model comparison, a common mistakes catalog (rubber-banding, desync, bandwidth), delta encoding and quantization, reconciliation smoothing, a Unity library comparison, NAT traversal, and Godot MultiplayerSynchronizer, prediction, interpolation, and ENet setup. Use when writing or debugging networked game code.

Install

npx vskill@latest install hermeticormus/claude-code-game-development/multiplayer-networking

SKILL.md

Multiplayer networking pattern library

Reference patterns for game multiplayer netcode.

Netcode model comparison

ModelBest forLatency toleranceBandwidthDeterminism required
RollbackFighting gamesVery tightLowYes
LockstepRTS, lockstep RTSTolerantVery lowYes
Client prediction + reconciliationFPS, MOBA, actionMediumMediumNo
Server-authoritative + interpolationRacing, MMO movementTolerantMedium-highNo
Pure state snapshotsSlow-paced gamesVery tolerantHighNo

Common mistakes catalog

"Rubber-banding when player runs forward"

The player's client predicts forward motion. Server agrees and confirms. No rubber-band. Then player turns; client predicts turn; server says "no, you didn't turn yet" because the turn input hadn't reached the server when the server's last tick fired. Server position is one tick behind; client snaps back.

Fix: smooth reconciliation. When server position differs from predicted, interpolate over 100-200ms rather than snapping.

"I shot them dead-on and missed"

Favor-the-target policy. Server's "current" state is what counts; if your shot took 50ms to reach the server, the target may have moved. Either:

  • Switch to favor-the-shooter (lag compensation)
  • Use projectile weapons (the projectile travels in server time, no compensation needed)
"I was shot around a corner"

Favor-the-shooter policy working as intended. The shooter saw you at their lag-compensated time; you've since moved behind cover. You're dead on the server before you visually see the threat.

Mitigation: limit lag compensation window (e.g., 100ms max, not 250ms).

"Multiplayer works in lab, breaks on mobile data"

Mobile cellular has higher jitter and occasional packet loss. Check:

  • Tick rate too high for cellular bandwidth budget
  • No packet loss tolerance (UDP without reliability layer)
  • No connection migration on IP change (mobile data → WiFi)
  • Power-save modes throttling background packets
"Desync after 30 seconds in lockstep RTS"

Nondeterminism. Audit:

  • Float math anywhere in game state → switch to fixed-point
  • RNG → seed at game start, use same RNG instance for all clients
  • Iteration order over collections → use ordered collections (List, sorted Dictionary)
  • Time-based logic → use tick count, not wall clock
  • Third-party libraries → check determinism guarantees
"Bandwidth balloons at high player counts"

Need interest management:

  • Don't replicate everyone to everyone
  • Each client subscribes to entities within its area of interest (visible + nearby)
  • Cell-based partitioning or proximity-based subscription
"Rollback budget exceeded constantly"

Either:

  • RTT too high for the chosen input delay; increase input delay
  • State serialization is too slow; profile + optimize
  • Game state contains nondeterministic elements making rollback impossible
"Players desync on resume from sleep / app backgrounding"

Game logic kept advancing while suspended (timer-based), or stopped while suspended (frame-based). Either way, syncing the resumed client requires state download from server.

Pattern: on resume, request full state from server before resuming play.

Bandwidth optimization patterns

Delta encoding
Frame N state: position = (100, 50, 200), rotation = 45deg, hp = 80
Frame N+1 state: position = (101, 50, 200), rotation = 45deg, hp = 80
Naive send: position + rotation + hp = ~16 bytes
Delta send: position.x changed = 4-byte field + 4-byte value = 8 bytes
Savings: ~50%. Compounds with quantization.
Quantization
  • Position: from 32-bit float to 16-bit fixed-point (sub-mm precision over 1km range): 50% size reduction
  • Rotation: from quaternion to compressed quaternion (smallest 3 + sign bit): 4 bytes → ~1.4 bytes
  • Velocity: 8-bit per axis when bounded: 32 bytes → 3 bytes
  • Time stamps: relative to a known epoch, 16-bit fits ~64 seconds at 1ms precision
Bit packing
Boolean state flags: is_jumping, is_attacking, is_grounded, is_alive
Naive: 4 bytes (one per bool)
Packed: 1 byte (4 bits used, 4 bits header)
State enum (8 states): 1 byte → 3 bits
Priority queues

When bandwidth budget is tight:

Priorities:
Critical (must arrive every tick): player position, HP
High (every 2-3 ticks): held weapon, ammo
Medium (every 5-10 ticks): outfit, secondary stats
Low (every ~100 ticks): emote, name, distant NPCs
Bandwidth-constrained: send only critical + as much high/medium as fits in budget

Reconciliation smoothing

csharp
private const float SMOOTHING_TIME = 0.15f; // 150ms
private Vector3 _renderOffset = Vector3.zero;
private void OnReconcile(Vector3 serverPosition)
{
// Don't snap to serverPosition. Save the offset.
_renderOffset = transform.position - serverPosition;
transform.position = serverPosition;
}
private void LateUpdate()
{
// Decay the offset over SMOOTHING_TIME
_renderOffset = Vector3.Lerp(_renderOffset, Vector3.zero, Time.deltaTime / SMOOTHING_TIME);
transform.position += _renderOffset; // Visual position uses offset; logical position is correct
}

Library comparison (Unity)

LibraryTypeMaturityRecommendation
Netcode for GameObjects (NGO)First-party from UnityActiveDefault for new projects
MirrorOpen source, community-maintainedMatureWhen NGO doesn't fit your model
FishNetOpen source, performance focusMatureWhen you need raw performance
Photon FusionCommercial, hostedMatureWhen you want hosted matchmaking + rooms
NakamaOpen source backend, multi-engineMatureWhen you want generic multiplayer backend
PlayFabMicrosoft commercialMatureEnterprise / live ops focus
EdgegapDedicated server hostingMatureWhen you need dedicated server scaling

For Godot: built-in MultiplayerAPI is solid for small projects. Nakama for larger projects + backend services.

For Unreal: built-in networking + replication graph is industry-standard.

NAT traversal reference

Connection typeStrategy
Both peers same LANDirect connection
One peer has public IPDirect to public peer
Both peers behind NAT (cone or restricted-cone)STUN
Both peers behind symmetric NATTURN (relay through server)
ICE handles selection between STUN + TURN

Steam's P2P API + Photon Cloud + Nakama all handle this for you. If self-hosting, integrate coturn (open source TURN server) or use Cloudflare's tier.

Godot MultiplayerSynchronizer Setup

gdscript
# Authoritative multiplayer character with MultiplayerSynchronizer
class_name NetworkedPlayer extends CharacterBody3D
@export var player_id: int = 0
# MultiplayerSynchronizer synchronizes these properties
# Configure in the Inspector on the MultiplayerSynchronizer node:
# position: unreliable, always (smooth movement)
# velocity: unreliable, always
# health: reliable, on_change (critical state)
# current_animation: reliable, on_change
@onready var sync: MultiplayerSynchronizer = $MultiplayerSynchronizer
func _ready() -> void:
# Only process input for our own character
set_physics_process(is_multiplayer_authority())
func _physics_process(delta: float) -> void:
# Only runs on authority (local player or server)
var input_dir := Input.get_vector(&"move_left", &"move_right", &"move_forward", &"move_back")
velocity.x = input_dir.x * 6.0
velocity.z = input_dir.y * 6.0
if not is_on_floor():
velocity.y -= 9.8 * delta
move_and_slide()
# MultiplayerSynchronizer broadcasts position/velocity to all peers automatically
# RPC call: client requests action, server validates and executes
@rpc("any_peer", "call_local", "reliable")
func request_attack(target_id: int) -> void:
if not is_multiplayer_authority():
return # Only server processes this
var target := get_node_or_null("/root/Game/Players/%d" % target_id)
if target and _is_valid_target(target):
_apply_damage.rpc(target_id, 10.0)
@rpc("authority", "call_local", "reliable")
func _apply_damage(target_id: int, amount: float) -> void:
# Called on all clients from server authority
if multiplayer.get_unique_id() == target_id:
# Apply to self
health -= amount

Client-Side Prediction with Reconciliation

gdscript
class_name PredictedPlayer extends CharacterBody3D
const MAX_PREDICTION_TICKS: int = 60 # 1 second buffer at 60Hz
# Input state snapshot for rollback
class InputSnapshot:
var tick: int
var input_vector: Vector2
var jump_pressed: bool
# State snapshot for reconciliation
class StateSnapshot:
var tick: int
var position: Vector3
var velocity: Vector3
var _pending_inputs: Array[InputSnapshot] = []
var _predicted_states: Array[StateSnapshot] = []
var _last_confirmed_tick: int = 0
func _physics_process(delta: float) -> void:
var input := InputSnapshot.new()
input.tick = multiplayer.get_remote_sender_id() # Use tick counter
input.input_vector = Input.get_vector(&"move_left", &"move_right", &"move_forward", &"move_back")
input.jump_pressed = Input.is_action_just_pressed(&"jump")
# Apply locally (prediction)
_apply_input(input, delta)
# Send to server
_send_input_to_server.rpc_id(1, input.tick, input.input_vector, input.jump_pressed)
# Store for reconciliation
var state := StateSnapshot.new()
state.tick = input.tick
state.position = global_position
state.velocity = velocity
_predicted_states.append(state)
_pending_inputs.append(input)
# Trim old predictions
while _predicted_states.size() > MAX_PREDICTION_TICKS:
_predicted_states.pop_front()
_pending_inputs.pop_front()
@rpc("authority", "call_local", "reliable")
func _receive_server_correction(confirmed_tick: int, server_position: Vector3, server_velocity: Vector3) -> void:
# Find matching predicted state
var mismatch_threshold := 0.1 # meters
var predicted_state: StateSnapshot = null
for state in _predicted_states:
if state.tick == confirmed_tick:
predicted_state = state
break
if not predicted_state:
return
if predicted_state.position.distance_to(server_position) > mismatch_threshold:
# Reconciliation: rollback and re-simulate from confirmed state
global_position = server_position
velocity = server_velocity
# Re-apply all unconfirmed inputs
for input in _pending_inputs:
if input.tick > confirmed_tick:
_apply_input(input, 1.0 / 60.0)
# Remove confirmed inputs
_pending_inputs = _pending_inputs.filter(func(i): return i.tick > confirmed_tick)
_predicted_states = _predicted_states.filter(func(s): return s.tick > confirmed_tick)
func _apply_input(input: InputSnapshot, delta: float) -> void:
var direction := Vector3(input.input_vector.x, 0, input.input_vector.y).normalized()
velocity.x = direction.x * 6.0
velocity.z = direction.z * 6.0
if not is_on_floor():
velocity.y -= 9.8 * delta
move_and_slide()

Entity Interpolation for Remote Players

gdscript
# Smooth remote player rendering with interpolation buffer
class_name InterpolatedRemotePlayer extends Node3D
const INTERPOLATION_DELAY: float = 0.1 # 100ms behind server time
class StateRecord:
var timestamp: float
var position: Vector3
var rotation: Quaternion
var _state_buffer: Array[StateRecord] = []
func receive_state(position: Vector3, rotation: Quaternion) -> void:
var record := StateRecord.new()
record.timestamp = Time.get_unix_time_from_system()
record.position = position
record.rotation = rotation
_state_buffer.append(record)
# Keep buffer bounded
if _state_buffer.size() > 20:
_state_buffer.pop_front()

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
65
Repository forks
11
7-day trend score
26

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/hermeticormus/claude-code-game-development/multiplayer-networking/badge)](https://verified-skill.com/skills/hermeticormus/claude-code-game-development/multiplayer-networking)

Something wrong with this skill or its page? Tell us.