CTP2 Bureau : How does the AI task it's units?
CTP2 Bureau
The Modding
Knowledgebase
Apolyton CTP2 forums
Apolyton CTP2 archives
Source Code Project SVN

How does the AI task it's units?

by Peter Triggs

0) Sources
1) The Map Phase
2) Data for the Primary Example
3) BeginScheduler: The Matching Phase
4) ProcessMatches
5) How can it go wrong?
6) What is to be done?

0) Sources:

A couple of the sources I've used need a bit of explanation. Richard Myers mentioned:

As a side note, this AI system is essentially the same as that used in CTP1, Dark Reign 1 and parts of StarTrek: Armada. We can thank Dr. Ian Davis for the original algorithm; I don't know where he got it from originally though. Ian has his own company now called maddoc software.

The AIP Manual (Artificial Intelligence Personalities manual, possibly written by Dr. Davis) for Dark Reign contains quite a good description of how the AI "matches a team's forces with that team's strategic goals" (especially of the early mapping phase which Richard never talked about) and I've incorporated some of it below. There's also a CTP1 file (done by "the Mad Dr. I") which has some information about strategic maps.

Other than that I've used the Activision documentation, Richard Myers' posts, and Locutus's analysis in the 'Conversations with Richard Myers' thread.

1) The Map Phase:

"The first step ... is to evaluate the playing field. This is done by dividing the entire map with a coarse grid, and evaluating each grid cell as a strategic goal. ... Some cells have no strategic value, and are dropped from consideration in this strategic planning cycle." (AIP Manual)

Or something like that. Presumably the UnitBeginTurnVision and CityBeginTurnVision events are used to construct "strategic maps" of what enemy armies and cities the AIciv knows about:

///////////////////////////////////////////////////////////////////////////////
//
// strategic_AI_config.txt
//
// This files contains configuration data for the strategic maps
//
//  Threat Map.................Where are the enemy units?
//  Empire Map.................How far are we from home?
//  Enemy Empire Map...........How far are we from the bad guys?
//
//
// By the Mad Dr. I
//
///////////////////////////////////////////////////////////////////////////////

There's also mention of a "death_map", an "exploration map", and a "goal_map". But this is CTP1 stuff and we don't know how much of it is still applicable. The only things it's safe to say are that something like this is going on, and that before the AI enters it's matching phase it has a number of strategic maps, one of which shows which enemy units and cities it can attack.

I think it's also safe to assume that this grid procedure is being used. It would explain a couple of the parameters used in defining the goals in goals.txt; see below.

2) Data for the Primary Example:

Suppose it's an AIciv's turn, say the French, and that they're at war only with the English (could be Human or AI). During it's BeginTurnVision phases, the computer has cycled through all it's units and made a map (not a map like we see, just an abstract map) of all the English units that it can see and all the English cities that it now sees or previously knew about. These are all it's possible targets for the goals we're going to consider: ATTACK, SEIGE, and HARASS.  I believe that the real map is divided into gridcells and that each gridcell has associated with it the following values:

a) a ThreatValue which reflects where the English troops are concentrated relative to the French troops and cities,

b) a PowerValue which reflects where the French troops are concentrated relative to the English troops and cities,

c) an EnemyValue which reflects how valuable that target is to the English and is probably based on the value of the buildings in any English city that is close to it.

