# How the EspionageManager Works in Unciv: Spy Management and Intelligence Operations

> Discover how Unciv's EspionageManager coordinates spy operations, calculates visibility, and manages turn-based intelligence missions. Learn about spy management in Unciv.

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

---

**The EspionageManager in Unciv orchestrates all spy operations for a civilization by maintaining an `ArrayList<Spy>` of operative agents, calculating visibility through enemy cities, filtering stealable technologies, and coordinating turn-based actions while delegating specific espionage behaviors to the `Spy` class state machine.**

The espionage system in the open-source Civilization V clone Unciv relies on the `EspionageManager` class to coordinate covert operations between rival civilizations. Located in [`core/src/com/unciv/logic/civilization/managers/EspionageManager.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt), this manager works in tandem with the `Spy` class defined in [`core/src/com/unciv/models/Spy.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/models/Spy.kt) and the city-level effects handler in [`CityEspionageManager.kt`](https://github.com/yairm210/Unciv/blob/main/CityEspionageManager.kt) to handle everything from spy recruitment to tech theft. Understanding how the EspionageManager works reveals the architecture behind diplomatic subterfuge and intelligence gathering in the yairm210/Unciv repository.

## Core Architecture of the EspionageManager

The EspionageManager serves as the central coordinator for all espionage activities within a civilization. It maintains an `ArrayList<Spy>` called `spyList` that tracks every operative under your control.

According to the Unciv source code, the manager delegates actual spy behavior to the `Spy` class, which implements a state machine for actions like establishing surveillance networks, stealing technology, inciting coups, and conducting counter-intelligence. The EspionageManager handles higher-level concerns such as visibility calculations, technology filtering, and UI state management, while [`CityEspionageManager.kt`](https://github.com/yairm210/Unciv/blob/main/CityEspionageManager.kt) manages city-specific effects like sabotage.

## Lifecycle and Initialization

### Transient Setup and Civilization Linking

When a game loads, the EspionageManager receives its parent civilization reference through the `setTransients` method. This method propagates the civilization reference to every stored spy via `Spy.setTransients`, ensuring each spy knows which civilization it serves and can access game-wide data.

### Spy Creation via addSpy()

The `addSpy()` method handles operative recruitment by generating a unique name through `getSpyName()`, determining the starting rank via `getStartingSpyRank()`, and instantiating a new `Spy` object. The new spy is registered in `spyList` and returned to the caller.

```kotlin
// Recruit a new spy for your civilization
val newSpy = civ.espionageManager.addSpy()
println("Created spy ${newSpy.name} with rank ${newSpy.rank}")

```

## Visibility and Intelligence Operations

### Calculating Spy Vision with getTilesVisibleViaSpies()

The manager determines which tiles are visible through espionage using `getTilesVisibleViaSpies()`. This method iterates over all spies in the "set-up" state, retrieves their assigned cities, and returns tiles within one-tile distance of each city center. This feeds directly into the fog-of-war system to reveal enemy territory on the world map.

```kotlin
// Reveal tiles visible through established spy networks
val visibleTiles = civ.espionageManager.getTilesVisibleViaSpies()
visibleTiles.forEach { tile ->
    // Process revealed tile for map display
}

```

### Tech Stealing Preparation

Before a spy can steal technology, the manager filters available targets through `getTechsToSteal(otherCiv)`. This method examines the target civilization's researched technologies and returns only those your civilization has not yet discovered or cannot currently research. The `Spy` class uses this filtered list when executing `StealingTech` actions.

## City and Location Queries

The EspionageManager provides several utility methods for locating spies:

- **`getCitiesWithOurSpies()`**: Returns all cities currently hosting your spies
- **`getSpiesInCity(city)`**: Retrieves all spies stationed in a specific city  
- **`getSpyAssignedToCity(city)`**: Returns the specific spy assigned to a city, or null if none exists

```kotlin
// List all cities with active spy networks
val citiesWithSpies = civ.espionageManager.getCitiesWithOurSpies()
citiesWithSpies.forEach { city ->
    val spy = civ.espionageManager.getSpyAssignedToCity(city)
    println("Spy ${spy?.name} is operating in ${city.name}")
}

```

## Turn Processing and State Management

### The endTurn() Method

At the conclusion of each civilization's turn, `endTurn()` forwards the call to every spy in `spyList`. Each spy then advances its action countdowns and potentially triggers state changes, such as completing network establishment or beginning active tech theft. This delegation pattern keeps the manager focused on coordination while the `Spy` class handles individual operative logic.

```kotlin
// Advance all espionage activities at end of turn
civ.espionageManager.endTurn()

```

### Cleanup on Civilization Destruction

When a civilization is defeated, `removeAllSpies()` executes to clean up operative networks. This method moves every spy to the hideout by setting their action to `None`, effectively removing them from foreign cities while preserving the spy objects in the list.

## UI Integration and Player Feedback

The EspionageManager drives the espionage overview interface through `shouldShowMoveSpies()`. This method determines whether to display the "Move Spies" button by checking three conditions: whether the player has dismissed the hint (`dismissedShouldMoveSpies`), whether any spy is currently idle, and whether there are explored foreign cities lacking assigned spies.

The [`EspionageOverviewScreen.kt`](https://github.com/yairm210/Unciv/blob/main/EspionageOverviewScreen.kt) file queries these values alongside idle spy lists to render the appropriate buttons and operative lists.

```kotlin
// Check if the Move Spies button should be visible
if (civ.espionageManager.shouldShowMoveSpies()) {
    // Enable the movement button in the UI
}

```

## Summary

- The EspionageManager in [`EspionageManager.kt`](https://github.com/yairm210/Unciv/blob/main/EspionageManager.kt) maintains the `spyList` and coordinates all civilization-level espionage operations
- Spy creation occurs through `addSpy()`, which generates unique names and starting ranks via helper methods
- Vision calculations use `getTilesVisibleViaSpies()` to reveal tiles within one range of established spy networks
- Tech stealing relies on `getTechsToSteal()` to filter targetable technologies from other civilizations
- Turn processing delegates to individual `Spy` objects through the `endTurn()` method, advancing countdowns and state machines
- UI visibility is controlled by `shouldShowMoveSpies()`, which checks for idle spies and available target cities

## Frequently Asked Questions

### What file contains the EspionageManager class in Unciv?

The EspionageManager class is implemented in [`core/src/com/unciv/logic/civilization/managers/EspionageManager.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt). This file contains the complete manager logic for spy lifecycle, visibility calculations, and UI coordination.

### How does a spy's visibility range work in Unciv?

The `getTilesVisibleViaSpies()` method in EspionageManager calculates visibility by iterating over all established spies and returning tiles within one-tile distance of each occupied city center. This is implemented in the manager rather than individual spies to aggregate vision across all operatives for the fog-of-war system.

### What happens to spies when a civilization is defeated?

When a civilization is destroyed, the `removeAllSpies()` method moves all spies to the hideout by setting their action to `None`. This clears them from foreign cities while preserving the spy objects in the `spyList`.

### How does the EspionageManager determine which technologies can be stolen?

The manager uses `getTechsToSteal(otherCiv)` to filter the target civilization's technologies, returning only those your civilization has not yet researched or cannot currently access. The `Spy` class then selects from this filtered list when executing theft actions.