# How Unciv Multiplayer and Turn Synchronization Works

> Discover how Unciv multiplayer and turn synchronization uses a dual-storage architecture and throttled polling to keep game states aligned across devices. Learn about its efficient networking.

- Repository: [Yair Morgenstern/Unciv](https://github.com/yairm210/Unciv)
- Tags: internals
- Published: 2026-06-18

---

**Unciv implements multiplayer through a dual-storage architecture where the `Multiplayer` class orchestrates between local `MultiplayerFiles` and remote `MultiplayerServer`, using throttled polling loops and event-driven UI updates to keep game states synchronized across devices.**

Unciv is an open-source, turn-based strategy game inspired by Civilization V. Understanding **Unciv multiplayer and turn synchronization** requires examining how the game balances local state management with remote server interactions to ensure all players see consistent game states across different devices.

## Core Architecture: The Dual-Storage Model

Unciv’s multiplayer system is built around two complementary data stores coordinated by a central orchestrator.

### Local Storage with MultiplayerFiles

The **`MultiplayerFiles`** class ([`core/src/com/unciv/logic/multiplayer/MultiplayerFiles.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/multiplayer/MultiplayerFiles.kt)) manages the *offline* side of the system. It stores local copies of saved games, tracks game previews, and provides filesystem helpers for quick access without network latency.

### Remote Storage with MultiplayerServer

The **`MultiplayerServer`** class ([`core/src/com/unciv/logic/multiplayer/storage/MultiplayerServer.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/multiplayer/storage/MultiplayerServer.kt)) handles the *online* side, abstracting remote storage backends such as Dropbox ([`storage/DropBox.kt`](https://github.com/yairm210/Unciv/blob/main/storage/DropBox.kt)) or custom servers ([`storage/UncivServerFileStorage.kt`](https://github.com/yairm210/Unciv/blob/main/storage/UncivServerFileStorage.kt)). It manages authentication, upload/download operations for full game files, and lightweight preview synchronization.

### The Multiplayer Orchestrator

The **`Multiplayer`** class ([`core/src/com/unciv/logic/multiplayer/Multiplayer.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/multiplayer/Multiplayer.kt)) serves as the central coordinator. It maintains references to both local files and the remote server, providing the high-level API used by UI components and the game engine to create, load, and synchronize multiplayer games.

## Game Lifecycle Operations

### Creating Online Games

When starting a new multiplayer session, `Multiplayer.createGame(newGame)` uploads the full `GameInfo` object to the server, then immediately adds a local preview via `MultiplayerFiles.addGame`. This ensures the creator has immediate offline access while the server hosts the authoritative copy.

```kotlin
// In NewGameScreen.createGame()
if (gameSetupInfo.gameParameters.isOnlineMultiplayer) {
    // Verify server connection first
    if (!checkConnectionToMultiplayerServer()) return
    // Create the game locally and upload it
    game.onlineMultiplayer.createGame(newGame)   // ← Multiplayer.createGame
}

```

*Source*: [`NewGameScreen.kt`](https://github.com/yairm210/Unciv/blob/main/NewGameScreen.kt) – lines 161‑167

### Loading and Updating Games

When entering a game, `WorldScreen.loadLatestMultiplayerState` first attempts to use the local copy stored in `MultiplayerFiles`. If the local state is out-of-date (detected via preview comparison), the system calls `Multiplayer.downloadGame(gameId)` to fetch the latest server version.

### Resigning and Skipping Turns

Both player resignation and turn skipping follow a consistent pattern: download the latest server copy, modify the `GameInfo`, run AI automation if needed, then re-upload the updated state. The `Multiplayer.resignPlayer` and `Multiplayer.skipCurrentPlayerTurn` methods handle these flows.

```kotlin
suspend fun skipCurrentPlayerTurn(game: MultiplayerGamePreview,
                                 playerCivName: String,
                                 responsibleCivNameOrPlayerId: String): String? {
    // download latest version
    val gameInfo = multiplayerServer.tryDownloadGame(preview.gameId)
    // sanity checks …
    // run AI automation (if needed) and advance turn
    NextTurnAutomation.automateCivMoves(playerCiv, false)
    gameInfo.nextTurn()
    // upload updated state + preview
    multiplayerServer.uploadGame(gameInfo, withPreview = true)
    game.updatePreview(gameInfo.asPreview())
    return null
}

```

*Source*: [`Multiplayer.kt`](https://github.com/yairm210/Unciv/blob/main/Multiplayer.kt) – lines 94‑132

## Turn Synchronization Mechanism

When a player ends their turn, Unciv employs a multi-stage synchronization process to propagate state changes to all participants.

### The Update Request Flow

1. **UI Trigger**: The "Next Turn" button calls `WorldScreen.game.onlineMultiplayer.requestUpdate()` (see `WorldScreen` at line 199).
2. **Throttled Iteration**: `Multiplayer.requestUpdate` iterates over every locally-known game preview, applying rate-limiting via the `throttle` helper (lines 29‑44).
3. **Preview Comparison**: Each `MultiplayerGamePreview.requestUpdate` contacts the server via `MultiplayerServer.downloadGamePreview` and compares the remote preview with the local copy.

### Out-of-Date Detection

The `Multiplayer.hasLatestGameState` method (lines 99‑103) checks if the `currentPlayer` and `turns` fields match between local and remote previews. When differences are detected, the UI displays an "update available" indicator and automatically invokes `Multiplayer.downloadGame` to pull the full `GameInfo`.

### Background Polling Loop

The `Multiplayer` constructor launches a coroutine named `multiplayerGameUpdater` that runs indefinitely, polling the server every 500 milliseconds:

```kotlin
multiplayerGameUpdater = flow<Unit> {
    while (true) {
        delay(500)
        // ... fetch settings, current game, then throttle refreshes
        throttle(lastCurGameRefresh, multiplayerSettings.currentGameRefreshDelay) {
            currentGame.requestUpdate()
        }
        throttle(lastAllGamesRefresh, multiplayerSettings.allGameRefreshDelay) {
            requestUpdate(doNotUpdate = listOf(currentGame))
        }
    }
}.launchIn(CoroutineScope(Dispatcher.DAEMON))

```

*Source*: [`Multiplayer.kt`](https://github.com/yairm210/Unciv/blob/main/Multiplayer.kt) – lines 61‑79

This background sync respects user-configurable delays (`currentGameRefreshDelay`, `allGameRefreshDelay`) and ensures all participants see current turn information even without manual refresh actions.

## Event-Driven UI Architecture

Unciv uses an internal `EventBus` system to decouple network operations from UI updates. Key events defined in [`OnlineMultiplayerEvents.kt`](https://github.com/yairm210/Unciv/blob/main/OnlineMultiplayerEvents.kt) include:

- `MultiplayerGameUpdated`
- `MultiplayerGameUpdateStarted`
- `MultiplayerGameUpdateEnded`
- `MultiplayerGameNameChanged`

UI components such as `MultiplayerStatusButton`, `MultiplayerStatusPopup`, and `MultiplayerScreen` subscribe to these events. For example, `MultiplayerStatusButton` registers listeners at lines 52‑71 in [`MultiplayerStatusButton.kt`](https://github.com/yairm210/Unciv/blob/main/MultiplayerStatusButton.kt), displaying loading spinners and updating tooltips when synchronization occurs.

## Server Abstraction and Authentication

`MultiplayerServer` abstracts concrete storage implementations behind a unified interface. The server URL is retrieved from user settings (`UncivGame.Current.settings.multiplayer.getServer()`).

Authentication is performed on each request if the server advertises an `authVersion` (see `MultiplayerServer.authenticate`). Servers also advertise capabilities via `ServerFeatureSet`, enabling optional UI features such as chat functionality.

## Summary

- **Dual-storage architecture**: `MultiplayerFiles` handles local caching while `MultiplayerServer` manages remote storage via Dropbox or custom servers.
- **Central orchestration**: The `Multiplayer` class coordinates between local and remote states, providing the primary API for game lifecycle operations.
- **Throttled synchronization**: The `throttle` helper prevents rate-limit violations, while the `multiplayerGameUpdater` coroutine polls every 500 milliseconds for background sync.
- **Preview-based detection**: Lightweight `MultiplayerGamePreview` objects enable quick comparison of turn numbers and current players before downloading full game states.
- **Event-driven updates**: The `EventBus` system keeps UI components synchronized with network state changes without tight coupling.

## Frequently Asked Questions

### How does Unciv determine whose turn it is in a multiplayer game?

Unciv uses extension functions in [`Multiplayer.kt`](https://github.com/yairm210/Unciv/blob/main/Multiplayer.kt) (lines 71‑73) to check if the current user is the active player. The `isUsersTurn()` function compares the `currentPlayer` civilization's `playerId` against the locally stored user ID from settings.

### What prevents Unciv from overwhelming the server with update requests?

All network operations pass through a `throttle` helper (lines 29‑44 in [`Multiplayer.kt`](https://github.com/yairm210/Unciv/blob/main/Multiplayer.kt)) that enforces rate limits based on configurable refresh delays. The background polling loop respects these limits while maintaining responsiveness.

### Can Unciv operate with servers other than Dropbox?

Yes. While Dropbox is the default backend, `MultiplayerServer` abstracts storage through interfaces that allow custom server implementations via [`UncivServerFileStorage.kt`](https://github.com/yairm210/Unciv/blob/main/UncivServerFileStorage.kt). Users can configure custom server URLs in the game settings, and the system supports authentication and feature negotiation via `ServerFeatureSet`.

### What happens when a player skips their turn in Unciv?

When skipping a turn via `Multiplayer.skipCurrentPlayerTurn`, the system downloads the latest server state, runs AI automation for the skipped civilization using `NextTurnAutomation.automateCivMoves`, advances the turn counter with `gameInfo.nextTurn()`, and re-uploads both the full game state and a lightweight preview to the server.