d) an AlliedValue which reflects how valuable that target is to the French and is probably based on the value of the buildings in any of cities owned by the French or their allies that is close to the target. (It's very probable that 'allied' means 'not enemy', and that's what I'm assuming, so that the French cities will be included. Otherwise this would only crop up if the French had an alliance.)

It's only by assuming that the gridcell approach is being used that I can make any sense out of the AlliedValueBonus parameter in the various goal structures. For example, in GOAL_SEIGE the AlliedValueBonus is 1000 and is described as being a "normalized bonus given based on value of target if it’s an ally". But why would we want to give an AIciv a bonus for attacking a city that belongs to one of it's allies?

Here's another interesting comment from the AIP Manual:

The current scheme for calculating the threat value of a region is to add up the strength of the enemy units in that region. If that were all, however, a region with one or no units would look to be pretty weak even though the next region over might have a hundred enemy units in it. Thus, we perform a "relaxation" of the borders of regions to let the threat value of a region bleed over into it's neighboring regions. 

Presumably the same sort of thing is done with the EnemyValue and AlliedValue so that an empty gridcell next to a large city will inherit some of that city's value. It seems to me that this explains the AlliedValueBonus: it's given to enemy targets that are close (either in the same gridcell or in a neighbouring one) to French or allied cities.

Anyway, these values are associated with each possible target in the gridcell (in particular each English army and city there). They represent the French 'picture' of the current state of the war, sort of like what you actually see on the screen when it's your turn, and are used when computing the Raw Priorities for sending French armies to attempt goals that are aimed at those targets.

But things actually start with the personality type of the French leader. Suppose he has (no surprise) the DeGaulle personality. Then via the flags in personalities.txt, he'll probably be using the goals from STRATEGY_MILITARIST_DEFAULT. He might be using goals from one of the war strategies (SEIGE, ATTACK, or DEFEND) but we don't know the conditions under which these are merged into his default strategy, so let's stick primarily with it.

First, though, before going into details, here's a couple of preliminary points. Consider the difference between the original STRATEGY_MILITARIST_DEFAULT and Cradle's STRATEGY_MILITARIST_DEFAULT. In Cradle, it has priorities like:

    GoalElement { Goal GOAL_SEIGE               Priority   705000  MaxEval  25  MaxExec  15 }
    GoalElement { Goal GOAL_ATTACK              Priority   850000  MaxEval  25  MaxExec  25 }
    GoalElement { Goal GOAL_HARASS              Priority   405000  MaxEval  25  MaxExec  25 }

The priorities here are the [b]Base Priorities[/b] for executing these goals. In virtue of their magnitude, they are the overwhelmingly most significant data for determining which goals the AI will pursue.  The differences between them (Richard called them 'skip values') are also very important. With the above priorities sequence the AI will be extremely inclined to execute the ATTACK goal. Moreover, since the skip values between these goal types are very large the modifiers in the goals' definitions will be largely unimportant and it will really concentrate on this goal. As Richard put it:

In general, goals execute from highest to lowest priority. ... For example, if the GoalElement for GOAL_ATTACK has a higher priority than for GOAL_SEIGE, then AI units will first try to attack enemy armies, and then with any remaining unassigned units, try to seige enemy cities. However, if two goals have very close raw priorities, then the match priority becomes more important. The match priority is computed based on how good a match a particular army is for a specific goal.
 
This is reflected in the original data:

    GoalElement { Goal GOAL_DEFEND              Priority   557000  MaxEval   2  MaxExec   1 PerCity }
    GoalElement { Goal GOAL_SEIGE               Priority   405000  MaxEval  20  MaxExec   4 }
    GoalElement { Goal GOAL_ATTACK              Priority   405000  MaxEval  25  MaxExec  25 }
    GoalElement { Goal GOAL_HARASS              Priority   305000  MaxEval  25  MaxExec  25 }

Here both the SEIGE and ATTACK goals have identical base priorities so the effect of the various modifiers in these goals' definitions in Goals.txt becomes significant. More on this below.

Another thing to keep in mind is what might be called the 'global sequence of all the relevent base priorities'. In the original game we've got the above GoalElements in STRATEGY_MILITARIST_DEFAULT while in STRATEGY_SEIGE we find:

    GoalElement { Goal GOAL_DEFEND              Priority   607000  MaxEval   2  MaxExec   1 PerCity }
    GoalElement { Goal GOAL_SEIGE               Priority   605000  MaxEval  25  MaxExec   2 }
    GoalElement { Goal GOAL_ATTACK              Priority   600000  MaxEval  25  MaxExec  10 }
    GoalElement { Goal GOAL_HARASS              Priority   595000  MaxEval  25  MaxExec  10 }

When the French switch to using STRATEGY_SEIGE, this data will be merged into it's current strategic state and 'overwrite' the original data from STRATEGY_MILITARIST_DEFAULT. (I'm pretty sure that the original data is chucked out and not just pushed lower down the list.) In terms of priorities the obvious change will be the increased French disposition to execute GOAL_HARASS, but it's actually more subtle than that because in STRATEGY_SEIGE these goals have different Force Matching settings from those used in STRATEGY_MILITARIST_DEFAULT.

The other obvious change in the above two strategies is the decrease in the skip values for GOAL_DEFEND. (But it's hard to see how GOAL_DEFEND interacts with the Defense strategies.)

As near as I can tell, one of the main reasons for the big jump in priorities from STRATEGY_MILITARIST_DEFAULT to STRATEGY_SEIGE is to ensure that the goals are executed using the latter's force matching settings. (Although, there's also MaxEval and MaxExec to take into account.)

You have to be very careful setting these base priorities. While testing things, I noticed that even though it was at war (and in fact using STRATEGY_SEIGE), when I went into cheat mode I could see that the AI was giving MovePathOrders to some of it's armies that were sending them off to far corners of the globe rather than attacking closer targets. The reason for this was that Hex set the GOAL_GOODY_HUT base priority in the explore strategies to 700k, which was the same as the GOAL_ATTACK base priority in his STRATEGY_SEIGE, and lower only to the GOAL_SEIGE base priority in that strategy. So if an army couldn't force match for it's GOAL_SEIGE or GOAL_ATTACK targets, it might be sent off to look for a goody hut.

