When I was but a wee lad still wet behind the ears I was actually pretty good at the Armored Core games. This is a franchise of games where you build a mech, called an Armored Core (AC), out of more than 14 categories of customizable parts with dozens of parts in each category. During those days I would dream of the ultimate build – I would spend all my time trying to optimize the build to make sure it was the fastest, most durable, highest attack power, etc… Unfortunately, to optimize it manually would be practically impossible given the number of combinations of possible parts. Fast forward 20 year later I now posses the necessary skill set to do so using the help of mathematical optimization techniques and programming. I introduce…

Binary Integer Programming

Binary Integer Programming (BIP) allows to optimize for a variable (e.g. value) for a discreet set of choices (e.g. items) given a constraint (e.g. cost). For example, you are planning a vacation and have a limited budget of $1200. You want to decide whether to go on a beach trip or a mountain trip. You have two options for each type of trip: Option A and Option B. The objective is to maximize the expected enjoyment of your vacation based on the trip rating for each option, given the budget constraint. The costs and trip rating values are as follows:

VariableOption A CostOption B CostOption A RatingOption B Rating
Beach$500$60089
Mountain$700$80076

To formulate the problem as a binary integer programming problem, we need to define the objective function and the constraint.

Objective Function: Maximize 8X1 + 9X2 + 7Y1 + 6Y2

Constraint: 500X1 + 600X2 + 700Y1 + 800Y2 ≤ 1200

Where we define the following binary variables as decision variables (1 if selected, 0 otherwise) for the option and destination:

  • X1: Option A for the beach trip
  • X2: Option B for the beach trip
  • Y1: Option A for the mountain trip
  • Y2: Option B for the mountain trip

Here the solution is clearly X1=1, X2=1, Y1=0, and Y2=0, which maximizes the objective function (=17) while still under budget ($1100 ≤ $1200).

BIP for Armored Core

In the following sections I define the optimization problem for Armored Core: Last Raven (AC:LR).

Binary Variables

AC:LR has 14 part categories (excluding optional part): Head, Core, Arm, Leg, Booster, FCS, Generator, Radiator, Inside, Extension, Back R, Back L, Arm R, Arm L. Each of these categories have some number of parts to choose from. A convenient choice is to represent each part category as a binary variable array, where the length of the array is the number of parts in that category. With some exception each part category can only have 1 part equipped. Given this constraint, an example of such a binary array could be X=[0 0 0 0 1] but it can not be X=[0 0 0 1 1] or any other additional 1, that is, when you sum up all the elements, it should be equal to 1. Mathematically, sum(X)<=1. For the following section lets define a symbol with a subscript that defines a binary array for a particular part category as: ℤpart

Requisite Constraints

As mentioned, you can only have one of each body part equipped so that the sum:

Σpartsℤpart=1

Also an AC can only be put together given the following “physical” constraints: it can not carry more weight than the carrying capacity of the chosen legs (called “max leg weight”), it can not carry heavier weapons than the carrying capacity of the arms (called “max arm weight”), and it can not equip parts that drain more energy than the energy output of its generator. We can define the constraint problem as such:

CarryWeight ≤ MaxLegWeight⋅ℤleg
ArmWeight ≤ MaxArmWeight⋅ℤcore
EnergySupply ≥ 0

where ⋅ is the dot product, ℤleg and ℤcore choose the corresponding leg and core part with their max leg weight and max arm weight capacity, respectively. CarryWeight is the weight of all parts except the legs themselves. ArmWeight is determined by the weight of the arms themselves, equipped weapons on the arms, and also the extension and inside part. EnergySupply is simply the energy output value of the generator part minus the energy drain of all equipped parts.

Constraints on Metrics

An AC build will have the following metrics: Attack, Defense, Mobility, Energy Supply, Cooling, and vs ECM, which will be ranked on a letter scale (E, D, C, B, A, S) in terms of performance given its numeric value. As an important side note: when I was putting parts together by hand, no matter what I did, I could not manage to get all characteristics to be at S!

The relation between in-game parameters (e.g. solid defense and energy defense) and which metric is affected (e.g. Defense) was for the most part straightforward, however, it took extensive data collection and analysis to figure out how the numeric value of some parameters affect the ranks so I only deduced the thresholds for rank A and S. Here is the table:

MetricParameterRank ARank S
Attack (ATK)TotalFirepower/1020,00025,000
Defense (DEF)TotalSolidDefense + TotalEnergyDefense3,8004,000
Mobility (MOB)GroundBoostSpeed410480
Energy Supply (ESUP)EnergySupply + EnergyCapacity/109,80010,400
Cooling (COL)TotalCooling + TotalForcedCooling34,00035,000
vs ECMTotalVSECM8201,000
Relationship between the metric and the in-game parameters. The minimum required parameter value for the specific rank is shown. Note that since I determined these values by hand, they may be slightly off.

