---
id: game-networking-multiplayer
title: Multiplayer Networking — Authoritative / Prediction / Lag
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [game, networking, multiplayer, vibe-coding]
tech_stack: { language: "TS / Various", applicable_to: ["Game"] }
applied_in: []
aliases: [authoritative server, client prediction, lag compensation, lockstep, rollback netcode]
---

# Multiplayer Networking

> Real-time game = network latency 의 적. **Authoritative server + client prediction + reconciliation**. Fighting / RTS = lockstep / rollback.

## 📖 핵심 개념
- Authoritative server: 진짜 state. cheat 방어.
- Client prediction: latency 숨김 — local 즉시 적용.
- Reconciliation: 서버 답 도착 → 차이 보정.
- Interpolation: 다른 player 의 위치 부드럽게.

## 💻 코드 패턴

### 기본 architecture
```
[Player A]  [Player B]  [Player C]
     │           │           │
     └───────────┼───────────┘
                 ▼
        [Authoritative Server]
                 │
                 ▼
            [Game State]
```

### Tick rate
```
Server tick:  20-60 Hz (FPS 보통 60 Hz, RTS 10 Hz)
Client send:  60 Hz input
Snapshot to client: 20-30 Hz (compressed)
```

### Server tick loop
```ts
const TICK_MS = 1000 / 60;
let last = Date.now();
let acc = 0;

setInterval(() => {
  const now = Date.now();
  acc += now - last;
  last = now;
  
  while (acc >= TICK_MS) {
    processInputs();
    updatePhysics(TICK_MS / 1000);
    acc -= TICK_MS;
  }
  
  if (now - lastSnapshot > 50) {  // 20 Hz snapshot
    sendSnapshotsToAll();
    lastSnapshot = now;
  }
}, 1);
```

### Client prediction
```ts
let serverState: GameState | null = null;
let predicted: GameState = initialState;
let pendingInputs: { seq: number; input: Input }[] = [];
let inputSeq = 0;

function frame(dt: number) {
  const input = readInput();
  inputSeq++;
  pendingInputs.push({ seq: inputSeq, input });
  
  ws.send(JSON.stringify({ type: 'input', seq: inputSeq, input }));
  
  predicted = applyInput(predicted, input, dt);
  render(predicted);
}

ws.onmessage = (msg) => {
  const data = JSON.parse(msg.data);
  if (data.type === 'snapshot') {
    serverState = data.state;
    predicted = data.state;
    
    // 서버가 이미 처리한 input 제거
    pendingInputs = pendingInputs.filter(p => p.seq > data.lastInputSeq);
    
    // 남은 input 들 다시 적용 (reconciliation)
    for (const p of pendingInputs) {
      predicted = applyInput(predicted, p.input, TICK_DT);
    }
  }
};
```

### 다른 player — interpolation
```ts
// 서버 snapshot N 시점 + N+1 시점
// 100ms 늦게 render → 부드러운 보간

const renderTime = Date.now() - 100;  // 100ms behind

function getInterpolated(playerId: string): Vec2 {
  const buffer = playerStates.get(playerId)!;
  const newer = buffer.find(s => s.t >= renderTime);
  const older = buffer.findLast(s => s.t <= renderTime);
  if (!newer || !older) return latest;
  
  const t = (renderTime - older.t) / (newer.t - older.t);
  return {
    x: lerp(older.pos.x, newer.pos.x, t),
    y: lerp(older.pos.y, newer.pos.y, t),
  };
}
```

### Lag compensation (FPS — server 시점 ↔ client 시점)
```
Player A 가 위치 X 보고 사격.
Server 가 받았을 때 A 의 위치 + B 의 위치는 다름.
Server 가 100ms 전 state 로 hit 검사 — fair.
```

```ts
function processShot(shooter: Player, ping: number) {
  const rewindTime = Date.now() - ping;
  const enemyState = getHistoricalState(rewindTime);
  for (const e of enemyState) {
    if (rayHit(shooter.aim, e.pos)) e.hp -= 50;
  }
}
```

### Lockstep (RTS, fighting)
```
모든 client 가 같은 tick 의 input 모두 받기 전 진행 X.
완전 deterministic — 같은 input + 같은 state = 같은 결과.

장점: 작은 트래픽, cheat 어려움
단점: 가장 느린 player 가 모두 늦춤
```

```ts
// 30 Hz 마다 input 보냄 + 모두 도착 후 진행
const turnInputs = new Map<number, Map<string, Input>>();

ws.onmessage = (msg) => {
  const { turn, playerId, input } = JSON.parse(msg.data);
  if (!turnInputs.has(turn)) turnInputs.set(turn, new Map());
  turnInputs.get(turn)!.set(playerId, input);
};

function tryAdvance() {
  const inputs = turnInputs.get(currentTurn);
  if (inputs?.size === playerCount) {
    advanceSimulation(inputs);
    currentTurn++;
  }
}
```