But to proceed with an example, we need some data. Suppose that

i) the French know of two garrisoned English cities (London, and Birmingham) and two English armies (EA0 and EA1),
  
ii) the French have 5 armies: A1,...,A5.

Try to keep the following picture in your mind: The two English armies are near London as are the French armies except for A1, which is within striking distance of Birmingham. Then the AI's decisions about how to task these armies will be very largely determined by the following data:

                      ThreatValue   PowerValue   EnemyValue  AlliedValue
    
             London      200           200          200         0
             Birmingham   0             50           50         0
             EA0         200           200          200         0
             EA1         200           200          200         0


(Note on actual numbers. cardinality not important.)

and where d(army,target) is the "distance in number of turns the army is from the target of the goal (as the crow flies, not path length). The number of turns is an approximation that takes into account how many cells a given unit type can move each turn."

           d(A1,London)=10      d(A1,Birmingham)= 3     d(A1,EA0)= 9     d(A1,EA1)= 8
           d(A2,London)= 2      d(A2,Birmingham)= 8     d(A2,EA0)= 2     d(A2,EA1)= 3
           d(A3,London)= 2      d(A3,Birmingham)=12     d(A3,EA0)= 3     d(A3,EA1)= 2
           d(A4,London)= 1      d(A4,Birmingham)= 9     d(A4,EA0)= 2     d(A4,EA1)= 2
           d(A5,London)= 3      d(A5,Birmingham)= 9     d(A5,EA0)= 3     d(A5,EA1)= 2

   
3) BeginScheduler: The Matching Phase

"The next step is to group our forces into squads. Pre-existing squads are usually left alone, and units that have not yet been assigned a squad are grouped with other units in the same gridcell. [from Dark Reign, probably irrelevent.]