The parameters are related to part stats in the following way:

  • TotalFirepower is the sum of firepower of all equipped parts where Firepower = Attack Power × Ammo Count.
  • TotalSolidDefense and TotalEnergyDefense is the sum of the solid and energy defense of all parts.
  • GroundBoostSpeed depends on two parameters with a non-linear dependence on the weight of the AC: BoostPower×(106.4/4797+0.00483) if TotalWeight ≤ 4797 else BoostPower×(106.4/TotalWeight+0.00483). BoostPower is a parameter of the booster part. TotalWeight is the sum total weight of all parts.
  • EnergySupply is the difference between EnergyOutput of the generator and the sum total of EnergyDrain of all equipped parts.
  • EnergyCapacity depends on two parameters of the radiator: CondenserCapacity+EmergencyCapacity/3.
  • TotalCooling is the radiator Cooling parameter plus the Cooling value of the AC body parts (head, core, arms, legs)
  • TotalForcedCooling is the radiator ForcedCooling parameter plus the Cooling (no forced cooling parameter) value of the AC body parts (head, core, arms, legs)
  • TotalVSECM is the sum total of the VS ECM value of all equipped parts (i.e. head and back radar)

First Result

Given the above we can now construct the optimization problem and solve it using the package cvxpy in Python. We need to define the constraint and the objective function to be maximized or minimized. First, we have the requisite constraints. Second, we want to ensure S rank for all metrics. Unfortunately, the hurdle is that the metric Mobility, which is determined by GroundBoostSpeed=BoostPower×(106.4/TotalWeight+0.00483), is a non-convex function due to the 0.00483 term and cvxpy can only handle convex functions. Luckily, the ratio BoostPower/TotalWeight is a quasi-convex function and can be solved using Disciplined Quasiconvex Programming using cvxpy. So the workaround is to choose the objective function to be maximized as BoostPower/TotalWeight, which if maximized would also maximize GroundBoostSpeed and hopefully reach the value above which we get S rank for mobility (since we can’t enforce the constraint). Running my Python code (available on GitHub) the script took about 30 seconds and produced the following build:

CategoryPart
HeadH10-CICADA2
CoreCR-C83UA
ArmYA10-LORIS
LegLH05-COUGAR
BoosterCR-B83TP
FCSCR-F75D
GeneratorKUJAKU
RadiatorCR-R92
InsideCR-I86FMM
ExtensionIWATO
Back RWB03R-SIREN
Back LCR-WB72RO2
Arm RWR04M-PIXIE2
Arm LCR-WH01HP
Optimized AC build. The optional parts O01-ANIMO, CR-O69ES, CR-O71EC, O04-GOLGI, and MARISHI are assumed equipped.

The calculated metrics are:

MetricValue (Rank)
ATK25068 (S)
DEF4004 (S)
MOB396.7 (<A)
EN SUP10613 (S)
COOLING35001.7 (S)
VS ECM1008 (S)
Optimized metric values. Mobility could not be optimized to S. Note that I included the effects of the optional parts O01-ANIMO (+10% Solid Defense), CR-O69ES (+10% Energy Defense), CR-O71EC (+8000 to EN CAP), O04-GOLGI (+6.9% energy weapon attack power) and MARISHI (+5% to total cooling by parts excluding radiator).
Optimized build

This build is not too bad, despite B rank mobility it is actually quite mobile. The biggest contributors to ATK is the rockets and floating mines – both of which are really hard to actually hit anything with.

Tuning

It is nice to see all these Ss but unfortunately we couldn’t get MOB to reach S rank. Fortunately, AC:LR has the tuning function, which allows one to optimize the equipped parts by modifying its properties slightly, such as decreasing its weight or increasing its defense stat. The limitation is that there are only 10 units of tuning per part that can be applied so it has to be redistributed among the different tuning categories strategically.

Again, using extensive data collection I figured out some relationships between the degree of tuning, an integer value between 0 and 10, and the effect it has on the stat:

Weight Tuning

I noticed there is a linear relationship (with a constant) between the initial weight of the part and the tuned weight at a given tuning value:

W_{tuned}=a(tune) \times W_{untuned} + b(tune)

where W_{untuned} and W_{tuned} is the untuned and tuned weight of the part, respectively. a(tune) and b(tune) is the slope and constant in the linear relationship that depends on the variable, tune, which is the degree of tuning, as shown in the following graphs:

