Prevent AI from placing ports on small lakes 🚢 (#4429)

## Description:

AI nations were placing ports on small decorative ponds scattered across
maps (Missisipi for example), wasting structure slots on strategically
useless water bodies. This fix adds a water component size check to the
port placement logic so the AI skips lakes that are too small for
meaningful port use. We already had a check for available trade
partners, but trading in small lakes is usually stupid.

**How it works:**
- `ConnectedComponents` now tracks component sizes during its existing
flood-fill (zero extra cost - counts tiles as they're visited)
- `AbstractGraph`, `WaterManager`, and the `Game` interface expose
`getWaterComponentSize(tile)` so callers can query the size of any water
body
- `NationStructureBehavior.randCoastalTileArray()` filters out non-ocean
water components below `MIN_PORT_WATER_COMPONENT_SIZE` (3000 minimap
tiles, ~12000 full-map tiles)
- Ocean tiles bypass the check entirely since they're always large
enough

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin
This commit is contained in:
FloPinguin
2026-06-28 19:19:58 -07:00
committed by GitHub
parent 06d505ebc9
commit ccd0745ad4
7 changed files with 142 additions and 19 deletions
@@ -78,6 +78,13 @@ const FIRST_MISSILE_SILO_RATIO = 0.4;
/** If we have more than this many structures per tiles, prefer upgrading over building */
const UPGRADE_DENSITY_THRESHOLD = 1 / 1500;
/**
* Minimum number of full-map water tiles a water body must have for the AI to
* consider placing a port on it. Prevents the AI from wasting ports on tiny
* decorative ponds scattered across the map.
*/
const MIN_PORT_WATER_COMPONENT_SIZE = 3000;
/** Estimated number of tiles per city equivalent, used when cities are disabled */
const TILES_PER_CITY_EQUIVALENT = 2000;
@@ -844,7 +851,14 @@ export class NationStructureBehavior {
// tile a valid port site — skip the component lookup.
if (this.game.isOcean(neighbor)) return true;
const comp = this.game.getWaterComponent(neighbor);
if (comp !== null && shared.has(comp)) return true;
if (comp === null || !shared.has(comp)) continue;
// Skip tiny lakes that are too small for meaningful port use (not on Easy).
const { difficulty } = this.game.config().gameConfig();
if (difficulty !== Difficulty.Easy) {
const size = this.game.getWaterComponentSize(neighbor);
if (size !== null && size < MIN_PORT_WATER_COMPONENT_SIZE) continue;
}
return true;
}
return false;
});