There were a lot of reports of the combat AI being very incompetent when trying to attack buildings, especially from close range. Ships would just float around unmoving instead of closing in and bringing their weapons to bear.
So I set about reproducing this problem, which turned out to be rather easy. I set up a combat of an AI grenade bomber versus a building, and the bomber completely failed to position itself.
I will explain the cause of this in a moment, but I first have to get into the detail of how combat AI works. Its main function is to evaluate possible positions for the ship to move to. For each position, it looks at how much damage it can deal from there and how much enemy fire it's exposed to. Some positions it can't get to, because something's in the way, or because they're above its service ceiling.
The other mildly confusing thing I need to mention is how the y-axis in the game's coordinate system works. It points downwards, so ships with a higher y-coordinate value are further down. And the zero point is at about 70 metres above ground. Not the most sensible way of doing things, but it just evolved that way.
I have a convenient debug view in the game that lets me see the combat AI's evaluation of all the positions it considers. In the case of the combat of the bomber versus the building, I could instantly see that it marked all the positions near the ground as unreachable. All the positions with y-coordinate greater than zero.
At this point I had a pretty good idea of what was going on. The culprit was an obscure module type value called aiMaxY. This value is used to tell the monster AI to not move too far down, because it looks weird. For example, it prevents the Aerial Jelly, which is meant to hover above your ships and attack like that, from moving all the way to the ground.
aiMaxY is set to 10000 by default, going all the way into the ground. But by looking at the debugger, I could see that the bomber's aiMaxY was actually set to 0. So the AI considered all positions where y was greater than 0 as invalid.
Now I just needed to figure out why the value was at 0 instead of 10000.
The culprit there turned out to be flipped modules. The information for flipped versions of modules is derived automatically from their un-flipped counterpart. The game does this by copying all the information and adjusting it where needed, making firing arcs point the other way, mirroring the graphical appearance, and so on.
But I had forgotten one line in this code. It didn't copy over the aiMaxY value, and instead left it at 0.
So all flipped modules would tell the combat AI to not take the ship beyond y = 0. Any airship with a flipped module had broken AI. Which was many but not all of them, making the bug appear inconsistently.
The fix was painfully easy: add that line to copy over the aiMaxY value for the flipped module. And with that one change, the AI started behaving much better!