Determining the dependence of the slope and constant parameter dependence on tuning, in the quadratic form y=ax^2+bx+c

The curve fits aren’t perfect but the maximum error is about one unit for the tuned parameter. We notice that the relation between tuned and tuned value is non-linear with respect to the tune value. How this is accounted for in the convex optimization will be addressed in the appendix.

Maximum Leg Weight

All tuned parameters have the same slope vs tune and constant vs tune functional relation as with weight. The slope and constant are as shown:

Determining the dependence of the slope and constant parameter dependence on tuning, in the quadratic form y=ax^2+bx+c
Solid Defense

Results as follows:

Determining the dependence of the slope and constant parameter dependence on tuning, in the quadratic form y=ax^2+bx+c
Energy Defense

Results as follows:

Determining the dependence of the slope and constant parameter dependence on tuning, in the quadratic form y=ax^2+bx+c

This was the last parameter I determined as this whole process was too time intensive. However, just the weight and defense parameters will turn out to be sufficient!

S-rank Result

After incorporating tuning into the optimization process (see appendix for non-trivial details) I managed to produce the following AC:

CategoryPart
HeadCR-H05XS-EYE3
CoreCR-C83UA
ArmYA10-LORIS
LegLN04-WALRUS2
Boosterunequipped
FCSCR-F75D
GeneratorKUJAKU
RadiatorCR-R92
InsideCR-I86FMM
ExtensionIWATO
Back RCR-WB69RO
Back LWB10R-SIREN2
Arm RWR07M-PIXIE3
Arm LYWH14PU-ROC4
Optimized AC build. The leg part is a hover part, which includes a booster. The optional parts O01-ANIMO, CR-O69ES, CR-O71EC, O04-GOLGI, and MARISHI are assumed equipped.
MetricValue (Rank)
ATK25060.8 (S)
DEF4000.4 (S)
MOB478.5 (A)
EN SUP10824 (S)
COOLING35313.3 (S)
VS ECM1016 (S)
Optimized metric values. Mobility could not be optimized to S. Note that I included the effects of the optional parts O01-ANIMO (+10% Solid Defense), CR-O69ES (+10% Energy Defense), CR-O71EC (+8000 to EN CAP), O04-GOLGI (+6.9% energy weapon attack power) and MARISHI (+5% to total cooling by parts excluding radiator).

Again I could not get S rank on MOB… However, in-game I could still tune the generator and radiator weight and I managed to get the following:

S-rank optimized build

S-rank-on-all-metrics! With weight sufficiently reduced the max boost speed (ground boost speed) increased to 498 – enough to S rank. Hilariously enough, despite the metrics, this build is quite bad and can’t boost in the air for long without overheating. In some sense, this was an exercise in proving that these metrics don’t translate to actual battle performance!

Appendix

Convex Optimization with Part Tuning

Including tuning in the optimization was not straightforward at all. As shown previously, the relation between tuned parameter and the degree of tuning is non-linear (thus non-convex) due to the quadratic dependence. Convex optimization can NOT solve this situation so I had to find a way to linearize the problem.

The solution was to consider a tuned part to be the same as a unique part in of itself. Since the tune parameter takes a discreet value between 0 and 10 it is like saying we have 11 unique parts – each with its tune adjusted values. For example, the weight value of a head part:

tune012345678910
weight514504494484477469462457452447444
Tuned weight of a head part

Since there is initial weight dependence on the final tuned value, this array has to be generated for each head part. There are 28 head parts to choose from, with the tuning, this will make it 28×11=308 “tuned” head parts to choose from. This combination is best represented as a two-dimensional matrix:

199241222143230293189228387141362199330315184202260343262514197228179241101468435145
196237218141226287186224379139355196324309181199255336257503194224176237100458426143
19323321513922228218322037213734819331830317819525133025349419122017323399450418141
19022921113721927818021736613534219031229817619224732524948518821717122998442411139
18722620813621627317821436013433718730729417319024331924547718521416922697434404137
18522320513421327017621135413233218530328917118724031524246918321116722396428398136
18322020313321026617420835013132718329928616918523731023846218120816522095422392135
18121720113220826317220634513032318129528216718323430723645717920616321795416387133
17921519913020626017020434212932017929227916618223230423345217720416221594412383132
17821419713020425816920233912831717829027716518023030123244717620216021494408380131
17621219612920325616820133612731517628827516417922829923044417520115921293405377131
Tuned weight of all the head parts. Tune value increases as descending rows from the top. Head part changes with specific column. The dark grey band is the 1st row representing the untuned value. The values in this matrix represent weight.

