How City Territory Growth Is Calculated in Unciv: Culture Mechanics Explained
City territory growth in Unciv is calculated based on accumulated culture, where each new tile requires an exponentially increasing amount of culture determined by the formula 6 × (tilesClaimed + 1.4813)^1.3, modified by game speed, city-state status, and unique abilities.
Unciv implements Civilization V's border expansion mechanics in Kotlin, handling city territory growth through a dedicated expansion manager. The system accumulates culture each turn and automatically claims new tiles when thresholds are met. Understanding this calculation helps players optimize their cultural output for faster territorial expansion.
The Culture Accumulation Mechanism
Every city in Unciv tracks its cultural progress toward claiming new tiles separately from empire-wide culture. The CityExpansionManager class located in core/src/com/unciv/logic/city/managers/CityExpansionManager.kt handles all aspects of border growth.
Turn-Based Culture Generation
Each turn, the city generates culture based on its yields, buildings, and modifiers calculated by CityStats. This produced amount feeds directly into the expansion system through the nextTurn(culture: Float) method. The incoming culture is converted to an integer and added to cultureStored, which represents the city's current progress toward its next tile.
// From CityExpansionManager.kt
fun nextTurn(culture: Float) {
cultureStored += culture.toInt()
if (cultureStored >= getCultureToNextTile()) {
val location = addNewTileWithCulture()
if (location != null) {
val locations = LocationAction(location.toHexCoord(), city.location.toHexCoord())
city.civ.addNotification("[${city.name}] has expanded its borders!", locations,
NotificationCategory.Cities, NotificationIcon.Culture)
}
}
}
How the Culture Cost Formula Works
The core of city territory growth calculation lies in getCultureToNextTile(), which implements Civ V's exponential cost curve. The method determines exactly how much culture must be stockpiled before the city can claim another hex.
The Base Calculation
The formula uses the number of tiles already claimed outside the city center ring (tilesClaimed()) as its primary variable:
// From CityExpansionManager.kt (lines 48-59)
var cultureToNextTile = 6 * (max(0, tilesClaimed()) + 1.4813).pow(1.3)
cultureToNextTile *= city.civ.gameInfo.speed.cultureCostModifier
if (city.civ.isCityState) cultureToNextTile *= 1.5f
for (unique in city.getMatchingUniques(UniqueType.BorderGrowthPercentage))
if (city.matchesFilter(unique.params[1]))
cultureToNextTile *= unique.params[0].toPercent()
return cultureToNextTile.roundToInt()
Key parameters in this calculation:
- Base curve:
6 × (n + 1.4813)^1.3where n equals tiles already claimed - Game speed modifier: Multiplied by the culture cost factor from the current game speed setting
- City-state penalty: City-states suffer a 50% cost increase (1.5× multiplier)
- Border growth uniques: Modifiers from abilities like "[X]% growth" reduce the effective cost through percentage adjustments
Tile Selection and Acquisition Process
When accumulated culture meets or exceeds the required threshold, the system automatically triggers territory expansion without player intervention.
Checking for Expansion Eligibility
The nextTurn method continuously monitors cultureStored against getCultureToNextTile(). Upon reaching the threshold, it invokes addNewTileWithCulture(), which deducts the cost and initiates the claiming sequence.
// Calculating required culture programmatically
val needed = city.expansion.getCultureToNextTile()
// Simulating culture addition (normally called by CityTurnManager)
city.expansion.nextTurn(5f)
Selecting the Best Tile
The chooseNewTileToOwn() method identifies candidates from getChoosableTiles()—unowned tiles within the city's expansion radius. It selects the optimal tile using Automation.rankTileForExpansion(), which evaluates factors like resource yields, strategic value, and proximity to the city center.
// From CityExpansionManager.kt
private fun chooseNewTileToOwn(): Tile? =
getChoosableTiles().minByOrNull { Automation.rankTileForExpansion(it, city) }
private fun addNewTileWithCulture(): Vector2? {
val chosenTile = chooseNewTileToOwn()
if (chosenTile != null) {
cultureStored -= getCultureToNextTile()
takeOwnership(chosenTile)
return chosenTile.position.toVector2()
}
return null
}
After selection, takeOwnership(tile) transfers the tile to the city, updates internal caches, reassigns working population if necessary, and notifies the player through the notification system.
Modifiers and Unique Abilities
Several game mechanics modify the base culture cost for city territory growth:
- Game Speed: Marathon and Epic speeds increase the culture required per tile, while Quick speeds reduce it through
cultureCostModifier - City-States: These entities automatically pay 1.5× the normal culture cost, slowing their natural expansion
- Border Growth Percentage: Uniques defined in
UniqueType.BorderGrowthPercentageallow buildings, policies, or leader abilities to modify costs. These uniques follow the format "[X]% [tileFilter]" and stack multiplicatively with other modifiers
Summary
- City territory growth depends on accumulated culture stored in
CityExpansionManager.cultureStored - Cost formula:
6 × (tilesClaimed + 1.4813)^1.3forms the exponential baseline from Civ V mechanics - Automatic expansion: When
cultureStoredexceedsgetCultureToNextTile(), the city claims the best available tile automatically each turn - Selection algorithm:
Automation.rankTileForExpansiondetermines which tile to take based on yields and strategy - Multiplicative modifiers: Game speed, city-state status (1.5× penalty), and unique abilities adjust the final culture cost
Frequently Asked Questions
Why does my city stop expanding even with high culture output?
If your city stops expanding despite generating culture, it has likely claimed all available tiles within its workable radius or cultural influence range. The getChoosableTiles() method returns an empty set when no valid unowned tiles remain adjacent to your territory, halting expansion regardless of how much culture is stockpiled.
How do game speeds affect border growth in Unciv?
Game speeds modify city territory growth through the cultureCostModifier property defined in the speed ruleset. Slower speeds like Marathon significantly increase this multiplier, requiring substantially more culture per tile, while Quick speeds reduce the cost accordingly. This modifier applies after the base calculation but before rounding to integers.
Can players manually choose which tile to claim next?
No, Unciv automatically selects expansion tiles using the Automation.rankTileForExpansion algorithm. This function prioritizes high-yield tiles, strategic resources, and tiles closer to the city center. Players cannot override this selection directly, though they can manipulate outcomes by controlling which tiles become available through manual tile purchasing or Great Person placement.
What is the significance of the 1.4813 constant in the formula?
The constant 1.4813 in the culture cost formula ensures the first few tiles follow Civ V's intended cost curve, preventing the first expansion from being prohibitively expensive while maintaining exponential growth. Combined with the 1.3 exponent, this creates a curve where early tiles cost roughly 10-20 culture, while later tiles can require thousands.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →