Lesson 7: Lag, Prediction, and Reconciliation Primer (Server-Authoritative Feel)

If your multiplayer slice only feels good on localhost, players will call it broken even when replication is technically correct.

This lesson gives you a small, practical pattern: the client predicts immediate movement feedback, the server remains authoritative, and periodic reconciliation corrects drift without visible snapping.

Lesson objective

By the end of this lesson, you will have:

  1. A lightweight prediction loop for local responsiveness
  2. A server-state snapshot path for authoritative correction
  3. Reconciliation thresholds that avoid over-correction jitter
  4. A repeatable latency test routine for 50ms, 120ms, and 200ms

Why this matters

Players judge control quality in the first 30 seconds. If every action waits on round-trip latency, your game feels unresponsive. If you trust clients too much, you create exploit surface.

Prediction plus reconciliation is the middle path: responsive feel plus server truth.

Core model in one sentence

Client predicts now, server validates soon, client reconciles gently.

Use that sentence as your review checklist for every movement or combat action in this vertical slice.

Step-by-step implementation pass

Step 1 - Separate input sampling from authoritative simulation

Keep clear roles:

  • Client samples local input each tick
  • Client performs short-horizon visual prediction
  • Server runs authoritative simulation and emits canonical state

Do not mix those responsibilities into one monolithic script.

Step 2 - Tag inputs with sequence numbers

Each client input packet should include:

  1. inputSequence
  2. Tick or timestamp
  3. Compressed intent values (move axis, jump, action)

Sequence IDs let you compare "what client predicted" versus "what server accepted."

Step 3 - Replay unacknowledged inputs after correction

When server state arrives:

  • Move client state toward authoritative state
  • Reapply unacknowledged inputs after that baseline
  • Keep correction smoothing bounded to avoid rubber-banding

This is the heart of reliable reconciliation.

Step 4 - Add correction thresholds

Use two thresholds:

  • Minor error threshold for smooth interpolation
  • Major error threshold for immediate snap-to-authority

Without thresholds, you either jitter constantly or hide large divergence too long.

Step 5 - Run a latency matrix test

Test at least:

  1. 50ms stable latency
  2. 120ms stable latency
  3. 200ms with occasional jitter spikes

Capture one short clip per case and note:

  • Input feel rating
  • Visible correction frequency
  • Largest observed positional error

Example pattern - prediction buffer with server reconciliation

using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

public class PredictedMover : NetworkBehaviour
{
    private struct InputFrame
    {
        public int sequence;
        public Vector2 move;
    }

    private readonly Queue<InputFrame> _pendingInputs = new();
    private int _nextSequence;
    private Vector3 _predictedPosition;
    private const float MinorErrorThreshold = 0.15f;
    private const float MajorErrorThreshold = 1.0f;

    private void Update()
    {
        if (!IsOwner) return;

        var input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        var frame = new InputFrame { sequence = _nextSequence++, move = input };
        _pendingInputs.Enqueue(frame);

        // Local prediction for responsiveness.
        _predictedPosition += new Vector3(input.x, 0f, input.y) * Time.deltaTime * 5f;
        transform.position = _predictedPosition;

        SubmitInputServerRpc(frame.sequence, input);
    }

    [ServerRpc]
    private void SubmitInputServerRpc(int sequence, Vector2 move, ServerRpcParams rpcParams = default)
    {
        // Server applies authoritative movement and sends snapshot back.
    }

    [ClientRpc]
    public void ReceiveAuthoritativeStateClientRpc(int ackSequence, Vector3 serverPosition)
    {
        while (_pendingInputs.Count > 0 && _pendingInputs.Peek().sequence <= ackSequence)
        {
            _pendingInputs.Dequeue();
        }

        var error = Vector3.Distance(_predictedPosition, serverPosition);
        if (error > MajorErrorThreshold)
        {
            _predictedPosition = serverPosition;
        }
        else if (error > MinorErrorThreshold)
        {
            _predictedPosition = Vector3.Lerp(_predictedPosition, serverPosition, 0.5f);
        }
    }
}

Keep this as a primer implementation. You can evolve it later for full rollback or advanced hit validation.

Mini challenge

Create a lag-test-notes.md file for your project and log one pass for each latency tier (50/120/200ms).

For each tier, record:

  1. One thing that still feels responsive
  2. One correction artifact you observed
  3. One practical tweak you will test next sprint

Pro tips

  • Use a fixed networking tick and keep simulation deterministic where possible.
  • Reconcile position and velocity together for cleaner motion.
  • Debug with on-screen sequence IDs so drift analysis is visible during playtests.

Common mistakes

  • Predicting gameplay outcomes the server never validates
  • Applying reconciliation every frame without thresholds
  • Ignoring packet jitter and only testing fixed latency

Troubleshooting

"Movement feels sticky at 120ms even with prediction."

Check that local visual prediction runs immediately from input, not after waiting for outgoing RPC confirmation.

"Players snap backward too often."

Your minor threshold may be too low, or server snapshots may be too infrequent for your movement speed.

"Desync grows during long strafes."

Verify sequence acknowledgment and replay logic. Missing or out-of-order input cleanup often causes accumulating drift.

FAQ

Should I predict everything or only movement?

For a vertical slice, start with movement and low-risk interactions. Predicting every system too early can hide authority bugs.

Is reconciliation the same as rollback netcode?

No. This lesson uses forward prediction plus correction. Full rollback requires deterministic re-simulation of prior states.

Do I need dedicated servers before this works?

No. The same pattern works in host-authoritative tests. Dedicated hosting changes topology, not the core prediction concept.

Recap

You now have a practical latency baseline: local prediction for feel, server authority for trust, and reconciliation for alignment.

Next lesson teaser

Lesson 8 moves from simulation feel to player flow: lobby and join code UX, so players can actually find and start sessions reliably.

Related links

Save your latency notes. They become your baseline evidence before adding more complex combat networking.