Claude-skill-registry dev-multiplayer-prediction-basics

Client-side prediction and server reconciliation core concepts. Use when implementing responsive multiplayer controls.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dev-multiplayer-prediction-basics" ~/.claude/skills/majiayu000-claude-skill-registry-dev-multiplayer-prediction-basics && rm -rf "$T"
manifest: skills/data/dev-multiplayer-prediction-basics/SKILL.md
source content

Client-Side Prediction Basics

Client prediction makes multiplayer feel responsive. Server reconciliation keeps it honest.

When to Use

Use for EVERY server-authoritative gameplay feature that needs responsive feel:

  • Movement (WASD, jump, sprint)
  • Shooting (aim, fire, ammo)
  • Interactions (vault, mantle, crouch)

Architecture Flow

INPUT
  │
  ├───► LOCAL PREDICTION (immediate visual feedback)
  │      │
  │      └───► Display updates instantly (feels responsive)
  │
  └───► SEND TO SERVER (validation)
         │
         │    NETWORK LATENCY (~100ms)
         │
         ▼
      SERVER PROCESSES
         │
         └───► SERVER STATE (authoritative)
                │
                │    NETWORK LATENCY
                │
                ▼
           CLIENT RECONCILES
                │
                ├───► Discard confirmed inputs
                ├───► Re-apply pending inputs
                └───► Smooth correction

Result: Responsive feel + cheat prevention

Key Concepts

Local Prediction

  • Apply input immediately on client
  • Show result to player instantly
  • No perceived lag

Server Validation

  • Send input to server
  • Server processes authoritatively
  • Validates rules, prevents cheating

Reconciliation

  • Server sends authoritative state back
  • Client removes processed inputs from pending
  • Re-applies remaining pending inputs
  • Smoothly interpolates to reconciled position

Input Message Pattern

interface InputMessage {
  type: 'player_input';
  input: {
    forward: boolean;
    backward: boolean;
    left: boolean;
    right: boolean;
    jump: boolean;
  };
  sequence: number;  // Incrementing counter for matching
}

Sequence Numbers

Critical for reconciliation:

  • Client increments counter for each input
  • Server echoes sequence in state updates
  • Client uses sequence to match server responses

Reconciliation Flow

// Server sends authoritative state
interface ServerState {
  position: { x: number; y: number; z: number };
  lastProcessedSequence: number;  // Key for reconciliation
}

// Client reconciles
function reconcile(serverState: ServerState) {
  // 1. Remove inputs server has processed
  pendingInputs = pendingInputs.filter(
    p => p.sequence > serverState.lastProcessedSequence
  );

  // 2. Start from server position (authoritative)
  let position = { ...serverState.position };

  // 3. Re-apply all pending inputs
  for (const input of pendingInputs) {
    position = applyInput(position, input.input, 0.016);
  }

  // 4. Smoothly interpolate display
  displayPosition = lerp(displayPosition, position, 0.2);
}

Config Synchronization

CRITICAL: Config values MUST match exactly between client and server.

Wrong Way

// ❌ Config defined separately
// Client: const MOVEMENT_SPEED = 10;
// Server: const MOVEMENT_SPEED = 10;  // Can drift!

Right Way - Shared Module

// ✅ shared/config/MovementConfig.ts
export const MOVEMENT_CONFIG = {
  walkSpeed: 10,
  sprintSpeed: 16,
  jumpForce: 8,
  gravity: 20,
} as const;

// Both client and server import from same file

Right Way - Server-Driven

// ✅ Server sends config on connect
onJoin(client: Client) {
  client.send({
    type: 'config_sync',
    movement: MOVEMENT_CONFIG,
  });
}

Testing Checklist

  • Input feels immediate (no perceived lag)
  • Server rejection causes rollback
  • Rollback is smooth (not jarring)
  • Reconciliation completes within 200ms
  • No rubber-banding under normal latency
  • High latency (200ms+) still playable

Common Mistakes

❌ Wrong✅ Right
No prediction, send input onlyPredict locally, then send
No reconciliation, just snapSmooth interpolation
Apply server state directlyRe-apply pending inputs first
Separate client/server configShared config module
Hardcoded values everywhereSingle source of truth

Performance Notes

  • Limit pending inputs to ~100 entries
  • Clean up old inputs (> 1 second)
  • Use object pooling for input objects
  • Batch reconciliation updates (not every frame)

Reference