### Rollback netcode (fighting)
```
1. Predict opponent input (last input 반복)
2. Local input 즉시 적용
3. 진짜 opponent input 받으면 → state rollback + replay
4. 사용자 = lag 거의 안 느낌

GGPO / Steam Networking / Rollback library.
```

```ts
const stateHistory: GameState[] = [];

function frame(localInput: Input, predictedRemote: Input) {
  const state = applyInputs(stateHistory.at(-1)!, localInput, predictedRemote);
  stateHistory.push(state);
  render(state);
}

// 진짜 remote input 도착
ws.onmessage = (msg) => {
  const { frame: remoteFrame, input } = JSON.parse(msg.data);
  
  if (predictedInputs[remoteFrame] !== input) {
    // Rollback + replay
    const correctState = applyInputs(stateHistory[remoteFrame - 1], localInputs[remoteFrame], input);
    stateHistory[remoteFrame] = correctState;
    
    for (let f = remoteFrame + 1; f <= currentFrame; f++) {
      stateHistory[f] = applyInputs(stateHistory[f - 1], localInputs[f], predictedRemote);
    }
  }
};
```

### Snapshot compression
```ts
// Delta encoding — 변경 만 보냄
function makeDelta(prev: State, cur: State): Delta {
  return {
    players: cur.players.map((p, i) => ({
      id: p.id,
      pos: !equal(p.pos, prev.players[i].pos) ? p.pos : undefined,
      hp: p.hp !== prev.players[i].hp ? p.hp : undefined,
    })),
  };
}

// Quantization — float → small int
const x16 = Math.floor((x + 1000) * 100);  // 16-bit
```

### Reliable vs unreliable
```
Reliable (TCP / WebSocket): 순서 보장, 늦음
Unreliable (UDP / WebRTC DataChannel): 빠름, drop OK

게임:
- Position update: unreliable (next snapshot 가 덮음)
- Chat / inventory: reliable
- Hit / kill events: reliable (또는 reliable+ack)
```

### WebRTC DataChannel
```ts
const pc = new RTCPeerConnection(...);
const dc = pc.createDataChannel('game', { ordered: false, maxRetransmits: 0 });
dc.send(buffer);  // unreliable, low latency
```

→ 위 WebRTC 문서.

### Server location
```
글로벌 game = 사용자 가까이 server.
- Region 별 server pool
- Match 시작 시 가장 가까운 region 선택
- Cross-region = 큰 ping
```

### Anti-cheat
```
- Server authoritative — client 의 state 절대 신뢰 X
- Movement validation (max speed)
- Damage calculation server-side
- Replay analysis
- Heuristic detection (aimbot 같은 패턴)
- Hardware fingerprint (Easy Anti-Cheat 등)
```

### Tools / framework
```
Colyseus:        Node, room-based MMO
Mirror:          Unity
Photon:          Unity / Unreal
Nakama:          Cross-platform
Hathora:         Managed game server
agones / Open Match: Kubernetes game server
```

```ts
// Colyseus
import { Server } from 'colyseus';
import { Room, Client } from 'colyseus';

class GameRoom extends Room {
  onCreate() {
    this.setState(new GameState());
    this.setSimulationInterval(dt => this.update(dt), 1000 / 30);
  }
  
  onMessage(client: Client, msg: any) { ... }
}

new Server({ port: 2567 }).define('game', GameRoom);
```

## 🤔 의사결정 기준
| Game type | 추천 |
|---|---|
| FPS / Action | Authoritative + prediction + lag comp |
| Fighting | Rollback netcode |
| RTS | Lockstep |
| MMO | Authoritative + interest management |
| Casual / async | REST API 충분 |
| Card game | TCP + tick low |

## ❌ 안티패턴
- **Client authoritative**: cheat 가능.
- **Prediction 없음 + 100ms ping**: 입력 lag.
- **Snapshot 매 frame full state**: bandwidth.
- **Reliable 모든 메시지**: latency 누적.
- **Rollback 무한 frame**: memory.
- **Server tick rate 낮음**: jitter.
- **Region 무시 prod**: 큰 ping.

## 🤖 LLM 활용 힌트
- Authoritative + client prediction + reconciliation 표준.
- Interpolation 100ms behind 부드럽게.
- Tick + snapshot rate 분리.
- Colyseus / Hathora 가 빠른 시작.

## 🔗 관련 문서
- [[Backend_WebSocket_Scaling]]
- [[Web_WebRTC_Realtime]]
- [[Game_Loop_ECS]]