Lets represent the above matrix as W_{\mathrm{head}}(tune) where tune and \mathrm{head} are the row and column indices starting from 0. So W_{\mathrm{2}}(1) = 218 in the above matrix.

Note that the above procedure has to be done for each category of tuned parameter, i.e. the energy defense, shell defense, etc… So we would have S_{head}^{j,k} where j and E_{head}^{j,k} where j, which would correspond to the solid and energy defense tuning, respectively.

Since now we are working with matrices we can not use our binary arrays for part selection. We have to create a binary matrix \mathbb{Z}^{\mathrm{cat}}_{\mathrm{part}}(tune), where \mathrm{part} selects the part (head, core…), \mathrm{cat} selects the category to tune (weight, shell defense,…), and tune selects the tune value (0, 1, …). So for head part with the three tuning categories we will have \mathbb{Z}^{\mathrm{Weight}}_{\mathrm{head}}(tune), \mathbb{Z}^{\mathrm{ShellDef}}_{\mathrm{head}}(tune), and \mathbb{Z}^{\mathrm{EnergyDef}}_{\mathrm{head}}(tune). For example, if we choose the 2nd head part with a tune of 2, \mathbb{Z}^{\mathrm{Weight}}_{\mathrm{1}}(2), it will look like this:

0000
0000
0100
0
00000
Binary matrix representing the part (column) and tune value (row). The “…” represents the matrix has an indefinite size and all unspecified values are 0.

With the addition of all these new variables that are used in the optimization we have to properly define the constraints. As shown in the above example, there can only be one non-zero element (and it must be =1).

\sum \mathbb{Z}^{cat}_{part}(tune)=1

The total tune across tune categories for a part must be no larger than 10.

\sum_{cat} \sum_{tune=0}^{10} tune\times\mathbb{Z}^{cat}_{part}(tune)\leq10

Lastly, the choice of part for each tune category must be the same (e.g. we can not have two different head parts equipped simultaneously). The best way to enforce this constraint is to keep the old binary array and let our binary matrices for each category equal it by summing across the rows:

\sum_{tune} \mathbb{Z}^{cat}_{part}(tune) = \mathbb{Z}_{part}

Using the Python Script

To generate your own optimized build get the Python script from my GitHub. You will need to install the numpy, pandas, and cvxpy (version 1.3.1 or greater) packages. Lastly, you will need to get the data from the Google Sheet file “Armored Core: Last Raven (PS2+PSP) – Parts Reference Sheet by iWantYourSmiles/FenLupis” and export the sheets as .tsv files into the parent directory of this script. The files to load are:

  • ACLR_Head.tsv
  • ACLR_Core.tsv
  • ACLR_Arm.tsv
  • ACLR_Leg.tsv
  • ACLR_Booster.tsv
  • ACLR_Generator.tsv
  • ACLR_Radiator.tsv
  • ACLR_FCS.tsv
  • ACLR_Weapon.tsv
  • ACLR_Back.tsv
  • ACLR_Extension.tsv
  • ACLR_Inside.tsv

At the top of the script we have some options selected by 1 or 0 for not selected:

The script lets you choose the SCIP solver, which seems generally faster than the default but it needs to be installed. The script can exclude parts from AC:Last Raven if considering only AC:Ninebreaker. The “OP” variables enable/disable optional parts.

The objective function is controlled by (un)commenting some lines of code (only 1 can be uncommented!):

The variable names and functions are somewhat self-explanatory – cp.Maximize(totalAP) will maximize the total AP of the AC. More complicated objective functions are shown under the *dqcp objectives* where it is optimizing a ratio or a multiplication of two convex function. This produces a quasi-convex objective function, which Disciplined Quasiconvex Programming can solve.

Special constraints for ensuring a specific rank of a metric is also done by (un)commenting some lines of code:

In the above image the constraints for all metrics is S rank.

P.S.

At the onset this “fun idea” seemed very straightforward and thought it would be done fairly quickly. However, there was a lot of work to determine the functional relations due to tuning and even the metrics took some work to determine. Initially I hoped to implement everything, such as tuning of other parameters and for other parts too, but I had achieved the all-S-rank AC with what I had. So I felt I had achieved my childhood dream and I am satisfied with the work done.

As for future prospects, it may be that the all-S-rank AC I found is not the only configuration. Incorporating tuning for other parts might possibly find another configuration. Also, to make a more practical AC it might be useful to “ban” some parts from the optimization process (e.g. rockets, floating mines).

Acknowledgements

For the code used above see my GitHub below!
Also my YouTube channel for AI related projects.

Published by Code Wanderer

Coding and science

Leave a comment

Design a site like this with WordPress.com
Get started