While in my case, working with the campaign AI proved to be a fruitless and unrewarding task (I discovered there were many factors that were ultimately influencing the behavior of the AI, factors external to the xml directives I was trying to modify) I did learn a thing or two about how the campaing_ai_db file works, and how it interfaces with the LTGD. I decided to share some of my findings here in case someone finds them useful. Before venturing any further, I will preemptively state that these are conclusions to which I arrived based on my observations, and experiences and while I believe I'm correct, short of looking at the game's source or using a disassembler, we can't be 100% certain of anything here.
Before starting, I will boldly state that after spending hours messing around with all this, I've no Idea what it is the invade decisions do exactly. Honestly, it's perplexing. I do know that they are not responsible for the merging of armies in any capacity, though. It might seem so at times, because, often a particular invasion directive can exacerbate the merging issue, but I am certain the root of the problem is elsewhere. I also believe that, although the invasion decisions influence the size of the stacks of AI armies, the biggest factor is something else entirely. It might not be be a single setting, but a combination of them, and they might even be on multiple files. So if you're looking to mod the campaing_ai_db file in hopes of addressing these two *big* issues, I suggest looking somewhere else first...
Also, I'd strongly recommend you to create a hotseat game, choosing whatever faction you want to monitor, select the skip_ai_movement option, use the toggle_fow command and finally give the control of your faction to the AI by typing control factionname. Where factioname is the name of the faction. This is the best way to observe the disaster that is the campaign AI and all the stupid glitches and bugs that make it perform so poorly. As you'll soon realize, most these have little to do with the campaing_ai_db file.
Behavior:
The way the AI behaves is mostly dictated internally, we can only give it general guidelines on what to do. Crusades and Jihads are completely handled internally, we have absolutely no control over it and can't make a faction join a crusade much less create a crusade army. Some AI config files waste many entries with several conditions trying to 'detect' crusades. this of course is a complete waste of space and processing cycles. On the other hand, can_force_invade does have an effect on crusades, since, when disabled, crusade armies will not attack the target faction. Internal code will create an army, often taking vital forces that could be used elsewhere, they will march the army all the way to the destination, and then just stand there, wasting time and money until can_force_invade equals true or the crusade ends.
Similarly, naval invasions are handled by internal engine code. We can't make a faction launch a naval invasion any more than we can make it join a crusade. Although we can completely disable the internal system which automatically selects a target faction to attack, by setting can_force_invade to false.
Vassal factions are not processed as normal factions. Once a faction becomes a vassal, it's invasion processing is disabled by the LTGD(with the exception of crusades which are handled internally anyway). This means that no matter how we much we try, we can't make a vassal launch an invasion, even to help their overlords. Only defend decisions are evaluated for these factions.
Destroyed and yet to be spawned factions, do not get processed by the LTGD. Hoewever, other factions still analyze them. There's no sure way to determine if a faction is destroyed or not yet on the map, to circumvent its processing.
Slave factions are, despite what one may think, processed sort of like a normal faction and both invade and defend decisions are evaluated by the LTGD. However, because of the peculiar situation they find themselves in, being spread around the world and at war with everyone, the game seems not to evaluate any invasion target for them and the data obtained from them is completely bogus most of the time. We occasionally have the same problem with other factions, which is why is important to evaluate the data we are receiving to see if it falls within "normal" range, and ignore it otherwise. So all this means that trying to make the slave faction do somehting meaningful as a whole is a fool's errand. I've had much success with them by using an extremely simple configuration, with only a couple of entries, that sees them defend their lands when attacked and venture causing devastation and often besieging and taking weak settlements.
Code:
<faction_ai_label name="slave_faction">
<defend_decisions>
<decision_entry>
<min_entry is_neighbour="true"/>
<faction_attitude defense="defend_deep"/>
</decision_entry>
</defend_decisions>
<invasion_decisions>
<decision_entry>
<min_entry is_neighbour="true"/>
<faction_attitude invade="invade_raids" invade_priority="512"/>
</decision_entry>
</invasion_decisions>
</faction_ai_label>
Decision entries:
Decision entries are evaluated sequentially, for each faction toward other factions. When an entry is found in which all tests are successful, the entry's parameters are read into the game and further evaluations for that target faction are aborted(unless continue is set to true).
Min and Max:
Min-max entries are akin to greater or equal than, less or equal than comparison operators, respectively. That is to say:
Code:
<decision_entry>
<min_entry military_balance="0.5"/>
<max_entry military_balance="1.0"/>
</decision_entry>
would be equivalent to the following expresion: "if (military_balance >= 0.5 && military_balance <= 1.0)" in a typical scripting language. Or: "if military balance is greater or equal to 0.5 and less or equal to 1.0" in plain English.
Decision entries that have a "true" or "false" value are interpreted as if their value is 1 for true and 0 for false. As such, there's no need to compare both ends of the spectrum, in this manner:
Code:
<decision_entry>
<min_entry is_neighbour="true"/>
<max_entry is_neighbour="true"/>
</decision_entry>
That is the same as saying: if neighbour is >= 1 and neighbour <= 1, which is not only redundant but wasteful of both space and posibly CPU cycles as each of the conditions specified here are parsed to later be evaluated during the game; and judging by the size of the original campaign AI I doubt the develoopers bothered to perform optimizations in this regard. A simple is_neighbour >= 1 will do if you want to test if it's a neighbour or, is neighbour <= 0 if you want to know if it is not a neighbour. Going back to the example above, that would be:
Code:
<decision_entry>
<min_entry is_neighbour="true"/> ; is_neighbour >= 1
</decision_entry>
OR if you want to see if it's not a neighbour:
Code:
<decision_entry>
<max_entry is_neighbour="false"/> ; is_neighbour <= 0
</decision_entry>
Other decision entries behave just like that. In fact everything that is assigned to a decision entry (="xxxx") has a numerical value, yes that includes religion and faction names. For example, stance modifiers' values are: Allied = 0, Neutral = 1, AtWar = 2. Knowing this can be very helpful for properly setting up conditions and reducing redundant comparisons. eg:
Code:
<decision_entry>
<min_entry stance="AtWar"/>
<max_entry stance="AtWar"/>
</decision_entry>
isn't necessary. Simply writing:
Code:
<decision_entry>
<min_entry stance="AtWar"/>
</decision_entry>
will suffice, because AtWar is the highest value and there are no other modifiers >= AtWar(2). Similarly, if you are looking for Allied this will do the trick:
Code:
<decision_entry>
<max_entry stance="Allied"/>
</decision_entry>
But if you want to find Neutral, specifically, then a range comparison is necessary because its value, 1, is between the others (0 and 2):
Code:
<decision_entry>
<min_entry stance="Neutral"/>
<max_entry stance="Neutral"/>
</decision_entry>
Religion has the following integral values: catholic = 0, orthodox = 1, islam = 2, pagan = 3
Rand:
The rand entry tests a random value between 0 and 1.0. Any value between that range has an equal chance of occurring. For example, the following would have an 80% chance of triggering:
Code:
<decision_entry>
<min_entry rand="0.2"/>
</decision_entry>
because there's an 80% chance for rand to return a number greater or equal to 0.2 (1.0 - 0.2 = 0.8).
I've seen people do things like:
Code:
<decision_entry>
<min_entry rand="0.2"/>
<max_entry rand="0.4"/>
</decision_entry>
to get a 20% chance of the condition to trigger; which completely boggles my mind. That is not necessary. The developer's themselves give plenty example on how to use rand in the vanilla campaign_ai file.
Default values:
Every decision entry comes initialized with a default value. What that value is, can be observed by enabling LTGD log. Knowing what the default values are can be important to avoid creating unnecessary entries / comparisons. All entries are reset to their default value after every turn. Some of the default values: invade_priority = -1, invade = invade_none, defend = defend_normal, continue = false.
Assigning entries their default value, isn't necessary, but contrary to tests, which have to be evaluated every turn by the game, this won't affect affect turn times because it's done while parsing the file and is a one off operation. At times, I explicitly set an entry to its default value. I do this to be explicit and help those reading the file, not because there's a need for it.
Misleading Decision entries:
num_enemies:
Contrary to what its name might indicate, this doesn't tell how many factions a faction is at war with, as shown in the diplomacy screen, but rather with how many factions they're military engaged with. You can be "at war" with 5 factions but only really fighting one, and num_enemies will correctly report 1.
can_force_invade:
Allows or denies the game to perform automatic invasions. The LTGD picks targets based on internal code, and seems to choose the one with highest score. These invasions are the only way to allow naval attacks. Invasions initiated by the "invade" directive will never be carried through the sea. Certain locations seem to have hardcoded naval invasions and will always occur regardless if this setting is enabled or disabled.
at_war:
A mystifying entry. After setting it to true, subsequent tests for stance="AtWar" will be true -even if the factions are not at war at all- for the duration of the turn. Yet there seems to be more to it, as my tests show factions to be more effective and aggressive when fighting rebels. In fact, when toggled, no invade directive seems necessary; factions will attack the nearest rebel settlements immediately and with great force. I've yet to determine if this occurs for non rebel settlements as well. It could be that the setting merely toggles on can_force_invade internally.