Since posting R9 I've been looking into the AI worker routines to see if I could allow the AI to replace tile improvements. I was hoping this would be a simple matter of flipping a bit somewhere, but it's not. Maybe that's for the best since it's led me to making deeper changes to the worker AI, and while I'm doing that I can hopefully get the AI to make better decisions overall about when to irrigate, like stopping it from irrigating grassland in despotism.
You might guess that the AI automates its workers but technically it doesn't, at least not for the core jobs. It does use the road building, pollution clearing, and forest chopping automation states, but for irrigating and mining it orders its workers around "manually". Though the functions it uses to determine what to build are probably reused for automation of human workers, I haven't looked into that. Drilling through several layers of AI decision making, at the bottom there's one critical function that determines whether to mine or irrigate a tile. For anyone following along at home, its GOG address is 0x42B820 and its signature is:
Code:
bool __thiscall City::should_irrigate (City * this, int tile_x, int tile_y, int terrain_type_id, int param_4)
This function's job is to answer the question "does this city want the tile at (tile_x, tile_y) irrigated?". If it answers no (false), that's interpreted to mean the tile should be mined instead.
This function is responsible for the two big problems with AI worker management: under the standard game rules it will never tell a worker to replace a mine with irrigation or vice-versa, and it fails to properly account for the despotism penalty when deciding what to build. It won't replace mines<->irrigation because it's programmed to always preserve the existing improvement as long as the nominal yield of the replacement is the same. I.e., if a city is surrounded by mined plains, the function reasons it could replace a mine worth 1 shield with irrigation worth 1 food and since 1 = 1, they're the same, not worth replacing. The problem of course is that it doesn't account for what the city needs. Note that the AI actually can replace improvements but only if you mod the game so that the replacement is nominally better. For example, set up a scenario with an AI city surrounded by mined plains and make irrigation worth 2 food on plains, you'll see the AI will irrigate over the mines where possible.
About the tile penalty, the should_irrigate function does contain some logic to deal with that, except it's bugged. At the beginning of the function, it multiplies the food and shield values of irrigation and mines by some factor that was presumably supposed to be variable but in the end it's fixed at 4.0 for both. The bug is that the logic to handle the despotism penalty uses the scaled yields instead of the raw yields. So the logic for irrigating grassland in despotism goes something like this: irrigating would be worth 4.0 * 1 food = 4 food, hitting the penalty, but mining would be worth 4.0 * 1 = 4 shields, also hitting the penalty, so the penalty doesn't matter.
The tile penalty issue could be fixed by setting the scaling factor to 1.0, assuming that has no negative side effects, but I don't see any way to fix the replacement issue without overhauling the logic since there the fundamental problem is that the function tries to compare the value of food and shields without checking what the city actually needs. So I plan to replace should_irrigate entirely like I did for the leader unit AI, and I'm doing it for the same reason that the logic's buggy but even without the bugs I don't like what it's trying to do. My idea for a replacement algorithm is that tiles owned by some city will be irrigated in order of their potential until the city has enough food to reach its target population (plus a little) then all remaining tiles will be mined.
So far I've implemented the basic algorithm and checked that it basically works. It was surprisingly tricky to get it working well, an easy failure mode to fall into is AI workers getting trapped in cycles of irrigating over mines then mining over irrigation, back and forth, on the same tile for potentially dozens of turns. The fundamental problem is a feedback loop between the amount of food the city has and the improvements surrounding it. To break that loop I had to make the irrigation logic dependent not on how much food the city has but rather only on the surrounding terrain. Even then there were subtler issues like the fact that the set of tiles available to irrigate changes depending on which ones are already irrigated, and that if a tile can be worked by different cities then it can get contradictory instructions for improvement.
Anyway, like I said it's basically working. The next steps are: (1) Define the target population for AI cities. Right now it equals the number of tiles owned by the city, which works well as a baseline, but ideally the definition would include limits imposed by buildings & freshwater and maybe happiness too. (2) Better prioritize irrigatable tiles. It needs to account for the fact that most tiles can either be mined or irrigated so when choosing to irrigate there's an opportunity cost for not mining. (3) Run some more experiments with the new algorithm. In particular I'm planning to create a scenario with starting areas designed to test the AI so I can compare with vs without the new irrigation logic.