Skip to content

Settler re-picks a destination when its chosen spot becomes unreachable (#213) - #1002

Merged
ajhalme merged 6 commits into
C7-Game:Developmentfrom
Billytifft:billy/ai-routing-foreign-barriers
Oct 4, 2026
Merged

ajhalme merged 6 commits into
C7-Game:Developmentfrom
Billytifft:billy/ai-routing-foreign-barriers

Conversation

@Billytifft

@Billytifft Billytifft commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

#213 — Settler AI stuck forever when destination is unreachable. The
original crash ("Could not get next part of path" exception) was already gone —
TryToMoveAlongPath repaths and fails gracefully with Result.Error. But the
settler was still effectively stuck: candidate selection re-picked the
unreachable destination (e.g. a rival unit parked on it), so the settler
re-evaluated the same impossible task every turn and never built.

Instead of blanket-rejecting foreign-occupied tiles at selection time (the
blocker may move before the settler arrives), the settler now only gives up a
tile when it actually tries to reach it and fails: on Result.Error it marks
that tile unreachable for the current AI pass, picks a new destination, and
repaths; if nothing is left it degrades to JOIN_CITY (disband) rather than
looping. The exclusion is scoped to the current SettlerAIData, so a tile
whose blocker moves away is eligible again later.

Changes

Verification

  • dotnet build — 0 errors
  • dotnet test — 78 passed, 0 failed
  • dotnet format ... whitespace --verify-no-changes — clean

@ajhalme ajhalme changed the title Don't route through rival cities or onto foreign-unit tiles (#213, #371) Don't route through rival cities or onto foreign-unit tiles Sep 28, 2026

@stavrosfa stavrosfa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that the description of the PR does not match the changes.

Something like "Don't send settlers on tiles that are currently occupied by foreign units" seems more accurate to me.

There is nothing that checks if the settler goes through a city. For example, what about cities that are empty? As you state in the description of this PR, this is already adressed.

Also, having the algorithm reject outright tiles that are currently occupied (a unit passing through), but then the settler arrives in 5 turns there, seems like a bad choice.
It makes more sense to me to reject these tiles, for example when the settler is right next to them. If it takes 5 turns to get there, and the tile is still occupied by a random fortified unit or whatever, then we can exclude them and repath accordingly.

I also want to point out (something very very obvious, but nevertheless) that there is a difference between an occupied tile and an owned tile.

This PR could probably serve as a temp fix for the bug with the settlers, but it should be accompanied by a TODO to make this better in the future.

Comment thread C7Engine/AI/UnitAI/SettlerLocationAI.cs Outdated
Comment thread C7Engine/AI/UnitAI/SettlerLocationAI.cs Outdated
@Billytifft

Copy link
Copy Markdown
Contributor Author

The PR name came from the related issues. I can update it.

Make MapUnit.HasForeignUnits internal so SettlerLocationAI can call it,
and drop the duplicate HasForeignUnitOnTile method and its XML doc.
@Billytifft Billytifft changed the title Don't route through rival cities or onto foreign-unit tiles Fix settler AI picking unreachable foreign-occupied tiles, add rival-city routing tests (#213, #371) Sep 28, 2026
@Billytifft

Copy link
Copy Markdown
Contributor Author

Comments addressed, including the PR title.

@ajhalme

ajhalme commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

having the algorithm reject outright tiles that are currently occupied (a unit passing through), but then the settler arrives in 5 turns there, seems like a bad choice.
It makes more sense to me to reject these tiles, for example when the settler is right next to them. If it takes 5 turns to get there, and the tile is still occupied by a random fortified unit or whatever, then we can exclude them and repath accordingly.

I agree with this approach. In general, what is here proposed to be categorically rejected isn't an illegal approach, or even a bad one. The AI already sends Settlers out with an escort that can take out the odd Warrior that blocks a narrow peninsula, say.

In any case, I'd approach this via target tile scoring adjustment that accounts for nearby enemy presence or existence of (nearby) owned tiles. Even the option of declaring a war could ultimately feature in the scoring.

@stavrosfa

Copy link
Copy Markdown
Contributor

To add to that, we should also perhaps think if we want all the AI to be able to know about nearby enemy presence.

We might want to have the easiest AI (chieftain) to not be able to know about an inactive (from their perspective) tile with a unit on it. And at the same time, a Sid difficulty AI to see everything.

So yeah, hardcoding this here might not be the best move.

We also already do this, in a different context humans/AI in general, where if you send a unit to go to a (either fog of war or inactive) tile with the go-to mechanic, it will accept it even if on that tile there is a foreign unit, and that's by design. But the AI will never do this, since it knows, even if it can't "see" the tile, that the move is illegal.
It's the same with, for example, sea tiles that are unexplored yet. We allow the human to go in any unexplored tile too. Check out ResolveIntent in MapUnit.cs for it.

This is true to the original game too.

…ne becomes unreachable

Instead of rejecting any tile that currently holds a foreign unit when
selecting a destination (which needlessly rules out spots the blocker may
leave before the settler arrives), let selection stay unrestricted and only
react when the settler actually fails to reach its destination.

When TryToMoveAlongPath reports an Error (e.g. a rival unit parked on the
destination mid-journey), SettlerAI now marks that tile unreachable for the
current AI pass, re-picks a destination excluding it, and repaths. If no
destination is left it falls back to JOIN_CITY instead of re-evaluating the
same impossible task every turn (C7-Game#213). The exclusion set lives in
SettlerAIData, so a fresh AI pass forgets it and a tile whose blocker moves
away is eligible again.

SettlerLocationAI.FindSettlerLocation/GetScoredSettlerCandidates gain an
optional excludedTiles set; the foreign-unit tile filter is removed and
MapUnit.HasForeignUnits goes back to private.

Tests now cover unrestricted selection, retargeting to a reachable
alternative, and graceful downgrade to JOIN_CITY when nothing is left.
@Billytifft Billytifft changed the title Fix settler AI picking unreachable foreign-occupied tiles, add rival-city routing tests (#213, #371) Settler re-picks a destination when its chosen spot becomes unreachable (#213); add rival-city routing tests (#371) Sep 29, 2026
@Billytifft

Copy link
Copy Markdown
Contributor Author

Reworked the fix.

A foreign unit on a tile no longer rejects it. The unit may move away before the settler arrives, so selection keeps that tile eligible.

The settler gives up a tile only when it tries to reach it and fails. TryToMoveAlongPath returns Result.Error, SettlerAI marks the tile unreachable for the current AI pass, picks a new destination, and repaths. If no tile is left, it falls back to JOIN_CITY instead of looping forever.

The exclusion list lives in SettlerAIData. A new AI pass clears it, so a tile whose blocker moves away is eligible again.

I added a TODO on FindNewDestination for the long-term fix: path-checking at selection time.

@ajhalme

ajhalme commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

The settler AI stuff looks fine, and seems to improve on the current infinitely stuck behaviour.

--

I'd drop all the material relating to #371. There's some duplication in the tests in RivalCityRoutingTest.cs, and I don't think the treatment of combat-time routing is quite correct either. It looks like the test is trying to nail down behaviour relating to the case where AI units should route around enemy cities on their way to a city deeper in the enemy empire. I mean I guess we can add tests for that, but I feel like the larger scale AI behaviour should be affirmed first.

This patch doesn't contain any fixes for #371, which may be correct, but what we really want is a fix for players not respecting right of passage. I'd look into to that as a target, in a separate PR or issue.

In general, no point in conflating fixes / test coverage patches for two issues that are only superficially related.

This PR now fixes only C7-Game#213 (settler AI stuck on an unreachable
destination). The rival-city routing tests were regression-only coverage
for behavior already fixed by intent-based movement, contained duplicated
test bodies, and encoded provisional combat-routing assumptions. They will
be reworked as part of a separate C7-Game#371 follow-up.
@Billytifft Billytifft changed the title Settler re-picks a destination when its chosen spot becomes unreachable (#213); add rival-city routing tests (#371) Settler re-picks a destination when its chosen spot becomes unreachable (#213) Oct 4, 2026
@ajhalme

ajhalme commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Thanks, this is much more compact and to the point now - good stuff!

@ajhalme
ajhalme merged commit 7d22f34 into C7-Game:Development Oct 4, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

If Settler AI can't complete the next step in its path, it gives up and stays where it is forever

3 participants