At this point the matching phase begins. Each usable squad of units is compared against any likely strategic goal. Every squad-goal pair is called a 'matching'. Each matching is assigned a numeric value [it's 'match priority'] according to a ... matching function."

In terms of events, we're at the BeginScheduler stage:

    BeginScheduler(int_t)    Match all armies with goals.

Note: I find the use of the word 'goal' somewhat confusing. I'll try to stick to the following terminology:

By a 'goal type', I mean a goal as defined in goals.txt, but without reference to any specific target. For example, GOAL_SEIGE as in goals.txt or as it reappears in strategies.txt.

By a 'goal_target', I mean a goal type applied to a possible target. I'll write this as, e.g., SEIGE(London). The word 'objective' would probably do, but I want to keep the ideas of a 'goal' and 'target' visible.

The first thing that happens is:

RM: "The priorities set in the strategies.txt file specify a base. All of the bonuses specified in the Goals.txt file are applied to this base priority to come up with what we call the goal's [goal_target's] raw priority. You'll notice that all of the computations needed to compute the bonuses refer to the goal [goal_target] and not to which particular army is executing the goal. The raw priority is used to do a first pass sort of the goals [goal_targets] which is used when applying the MaxEval."

Suppose, as per Locutus, that there are only three types of strategic goal to consider:

    GoalElement { Goal GOAL_SEIGE               Priority   405000  MaxEval  20  MaxExec   4 }
    GoalElement { Goal GOAL_ATTACK              Priority   405000  MaxEval  25  MaxExec  25 }
    GoalElement { Goal GOAL_HARASS              Priority   355000  MaxEval  25  MaxExec  25 }


So for each of the above goal types, the computer will map each possible target for that goal onto a 'raw priority':


          RawPriority(GOAL(target))= BasePriority(GOAL)+ GOAL(target).modifiers


The result is a measure of how much the French want to apply that goal to that target, e.g., how important is it for them to seige London. The complete list of these Raw Priorities represents what the AIciv would like to do BEFORE it looks at where and how strong it's own armies are.


The domain of the function (what it's possible for their units to do) is:

          SEIGE(London)
          SEIGE(Birmingham)
          ATTACK(EA0)
          ATTACK(EA1)
          HARASS(EA0)
          HARASS(EA1)

This is determined by our assumed data and the TargetType, and TargetOwner flags in the goals' definitions in goals.txt.

The raw priority value that is assigned to each of these goal_targets is determined as follows:

The following priority bonus values are added to the base priority values from the strategy to determine if the goal should be executed. Some are absolute bonuses that are applied when true while others are scaled. Scaled bonuses are given as a ratio from 0 to the value of the bonus depending on game state. For example: the target with highest threat rating would get the full ThreatBonus and all other targets would be given a percentage of the bonus based on the ratio of it’s threat rating to the highest threat rating.

     ThreatBonus - Scaled bonus given based on amount of threat  
     EnemyValueBonus - Scaled bonus given based on value of target if it’s an enemy  
     AlliedValueBonus - Scaled bonus given based on value of target if it’s an ally  
     PowerBonus - Scaled bonus given based on power level of target  
     DistanceToHomeBonus - Scaled bonus given based on target distance from center of empire  
     DistanceToEnemyBonus - Scaled bonus given based on target distance from center of enemy empire  
     ChokePointBonus - Flat bonus given if target is at a chokepoint location  
     UnexploredBonus - Flat bonus given if target is in unexplored territory  
     ObsoleteArmyBonus - Flat bonus if squad is obsolete  
     TreaspassingArmyBonus - Flat bonus given if squad is trespassing

For example, in the definition of the SEIGE goal we have

  ThreatBonus             500 // the target most threatening to us will receive this bonus.
                                  // normalized for lesser threatened targets
  EnemyValueBonus            -250 // normalized bonus given based on value of target if it’s an enemy
                                  //the negative number means that we'll attack less valuable targets first
  AlliedValueBonus      1000 // normalized bonus given based on value of target if it’s an ally
                                 
  PowerBonus        500 // the target we most threaten gets this bonus.
                                  // normalized for lesser threatened targets
  DistanceToHomeBonus         0 // actual bonus is computed by multiplying this value by the straight line 
                                  // distance from the target to the center of the player's empire (Empire Map ?)
  DistanceToEnemyBonus         0 // ditto      "   "    "    "   "    "    "   "  enemy's    "   (Enemy Empire Map ?)
  ChokePointBonus             500 // Flat bonus given if target is at a chokepoint location
  UnexploredBonus         -999999 // don't go into unexplored territory
  ObsoleteArmyBonus       100 // Flat bonus if squad is obsolete (in force matching, use our old units first?)
  TreaspassingArmyBonus         0 // Flat bonus given if squad is trespassing

In the data presented above, we assumed that both English armies were located near London, so London got the highest ThreatValue, and it will get the maximum 500 ThreatBonus. We also assummed that most of our troops are massed near it, so it has the highest PowerValue and hence the maximum 500 PowerBonus. Also it will get the maximum EnemyValueBonus and no AlliedValueBonus, so:

RawPriority(SEIGE(London))
  = BasePriority(GOAL_SEIGE)                  405000
    +GOAL_SEIGE(London).ThreatBonus          +   500
    +GOAL_SEIGE(London).PowerBonus           +   500
    +GOAL_SEIGE(London).EnemyValueBonus      +  -250
    +GOAL_SEIGE(London).AlliedValueBonus     +     0
                                           ----------
                                              405750

Since the normalized GOAL_SEIGE.ThreatBonus, GOAL_SEIGE.PowerBonus, and GOAL_SEIGE.EnemyValueBonus for Birmingham would in this case be 0 (because we're only considering two cities; if there were more cities, it would be 0<= n <500) the raw priority for seiging it would just be:

RawPriority(SEIGE(Birmingham))
  = BasePriority(GOAL_SEIGE)                      405000
    +GOAL_SEIGE(Birmingham).ThreatBonus          +     0
    +GOAL_SEIGE(Birmingham).PowerBonus           +     0
    +GOAL_SEIGE(Birmingham).EnemyValueBonus      +     0
    +GOAL_SEIGE(Birmingham).AlliedValueBonus     +     0
                                              ----------
                                                  405000

The next type of strategic goal is GOAL_ATTACK. We assumed that both English armies were in the same gridcell as London, so they'll get the full GOAL_ATTACK.ThreatBonus of 500, the full GOAL_ATTACK.PowerBonus of 500, and full GOAL_ATTACK.EnemyValueBonus of -250.  Suppose also that EA1 is on a chokepoint so that it gets the (flat) GOAL_ATTACK.ChokePointBonus of 50. Then the raw priorities for our armies to attack these English armies would be:

RawPriority(ATTACK(EA0))
    =BasePriority(GOAL_ATTACK)                405000
     +GOAL_ATTACK(EA0).ThreatBonus           +   100
     +GOAL_ATTACK(EA0).PowerBonus            +   500
     +GOAL_ATTACK(EA0).EnemyValueBonus       +  -250
     +GOAL_ATTACK(EA0).ChokePointBonus       +     0
                                          -----------
                                              405350

and

RawPriority(ATTACK(EA1))
    =BasePriority(GOAL_ATTACK)                405000
     +GOAL_ATTACK(EA1).ThreatBonus           +   100
     +GOAL_ATTACK(EA1).PowerBonus            +   500
     +GOAL_ATTACK(EA1).EnemyValueBonus       +  -250
     +GOAL_ATTACK(EA1).ChokePointBonus       +    50
                                          -----------
                                              405400

The last type of strategic goal is GOAL_HARASS. The base priority here is much lower and under the assumptions just made we'd get:

RawPriority(HARASS(EA0))
    =BasePriority(GOAL_HARASS)                355000
     +GOAL_HARASS(EA0).ThreatBonus           +   100
     +GOAL_HARASS(EA0).PowerBonus            +   500
     +GOAL_HARASS(EA0).EnemyValueBonus       +  -250
     +GOAL_HARASS(EA0).ChokePointBonus       +     0
                                          -----------
                                              355350


and

RawPriority(HARASS(EA1))
    =BasePriority(GOAL_HARASS)                355000
     +GOAL_HARASS(EA1).ThreatBonus           +   100
     +GOAL_HARASS(EA1).PowerBonus            +   500
     +GOAL_HARASS(EA1).EnemyValueBonus       +  -250
     +GOAL_HARASS(EA1).ChokePointBonus       +    50
                                          -----------
                                              355400

Once it's got all the raw priorities computed, the computer does the first pass sort Richard mentioned above. This is where the MaxEval parameter enters in. When it's making it's list of raw priorities, it only considers the top MaxEval number of goal_targets of the goal type under consideration. For example, for the SEIGE goal type, MaxEvel=20 so it will only put it's top 20 goal_targets into it's list of goal_targets that have been assigned raw priorities. This is the list that will be matched with valid armies for it's goal_targets. So, in the case of our Primary Example, putting the above all together, and doing the first pass sort, gives the following list of raw priorities:

    SEIGE(London)       405750
    ATTACK(EA1)         405400
    SEIGE(Birmingham)   405000
    ATTACK(EA0)         405350
    HARASS(EA1)         355400  
    HARASS(EA0)         355350

As mentioned above, this list of raw priorities represents what the AIciv would like to do BEFORE it looks at where and how strong it's own armies are.

Here's some calculations that follow from the same geographical assumptions made above but use the data from some of the modded versions of the game:
 
Example 2) The seige, attack, and harass goals from Cradle (MILITARIST_DEFAULT):

RawPriority(SEIGE(London))                                    RawPriority(SEIGE(Birmingham))
    = BasePriority(GOAL_SEIGE)                  705000            = BasePriority(GOAL_SEIGE)                      705000
      +GOAL_SEIGE(London).ThreatBonus          +  1500              +GOAL_SEIGE(Birmingham).ThreatBonus          +     0
      +GOAL_SEIGE(London).PowerBonus           + 70000              +GOAL_SEIGE(Birmingham).PowerBonus           +     0
      +GOAL_SEIGE(London).EnemyValueBonus      +  -250              +GOAL_SEIGE(Birmingham).EnemyValueBonus      +     0
      +GOAL_SEIGE(London).AlliedValueBonus     +     0              +GOAL_SEIGE(Birmingham).AlliedValueBonus     +     0
                                            ----------                                                        ----------
                                                775150                                                            705000
 RawPriority(ATTACK(EA0))                                    RawPriority(ATTACK(EA1))
    = BasePriority(GOAL_ATTACK)                  850000            = BasePriority(GOAL_ATTACK)                   850000
      +GOAL_ATTACK(EA0).ThreatBonus             +   500              +GOAL_ATTACK(EA1).ThreatBonus              +   500
      +GOAL_ATTACK(EA0).PowerBonus              + 10000              +GOAL_ATTACK(EA1).PowerBonus               + 10000
      +GOAL_ATTACK(EA0).EnemyValueBonus         +  -250              +GOAL_ATTACK(EA1).EnemyValueBonus          +  -250
      +GOAL_ATTACK(EA0).AlliedValueBonus        +     0              +GOAL_ATTACK(EA1).AlliedValueBonus         +     0
      +GOAL_ATTACK(EA0).ChokePointBonus         +     0              +GOAL_ATTACK(EA1).ChokePointBonus          +   250
                                             ----------                                                      ----------
                                                 860250                                                          860500
 
 RawPriority(HARASS(EA0))                                    RawPriority(HARASS(EA1))
    = BasePriority(GOAL_HARASS)                  405000            = BasePriority(GOAL_HARASS)                   405000
      +GOAL_HARASS(EA0).ThreatBonus             +   500              +GOAL_HARASS(EA1).ThreatBonus              +   500
      +GOAL_HARASS(EA0).PowerBonus              +  8500              +GOAL_HARASS(EA1).PowerBonus               +  8500
      +GOAL_HARASS(EA0).EnemyValueBonus         +  -500              +GOAL_HARASS(EA1).EnemyValueBonus          +  -500
      +GOAL_HARASS(EA0).AlliedValueBonus        +     0              +GOAL_HARASS(EA1).AlliedValueBonus         +     0
      +GOAL_HARASS(EA0).ChokePointBonus         +     0              +GOAL_HARASS(EA1).ChokePointBonus          +   550
                                             ----------                                                      ----------
                                                 413500                                                          414050

which gives us the following ranking of the AIciv's raw priorities:

    ATTACK(EA1)         860500
    ATTACK(EA0)         860250
    SEIGE(London)       775150  
    SEIGE(Birmingham)   705000
    HARASS(EA1)         414050  
    HARASS(EA0)         413500

(But these may be scewed up in Cradle by the FrenzyAI code.) Notice that here, as I mentioned above, the French have greatly increased raw priorities to attack the English armies rather than seige their cities.

Example 3) The seige, attack, and harass goals from the WW2 scenario (WW2_GERMAN_DEFAULT):

RawPriority(SEIGE(London))                                    RawPriority(SEIGE(Birmingham))
    = BasePriority(GOAL_SEIGE)                  995000            = BasePriority(GOAL_SEIGE)                      995000
      +GOAL_SEIGE(London).ThreatBonus          +   500              +GOAL_SEIGE(Birmingham).ThreatBonus          +     0
      +GOAL_SEIGE(London).PowerBonus           +   500              +GOAL_SEIGE(Birmingham).PowerBonus           +     0
      +GOAL_SEIGE(London).EnemyValueBonus      +  -250              +GOAL_SEIGE(Birmingham).EnemyValueBonus      +     0
      +GOAL_SEIGE(London).AlliedValueBonus     +     0              +GOAL_SEIGE(Birmingham).AlliedValueBonus     +     0
                                            ----------                                                        ----------
                                                995750                                                            995000
                                                                                                 
 RawPriority(ATTACK(EA0))                                    RawPriority(ATTACK(EA1))
    = BasePriority(GOAL_ATTACK)                  805000            = BasePriority(GOAL_ATTACK)                   805000
      +GOAL_ATTACK(EA0).ThreatBonus             +   100              +GOAL_ATTACK(EA1).ThreatBonus              +   100
      +GOAL_ATTACK(EA0).PowerBonus              +   500              +GOAL_ATTACK(EA1).PowerBonus               +   500
      +GOAL_ATTACK(EA0).EnemyValueBonus         +  -250              +GOAL_ATTACK(EA1).EnemyValueBonus          +  -250
      +GOAL_ATTACK(EA0).AlliedValueBonus        +     0              +GOAL_ATTACK(EA1).AlliedValueBonus         +     0
      +GOAL_ATTACK(EA0).ChokePointBonus         +     0              +GOAL_ATTACK(EA1).ChokePointBonus          +   250
                                             ----------                                                      ----------
                                                 805350                                                          805600
 
   
 RawPriority(HARASS(EA0))                                    RawPriority(HARASS(EA1))
    = BasePriority(GOAL_HARASS)                  755000            = BasePriority(GOAL_HARASS)                   755000
      +GOAL_HARASS(EA0).ThreatBonus             +   100              +GOAL_HARASS(EA1).ThreatBonus              +   100
      +GOAL_HARASS(EA0).PowerBonus              +   500              +GOAL_HARASS(EA1).PowerBonus               +   500
      +GOAL_HARASS(EA0).EnemyValueBonus         +  -500              +GOAL_HARASS(EA1).EnemyValueBonus          +  -500
      +GOAL_HARASS(EA0).AlliedValueBonus        +     0              +GOAL_HARASS(EA1).AlliedValueBonus         +     0
      +GOAL_HARASS(EA0).ChokePointBonus         +     0              +GOAL_HARASS(EA1).ChokePointBonus          +   550
                                             ----------                                                      ----------
                                                 755100                                                          755650

which gives us the following ranking of the AIciv's raw priorities:

    SEIGE(London)       995750   
    SEIGE(Birmingham)   995000
    ATTACK(EA1)         805600
    ATTACK(EA0)         805350
    HARASS(EA1)         755650  
    HARASS(EA0)         755100

Example 4) The limiting case: where all bonus's are 0.  In this case raw priority = base priority.

    SEIGE(London)       405000   
    SEIGE(Birmingham)   405000
    ATTACK(EA1)         405000
    ATTACK(EA0)         405000
    HARASS(EA1)         355000  
    HARASS(EA0)         355000
                                      
(How does it sort?)

Returning to the primary example, at this point we've determined it's raw priorities: 'what the AIciv would like to do before it looks at where and how strong it's own armies are'. Now we have to determine which of it's armies it would like to assign to it's goal_targets.

So we come to defining a matching function for the seige goal type. As indicated above, it's domain was determined by the MaxEval parameter in

     GoalElement { Goal GOAL_SEIGE               Priority   405000  MaxEval  20  MaxExec   4 }

Here the top 20 seige goal_targets from the above list will be matched with each possible valid army as determined by the SquadClass flags in the definition of GOAL_SEIGE. We're assumming we've got five such valid armies: A1,...,A5. Since we've only got 2 (< MaxEval=20) goal_targets, the possible squad-goal_target pairs (the domain of the matching function) are then

    < A1, SEIGE(London) >
    < A1, SEIGE(Birmingham >
    < A2, SEIGE(London) >
    < A2, SEIGE(Birmingham >
   
     .
     .
     .
    < A5, SEIGE(London) >
    < A5, SEIGE(Birmingham >

(If we had known about 25 English cities, then the 5 with the lowest SEIGE raw priorities would have been filtered out when the first pass sort was done. Notice also that, on the other hand, if MaxEval=1, we'd only be considering seiging London.)

The matching value is then calculated via: 

MatchingValue(army, goal_target) 
           = RawPriority(goal_target) + DistanceModifierFactor * NumberOfTurnsArmyIsFromTarget(army,target)

The DistanceModifierFactor is -50 in the DEFAULT discovery strategies and, as Locutus remarked, "the game will use some (to us unknown) algorithm to find" the NumberOfTurnsArmyIsFromTarget. Say army A1 is 10 turns away from London so that (with d(army,target)=NumberOfTurnsArmyIsFromTarget ):

MatchingValue(A1, SEIGE(London)) 
            = RawPriority(SEIGE(London))                                           = 405750
              + (DistanceModifierFactor * d(A1, London))                            + (-50*10)
                                                                                  --------------
                                                                                   = 405250

If army A2 is 2 turns away from London:

MatchingValue(A2, SEIGE(London))
            = RawPriority(SEIGE(London))                                           = 405750
              + (DistanceModifierFactor * d(A2, London))                            + (-50*2)
                                                                                  --------------
                                                                                   = 405650

Also, if army A1 is 3 turns away from Birmingham:

MatchingValue(A1, SEIGE(Birmingham)) 
            = RawPriority(SEIGE(Birmingham))                                       = 405000
              + (DistanceModifierFactor* d(A1,Birmingham))                          + (-50*3)
                                                                                  --------------
                                                                                   = 404850

(So the negative distance modifier factor means that units closer to the target are more prioritized to execute the goal. A larger  distance modifier factor, -150 as in STRATEGY_SEIGE or -250 as in STRATEGY_BARBARIAN, should mean that more armies get sucked towards their closest goal_target: notice that, above, A1 will be sent 10 turns to London rather than 3 turns to Birmingham. ?!) [Check the WW2 strategies!]

RM: "All matches for all goals are sorted into a big list and are executed from highest to lowest."

So we do the same thing for the ATTACK and HARASS goal types. This gives us the final big list:

             MatchingValue(A4, SEIGE(London))     =405950
             MatchingValue(A3, SEIGE(London))     =405900
             MatchingValue(A5, SEIGE(London))     =405850
             MatchingValue(A2, SEIGE(London))     =405650
             MatchingValue(A3, ATTACK(EA1))       =405300
             MatchingValue(A4, ATTACK(EA1))       =405300
             MatchingValue(A5, ATTACK(EA1))       =405300
             MatchingValue(A2, ATTACK(EA1))       =405250
             MatchingValue(A2, ATTACK(EA0))       =405250
             MatchingValue(A4, ATTACK(EA0))       =405250
             MatchingValue(A1, SEIGE(London))     =405250
             MatchingValue(A3, ATTACK(EA0))       =405200
             MatchingValue(A5, ATTACK(EA0))       =405200
             MatchingValue(A1, ATTACK(EA1))       =405000
             MatchingValue(A1, ATTACK(EA0))       =404900
             MatchingValue(A1, SEIGE(Birmingham)) =404850
             MatchingValue(A2, SEIGE(Birmingham)) =404600
             MatchingValue(A4, SEIGE(Birmingham)) =404550
             MatchingValue(A3, SEIGE(Birmingham)) =404400
             MatchingValue(A5, SEIGE(Birmingham)) =404350
             MatchingValue(A3, HARASS(EA1))       =355300
             MatchingValue(A4, HARASS(EA1))       =355300
             MatchingValue(A5, HARASS(EA1))       =355300
             MatchingValue(A2, HARASS(EA1))       =355250
             MatchingValue(A2, HARASS(EA0))       =355250
             MatchingValue(A4, HARASS(EA0))       =355250
             MatchingValue(A5, HARASS(EA0))       =355200
             MatchingValue(A3, HARASS(EA0))       =355200
             MatchingValue(A1, HARASS(EA1))       =355000
             MatchingValue(A1, HARASS(EA0))       =354900

This list represents what the AI would like to do, given the position of it's armies and we can go on to the ProcessMatches phase.

4) ProcessMatches:

All of the above was done during the BeginScheduler phase, it is immediately followed by the ProcessMatches event, which is described as:

ProcessMatches(int_t, int_t)    Perform one pass through match list, executing matches.

The int is the number of the pass. It's a function of Difficulty level, IIRC there's 6 passes on all levels except Impossible, where there's an extra one. For each pass, the computer goes down the list as follows:

"... Once a matching is created for each possible squad-goal pair, the computer finds the best matchings (highest matching values). It then allocates troops from the squads to the goals of the best matches until the most important current goals have enough troops committed to them to fulfill their requirements. If the system cannot allocate enough troops to fulfill a goals minimum troop requirements, it will not allocate any troops to that goal.

At the end of a matching cycle, the [system] should have a large number of goals fulfilled. There may be units from multiple squads  attached to a particular goal, so the old squads are eliminated and squads are formed out of all the units allocated to a single goal. The [system] then sends each revised squad on it's way to the goal to which it's matched." (AIP Manual)

Locutus described in some detail what happens during a pass through the list of matching values:

Now all Siege goals are executed by the armies with the highest Matching Value. The only catch is that sometimes the Force Matching numbers of this army aren't sufficient. In that case other armies need to attack that same goal as well. The number of armies that will attack is the number of armies needed to match the Force Matching numbers. So if some goal requires 40 ranged attack points to Force Match and A1 and A2 are the armies with the highest priorities for this goal and A1 has a 23 ranged attack points and A2 has 18 points, then both A1 and A2 will attack these goals since only one of the armies doesn't have the required amount of ranged attack points and thus doesn't Force Match (this is presuming all other values match as well).

Since [ MatchingValue(A4, SEIGE(London)) ] is the highest goal on the list, as many armies from the list will be selected to Siege that particular goal as needed to match the Force Matching numbers (hereby the armies with the highest Matching Values are always picked). Let's say that in this case 2 armies will be enough. In that case [A4] and [A3] will Siege [London] (since those are the two armies with the highest Matching Values ...[in the list]).

Then [RM]:
 
Remember that each match between a particular army and goal generates a match value. As the matches are executed, the army in the match is assigned to the goal. When a goal has sufficient armies assigned, then the goal is 'executed' and the armies assigned actually begin moving toward the goal.

So, armies A3 and A4 have been assigned to seige London. The next four items on the list are

             MatchingValue(A5, SEIGE(London))     =405850
             MatchingValue(A2, SEIGE(London))     =405650
             MatchingValue(A3, ATTACK(EA1))       =405300
             MatchingValue(A4, ATTACK(EA1))       =405300

The first two will be ignored because we've already successfully forced matched enough armies to seige London and the second two will be ignored because their armies have already been assigned to a goal_target. This brings up

             MatchingValue(A5, ATTACK(EA1))       =405300

Assuming that the French army A5 meets the force matching criteria for attacking the English army EA1 it will be assigned to this goal-target and the pass will continue. Again assuming that the force matching criteria are met the pass would end with the following two additional assignments:

             MatchingValue(A2, ATTACK(EA0))       =405250
             MatchingValue(A1, SEIGE(Birmingham)) =404850

This situation where every French army is assigned to a goal_target on the first pass through the list of matching values will hardly ever happen: you'd have to have strong French armies, weak English armies, and low force matching criteria.

Remark: That's as far as I've got. AFAIK, there's no proper information about how force matching works in detail so I don't know whether I'll ever be able to finish this.

  Administration
This site is currently maintained by: BureauBert (Webmaster)  Maquiladora (Manager)  Legal Notice: Legal Notice Statistics: 123530 unique users since 02.05.2004 (» Site Statistics) Hosted by: WebhostOne
Share this page:  VK Facebook Twitter LiveJournal OK MR Google+ LinkedIn tumblr Pinterest Blogger Digg Evernote