archive by month
Skip to content

AIIDE 2021 - BananaBrain versus Dragon

BananaBrain and Dragon both recorded their own opening builds for all 157 games played, so I can align their learning files and see how their strategies matched up against each other. BananaBrain also recorded its representation of what the opponent played, so I can compare its idea of Dragon’s build with Dragon’s own idea. I first did this last year. Dragon is carried over from last year unchanged, while BananaBrain is much stronger now.

The win rates and coloring are from the point of view of BananaBrain. Blue is good for BananaBrain and red is good for Dragon.

bananabrain strategies versus dragon strategies

overall1rax fe2rax bio2rax mechbiodirty worker rushmass vulturesiege expand
overall117/157 75%8/8 100%19/30 63%8/8 100%12/13 92%8/8 100%27/36 75%35/54 65%
PvT_10/12gate34/48 71%1/1 100%9/17 53%1/1 100%2/2 100%2/2 100%3/5 60%16/20 80%
PvT_1gatedtexpo0/1 0%-0/1 0%-----
PvT_28nexus3/6 50%-1/2 50%---1/1 100%1/3 33%
PvT_2gaterngexpo2/4 50%-0/1 0%---1/1 100%1/2 50%
PvT_32nexus0/1 0%------0/1 0%
PvT_9/9gate78/96 81%7/7 100%9/9 100%7/7 100%10/11 91%6/6 100%22/29 76%17/27 63%
PvT_9/9proxygate0/1 0%------0/1 0%

dragon as seen by bananabrain

dragon played#bananabrain recognized
1rax fe87 T_unknown | 1 T_fastexpand
2rax bio3030 T_unknown
2rax mech88 T_unknown
bio1313 T_unknown
dirty worker rush88 T_unknown
mass vulture3621 T_1fac | 14 T_unknown | 1 T_2fac
siege expand5438 T_1fac | 16 T_unknown

Last year this table showed that BananaBrain was weak at recognizing Dragon’s builds, with a lot of unknowns. There are more recognized builds this year, but BananaBrain plays differently so I’m not sure whether BananaBrain has improved at recognition. What is clear is that everything is blue. Recognizing some builds does not seem to have helped BananaBrain; it did well no matter what.

AIIDE 2021 - what Dragon learned

Dragon records for each game only its own build and win/loss, so the information is sparse. It has a total of 7 builds. Dragon is a carryover bot, and I analyzed its game records from AIIDE 2020 last year. Dragon considers that “the opening” is a very brief phase of the game: It quickly adapts to what it sees of the opponent’s play, and the opening build fades out of view. Last year I found that, against many opponents, the choice of opening build made little difference; the game was decided later.


#1 stardust

openinggameswinsfirstlast
1rax fe240%5156
2rax bio356%1153
2rax mech180%2146
bio205%3144
dirty worker rush190%0133
mass vulture200%4152
siege expand210%6150
7 openings1572%


It’s interesting that the only openings to make a dent were “2rax bio” and “bio”. Was Stardust surprised by marines? If Stardust made zealots to get units faster, that may have backfired.


#2 bananabrain

openinggameswinsfirstlast
1rax fe80%682
2rax bio3037%1156
2rax mech80%7109
bio138%8140
dirty worker rush80%5148
mass vulture3625%2151
siege expand5435%0145
7 openings15725%


Again, the marines were a relatively successful choice against protoss. It’s a surprise. #8 DaQin below is different. Yesterday we saw that BananaBrain liked zealot openings against Dragon, and it’s true that marines with good micro can hold their own against zealots. Maybe BananaBrain liked zealots because they upset Dragon’s tech builds, and Dragon found that marines answered best, so that the two settled into this equilibrium with neither bot able to 100% counter the other. It’s a nice story, at least.


#4 steamhammer

openinggameswinsfirstlast
1rax fe5345%8124
2rax bio714%0153
2rax mech1225%4121
bio1729%6135
dirty worker rush4740%2156
mass vulture1527%10117
siege expand60%5122
7 openings15736%


Switching between opposite builds like fast expand (“1rax fe”) and worker rush (“dirty worker rush”) is not a bad plan for defeating Steamhammer. I don’t think Dragon did it on purpose, though. Most openings scored about equal.


#5 mcrave

openinggameswinsfirstlast
1rax fe2080%34153
2rax bio4065%0109
2rax mech1765%2390
bio2462%64100
dirty worker rush20%2098
mass vulture633%63137
siege expand4672%11154
7 openings15566%


Again, most were about equal, with only a couple of exceptions. “1rax fe” was also best against last year’s McRave, even though it played rather differently.


#6 willyt

openinggameswinsfirstlast
2rax bio10%5353
mass vulture15498%0154
2 openings15597%


Last year Dragon chose “2rax mech” as its build to trample on WillyT (winning fewer, 94%, even though this year’s WillyT is substantially stronger). I think it found something that worked and felt no need to experiment any further.


#7 microwave

openinggameswinsfirstlast
1rax fe2467%1120
2rax bio2861%31152
2rax mech6674%0149
bio2662%46147
dirty worker rush20%8124
mass vulture540%12156
siege expand633%43153
7 openings15765%



#8 daqin

openinggameswinsfirstlast
1rax fe9856%8156
2rax bio617%14104
2rax mech1136%1105
bio1331%7136
dirty worker rush50%5153
mass vulture2050%9103
siege expand40%0132
7 openings15747%


Best was the fast expansion. (“1rax fe” is faster than “siege expand”.) That makes sense against DaQin’s style of play.


#9 freshmeat

openinggameswinsfirstlast
1rax fe1225%4128
2rax bio1233%18143
2rax mech2536%0141
bio1030%9139
dirty worker rush3435%7155
mass vulture4953%10156
siege expand1527%15142
7 openings15739%


Mostly about equal again. At some point I’ll look at the games and see how FreshMeat upset Dragon.


#10 ualbertabot

openinggameswinsfirstlast
1rax fe4092%116155
2rax bio667%5473
2rax mech250%114115
bio1377%3649
dirty worker rush1776%5074
mass vulture7684%0113
siege expand250%85110
7 openings15683%

AIIDE 2021 - what BananaBrain learned

Here’s my summary of BananaBrain’s learning files. BananaBrain records both its own strategy and the recognized enemy strategy for every game.

#1 stardust

openinggameswinsfirstlast
PvP_10/12gate50%9121
PvP_12nexus50%10119
PvP_2gatedt50%3120
PvP_2gatedtexpo1619%0125
PvP_2gatereaver50%1118
PvP_3gaterobo50%13123
PvP_3gatespeedzeal50%5116
PvP_4gategoon2425%12126
PvP_9/9gate50%6122
PvP_9/9proxygate3829%8156
PvP_nzcore1315%4149
PvP_zcore50%7117
PvP_zcorez911%11144
PvP_zzcore1724%2127
14 openings15717%
enemygameswins
P_1gatecore650%
P_2gate2619%
P_2gatefast1331%
P_4gategoon10714%
P_cannonturtle10%
P_unknown40%
6 openings15717%


The most successful: Double proxy gates. Stardust plays the same every game, except for reactions to its opponent, so it’s interesting that BananaBrain diagnosed so many different openings. I suspect that they were all, or nearly all, 4 gate goon, and BananaBrain was not always able to scout long enough to see it. I think the variety is what you get when BananaBrain sees only part of the build.


#3 dragon

openinggameswinsfirstlast
PvT_10/12gate4871%0156
PvT_1gatedtexpo10%1616
PvT_28nexus650%13119
PvT_2gaterngexpo450%1091
PvT_32nexus10%8989
PvT_9/9gate9681%2118
PvT_9/9proxygate10%9292
7 openings15775%
enemygameswins
T_1fac5966%
T_2fac1100%
T_fastexpand1100%
T_unknown9679%
4 openings15775%


The best builds were zealot builds. BananaBrain seems to be especially successful with early zealot pressure.


#4 steamhammer

openinggameswinsfirstlast
PvZ_10/12gate367%57
PvZ_1basespeedzeal2186%37157
PvZ_2basespeedzeal978%21149
PvZ_4gate2archon10%3131
PvZ_5gategoon250%2930
PvZ_9/9gate9288%61156
PvZ_9/9proxygate10%5757
PvZ_bisu580%3236
PvZ_neobisu1283%819
PvZ_sairdt367%146148
PvZ_sairgoon10%2020
PvZ_sairreaver367%5860
PvZ_stove580%04
13 openings15883%
enemygameswins
Z_10hatch3284%
Z_12hatch5775%
Z_12hatchmain1100%
Z_12pool2100%
Z_4/5pool1100%
Z_9pool2396%
Z_9poolspeed888%
Z_overpool1984%
Z_unknown1580%
9 openings15883%


Again, zealot builds. Steamhammer tried a wide variety of counters, of which 12 hatch worked best. BananaBrain records only the earliest steps of zerg openings, so what BananaBrain calls Z_12hatch could have a range of followups.


#5 mcrave

openinggameswinsfirstlast
PvZ_10/12gate5485%17119
PvZ_1basespeedzeal367%5860
PvZ_2basespeedzeal580%15
PvZ_4gate2archon10%6161
PvZ_5gategoon10%6666
PvZ_9/9gate10%66
PvZ_9/9proxygate875%24100
PvZ_bisu580%5357
PvZ_neobisu475%6265
PvZ_sairdt367%1416
PvZ_sairgoon1283%7105
PvZ_sairreaver10%00
PvZ_stove5988%31156
13 openings15782%
enemygameswins
Z_12hatch8481%
Z_12pool20%
Z_9pool2378%
Z_overpool4589%
Z_unknown3100%
5 openings15782%


Most things worked against McRave, but especially tech openings. The earliest steps of McRave’s openings are stereotyped, so BananaBrain recognized few choices.


#6 willyt

openinggameswinsfirstlast
PvT_10/12gate4493%750
PvT_12nexus683%05
PvT_2gatedt10%66
PvT_32nexus2488%5174
PvT_9/9proxygate7799%80156
PvT_dtdrop250%7879
PvT_stove367%7577
7 openings15793%
enemygameswins
T_1fac12100%
T_2rax5595%
T_fastexpand5288%
T_unknown3895%
4 openings15793%


The proxy gates won 76 times out of 77. Ouch.


#7 microwave

openinggameswinsfirstlast
PvZ_10/12gate8697%31156
PvZ_1basespeedzeal10%2323
PvZ_2basespeedzeal250%1920
PvZ_4gate2archon250%2425
PvZ_5gategoon3083%3979
PvZ_9/9gate1580%982
PvZ_9/9proxygate250%6364
PvZ_bisu250%78
PvZ_neobisu250%2122
PvZ_sairdt580%04
PvZ_sairgoon580%2630
PvZ_sairreaver250%56
PvZ_stove367%1618
13 openings15787%
enemygameswins
Z_10hatch8100%
Z_12hatch3197%
Z_12pool1385%
Z_4/5pool13100%
Z_9pool5879%
Z_9poolspeed6100%
Z_overpool2075%
Z_unknown888%
8 openings15787%


Zealots were best again, though dragoons were good too. I wonder why the economic 10/12 gates were more successful than the fast 9/9 gates? It suggests that Microwave may overdefend, fearing fast zealots, and not have a strong enough economy to hold off efficient zealots instead. Or the followup after the zealots; BananaBrain likes to expand quickly.


#8 daqin

openinggameswinsfirstlast
PvP_2gatedt1080%037
PvP_2gatedtexpo10%66
PvP_2gatereaver14292%7156
PvP_9/9gate367%3133
PvP_zcore10%2626
5 openings15790%
enemygameswins
P_1gatecore6988%
P_4gategoon6891%
P_ffe1100%
P_unknown1989%
4 openings15790%


DaQin was apparently not ready for reavers. Otherwise it did not badly against a powerful opponent.


#9 freshmeat

openinggameswinsfirstlast
PvZ_4gate2archon888%2633
PvZ_9/9gate122100%35156
PvZ_neobisu1486%013
PvZ_sairgoon10%3434
PvZ_stove1283%1425
5 openings15796%
enemygameswins
Z_12hatch2785%
Z_12hatchmain2291%
Z_12pool1100%
Z_4/5pool27100%
Z_9pool11100%
Z_overpool3100%
Z_unknown66100%
7 openings15796%



#10 ualbertabot

openinggameswinsfirstlast
PvU_10/12gate475%03
PvU_9/9gate10%44
PvU_9/9proxygate580%1014
PvU_nzcore580%59
PvU_zcore14297%15156
5 openings15795%
enemygameswins
P_1gatecore19100%
P_2gate1100%
P_2gatefast2584%
P_4gategoon3100%
P_unknown6100%
T_1fac1100%
T_2fac22100%
T_2rax1694%
T_unknown11100%
Z_12hatch26100%
Z_4/5pool2387%
Z_overpool3100%
Z_unknown1100%
13 openings15795%

AIIDE 2021 - Stardust table in minutes and seconds

It occurred to me a little late that many people would find the Stardust data table easier to understand if the frame times were converted to minutes and seconds. So here’s that version. See the previous post from today.

firstDarkTemplarCompleted pylonInOurMain firstMutaliskCompleted
opponent games n min median max n min median max n min median max
bananabrain 155 20 5:15 5:29 16:11 0 - - - 0 - - -
dragon 156 0 - - - 0 - - - 0 - - -
steamhammer 158 0 - - - 0 - - - 17 4:59 5:43 7:11
mcrave 157 0 - - - 0 - - - 124 6:17 7:35 11:12
willyt 157 0 - - - 0 - - - 0 - - -
microwave 157 0 - - - 0 - - - 17 5:07 5:55 7:54
daqin 156 126 5:13 5:29 12:36 2 1:53 1:54 1:55 0 - - -
freshmeat 157 0 - - - 0 - - - 1 11:40 11:40 11:40
ualbertabot 157 17 4:19 4:29 4:36 0 - - - 0 - - -

AIIDE 2021 - Stardust’s learning

I investigated how Stardust’s learning works, and what it learned. It’s unusual, so it was worth a close look.

In its learning file of game records for each opponent, Stardust records values for 3 keys for each game, firstDarkTemplarCompleted, pylonInOurMain, and firstMutaliskCompleted. If the event occurs in the game, the value is the frame time of the event; otherwise the value is 2147483647 (INT_MAX, the largest int value, in this C++ implementation). It also records whether the game was a win or a loss. It records the hash of the map, too, but that doesn’t seem to be used again.

summarizing the data

The class Opponent is responsible for providing the learned information to the rest of the bot. It summarizes the game records via two routines.

  int minValueInPreviousGames(const std::string &key, int defaultNoData, int maxCount = INT_MAX, int minCount = 0);

If there are at least minCount games, then look through the game records, most recent first, for up to maxCount games. Look up the key for each game and return its minimum value, or the default value if there are none. This amounts to finding the earliest frame at which the event happened, or the default if it did not happen in the specified number of games.

   double winLossRatio(double defaultValue, int maxCount = INT_MAX);

Look through the game records, most recent first, for up to maxCount games and return the winning ratio, or the default value if there are no games yet.

using the summarized data

Each of the 3 keys is used in exactly one place in the code. Here is where firstDarkTemplarCompleted is looked up in the PvP strategy code:

    if (Opponent::winLossRatio(0.0, 200) < 0.99)
    {
        expectedCompletionFrame = Opponent::minValueInPreviousGames("firstDarkTemplarCompleted", 7300, 15, 10);
    }

This means “If we’re rolling you absolutely flat (at least 99% wins in the last 200 games), then it doesn’t matter. Otherwise there’s some risk. In the most recent 15 games, find the earliest frame that the first enemy dark templar was (estimated to be) completed, or return frame 7300 if none.” The default frame 7300 is not the earliest a DT can emerge; they can be on the map over a thousand frames earlier. So it is not a worst-case assumption. Further code overrides the frame number if there is scouting information related to dark templar production. It attempts to build a defensive photon cannon just in time for the enemy DT’s arrival, and sometimes to get an observer.

The key pylonInOurMain is part of cannon rush defense. Stardust again checks the win ratio and again looks back 15 games with a minimum game count of 10, this time with a default of 0 if there are not enough games. It starts scouting its base 500 frames (about 21 seconds) ahead of the earliest seen enemy pylon appearing in its base, which may be never. The idea is that Stardust doesn’t waste time scouting its own base if it hasn’t seen you proxy a pylon in the last 15 games, and delays the scout if the pylon is proxied late.

The key firstMutaliskCompleted is used very similarly, to decide whether and when to defend each nexus with cannons. The goal is to get cannons in time in case mutalisks arrive without being scouted. There are simple rules to decide how many cannons at each nexus:

    // Main and natural are special cases, we only get cannons there to defend against air threats
    if (base == Map::getMyMain() || base == Map::getMyNatural())
    {
        if (enemyAirUnits > 6) return 4;
        if (enemyAirThreat) return 3;
        if (enemyDropThreat && BWAPI::Broodwar->getFrameCount() > 8000) return 1;
        return 0;
    }

    // At expansions we get cannons if the enemy is not contained or has an air threat
    if (!Strategist::isEnemyContained() || enemyAirUnits > 0) return 2;
    if (enemyAirThreat || enemyDropThreat) return 1;
    return 0;

If the firstMutaliskCompleted check says that it’s time, it sets enemyAirThreat to true and makes 3 cannons each at main and natural, and at least 1 at each other base.

the data itself

Here’s my summary of the data in Stardust’s files. The files include prepared data. I left the prepared data out; this covers only what was recorded during the tournament. The tournament was run for 157 rounds, although the official results are given after round 150. The table here is data for all 157 rounds. I don’t have a way to tell which unrecorded games were from rounds 1-150 and which were from 151-157... though I think I could guess.

n is the number of games for which a value (other than 2147483647) was recorded for the key. The values are frame numbers.

firstDarkTemplarCompleted pylonInOurMain firstMutaliskCompleted
opponent games n min median max n min median max n min median max
bananabrain 155 20 7579 7897.5 23319 0 - - - 0 - - -
dragon 156 0 - - - 0 - - - 0 - - -
steamhammer 158 0 - - - 0 - - - 17 7188 8241 10355
mcrave 157 0 - - - 0 - - - 124 9070 10939 16146
willyt 157 0 - - - 0 - - - 0 - - -
microwave 157 0 - - - 0 - - - 17 7371 8534 11397
daqin 156 126 7533 7912.5 18154 2 2721 2743.5 2766 0 - - -
freshmeat 157 0 - - - 0 - - - 1 16801 16801 16801
ualbertabot 157 17 6230 6477 6627 0 - - - 0 - - -

As you might expect after deep contemplation of the nature of reality, only protoss makes dark templar or proxy pylons, and only zerg makes mutalisks. Nothing interesting was recorded for the terran opponents.

Notice that UAlbertaBot sometimes makes dark templar much earlier than the no-data 7300 frame default time; the others do not. DaQin is recorded as twice placing a proxy pylon in Stardust’s main. I didn’t think it ever did that. I guess it’s a holdover from the Locutus proxy pylon play, to trick opponents into overreacting? DaQin made DTs in most games, and McRave went mutalisks in most games. FreshMeat is recorded as having made a mutalisk (or more than one) in exactly one game, which seems unusual.

AIIDE 2021 - the learning curves

Before I dig into what each bot learned, I thought I’d look at the win percentage over time graph. Every bot wrote data, and it is likely that every bot attempted to learn and improve over time. Only some succeeded in improving their results, though.

Every bot shows a startup transient on the graph. The early swings up and down are controlled by some combination of luck and learning; luck because there are few games so statistical variation is high, and learning if and when the learning algorithms make fast adjustments (I think they usually do). To disentangle luck from learning, I think I want both statistical tests and a look into the algorithms to see what the learning rates could be. It would be too much for one post. In this post, I’m looking at the curves after 20 or 30 rounds, when the swings have mostly leveled off. I’m answering the question: Is the bot able to keep learning throughout a long tournament, outlearning its competition in the long run?

Four bots more or less held even. There are wobbles or slight trends, but not large ones. It’s what you expect if most bots are about equally good at lifetime learning. The learning systems are more or less saturated, and when one discovers an exploit, its counterpart figures out soon enough how to neuter the exploit, or so I imagine it. The learning competition is near an equilibrium.

graph of level-ish lines

Stardust doesn’t learn much, and apparently doesn’t have to. Steamhammer and McRave have messy early curves, perhaps reflecting complicated learning systems. FreshMeat has a beautiful clean early curve, unlike any other bot’s, suggesting that it knows what it is doing and straightforwardly does it. All 3 of the lower bots show low humps followed by slight regressions. I provisionally interpret that as the bot’s learning system saturating, then its opponents adjusting to that over time.

Four bots were able to improve. BananaBrain was in a class by itself, improving far more than any other bot. WillyT, Microwave, and UAlbertaBot had slight upward trends. None of them looks as impressive as AIUR did in 2015.

graph of rising lines

What gives BananaBrain a steeper curve? Is it good at learning in the long term, or bad at learning in the short term? (See that down-hook at the beginning.) I’ll look into it later on.

Dragon and DaQin fell behind. If somebody’s going up, somebody else must be going down. It may not be a coincidence that both are carryover bots from last year. Dragon’s learning files have a simple structure, the strategy name and win/loss. DaQin plays few strategies and has few ways to escape from exploits that other bots may find.

graph of falling lines

Next: Looking at Stardust’s learning.

AIIDE 2021 - what bots wrote data?

I looked in each bot’s final write directory to see what files it wrote, if any, and in its AI directory to see if it had prepared data for any opponents. Be sure to note: A bot does not necessarily use the data it writes. Preparation for specific opponents is not necessarily in the form of data in the AI directory, it might be in code.

#botinfo
1StardustUnlike last year, this year Stardust wrote data. It’s in JSON format, and records the map by hash, win or loss, and the timings of up to 3 game events, named firstDarkTemplarCompleted, firstMutaliskCompleted, and pylonInOurMain. The times look like frame numbers, and the great majority are 2147483647 (-1 printed as unsigned), which must mean “didn’t happen”. There is prepared data for 7 opponents (including PurpleWave which did not compete), so I assume that Stardust uses the data. I’ll find out for sure when I look at the source.
2BananaBrainThe learning files look unchanged from last year and the year before: One file for each opponent in the form of brief records of results. Each record consists of date+time, map, BananaBrain’s strategy (“PvZ_9/9proxygate”), the opponent’s recognized strategy (“Z_9pool”), a floating point number which we were told last year is the game duration in minutes, and the game result. Pre-learned data for DaQin and Dragon, the two stronger carryover bots. Last year there was pre-learned data for more opponents; maybe prep for opponents that might change turned out risky.
3DragonSimple game records, one per line, with strategy and game result, like "siege expand" won.
4SteamhammerSteamhammer’s learning file format is documented here.
5McRaveThe files look to have the same information as last year, but the format is slightly different. Two files for each opponent, named like ZvU UAlbertaBot.txt and ZvU UAlbertaBot Info.txt. The first file is short and counts wins and losses overall and for each of McRave’s strategies. The info file has detailed game records with aspects of the opponent’s strategy (2Gate,Main,ZealotRush), McRave’s strategy at 3 levels of abstraction (PoolHatch,Overpool,2HatchMuta), timings, and unit counts. No prepared files.
6WillyTThe files seem to have been corrected since last year. There is one file per opponent, one line per game, with lines that look like 20211005,Z,03,0. The items look like date, opponent race, a number 01 02 or 03, and win/loss. No prepared files.
7MicrowaveResult and history files for each opponent. They look identical to last year’s, except that Microwave now lists a much larger number of strategies for itself. The result files count wins and losses for each Microwave strategy. The history files have a one-line record of data about each game. Also pre-learned history files for all opponents, each with over 100 game records.
8DaQinCarried over from last year. Learning files straight from its parent Locutus (very similar to the old format Steamhammer files). No prepared files (and they’d be out of date if they existed).
9FreshMeat Three files for each opponent, except 6 files for UAlbertaBot, presumably because it plays random. The contents of the files are opaque: Two are bare lists of numbers, one is a list of incomprehensible 14-character strings. I’ll have to read the code. No prepared files.
10UAlbertaBotCarried over from past years. For each opponent, a file listing strategies with win and loss counts for each.

The only real surprise is Stardust’s minimalist and rather weird-seeming data. FreshMeat is new, of course, so anything it did would be unsurprising! It’s notable that every single participant wrote learning data, but that’s not a surprise either because this was an elite tournament. Except for Stardust, all the elite bots have used learning for years.

In unrelated news, I expected that CoG would post replays and learning files shortly after the AIIDE submission deadline. But no, they haven’t done it yet.

AIIDE 2021 - results by map

This post is about the details of how bots performed on maps. I wrote up the map pool last year. In order across the top of each table, there are 3 maps with 2 starting positions, 2 with 3, and 5 with 4. The tables are full of information, but I’ve learned that it is hard to extract insights from the information; to find out what strengths and weaknesses the data points out, you usually have to watch the games. The value of the tables lies in telling authors what games to watch to identify weaknesses.

For reference, here’s a copy of the map table from yesterday, the summary of how well bots did overall on each map.

#botoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust95.63%96%97%97%98%90%94%98%97%94%96%
2bananabrain79.70%79%81%81%80%83%79%74%81%80%79%
3dragon51.19%50%47%52%50%50%55%56%50%50%51%
4steamhammer49.78%51%56%49%50%44%51%48%49%50%49%
5mcrave41.70%45%47%41%41%38%35%42%41%44%41%
6willyt41.05%38%39%42%36%36%51%38%43%49%40%
7microwave40.70%46%41%41%36%40%41%38%39%40%45%
8daqin39.63%41%36%42%42%45%44%39%41%31%35%
9freshmeat33.61%31%36%33%34%37%32%37%31%35%31%
10ualbertabot26.70%22%19%22%31%38%17%31%27%28%32%

Each bot gets its own table, how well it performed against each opponent on each map. Each cell represents 15 games, occasionally 14 if not all games completed, so expect noise in the numbers.

#stardustoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
2bananabrain84%93%93%80%80%67%60%100%87%93%87%
3dragon98%87%100%100%100%100%93%100%100%100%100%
4steamhammer100%100%100%100%100%100%100%100%100%100%100%
5mcrave95%100%93%93%100%100%100%87%93%100%80%
6willyt95%100%100%100%100%93%100%100%100%60%100%
7microwave100%100%100%100%100%100%100%100%100%100%100%
8daqin91%87%87%100%100%53%93%100%93%93%100%
9freshmeat99%100%100%100%100%93%100%100%100%100%100%
10ualbertabot99%93%100%100%100%100%100%93%100%100%100%
overall95.63%96%97%97%98%90%94%98%97%94%96%

A solid wall of blue, but with a few gouges. The lower results versus WillyT on Python and DaQin on Longinus probably represent weaknesses exposed by specific game events that these players tend to bring about on these maps. The weaknesses are not visible in the overall chart, only here where broken down by opponent. The weaknesses show up in only a few cells, but they might occur in many games. Maybe the opponent only happened to exploit the weaknesses then.

#bananabrainoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust16%7%7%20%20%33%40%0%13%7%13%
3dragon76%93%73%67%80%87%73%60%73%80%73%
4steamhammer83%80%87%80%100%80%80%80%87%80%80%
5mcrave83%67%80%93%80%100%80%73%80%93%80%
6willyt93%93%93%93%100%93%87%93%87%100%87%
7microwave86%87%100%80%87%87%73%93%100%67%87%
8daqin90%87%100%93%80%93%80%73%93%100%100%
9freshmeat96%100%100%100%87%87%93%100%100%93%100%
10ualbertabot95%93%93%100%87%87%100%93%100%100%93%
overall79.70%79%81%81%80%83%79%74%81%80%79%

And this is a blue wall with sharp stuff on top, staining the top course of bricks with blood.

#dragonoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust2%13%0%0%0%0%7%0%0%0%0%
2bananabrain24%7%27%33%20%13%27%40%27%20%27%
4steamhammer37%53%47%27%40%47%13%53%33%27%33%
5mcrave67%53%27%53%80%73%87%73%80%80%67%
6willyt96%93%93%100%87%100%93%93%100%100%100%
7microwave66%47%80%60%93%60%80%67%73%53%47%
8daqin47%40%40%40%27%40%47%67%33%73%60%
9freshmeat39%47%40%60%33%40%47%27%20%27%47%
10ualbertabot83%100%73%93%67%80%93%87%87%67%80%
overall51.19%50%47%52%50%50%55%56%50%50%51%

Dragon’s results, as last year, are inconsistent across maps. Again, it doesn’t show in the averages across the bottom. Actually, comparing with other bots, it doesn’t seem much different. Most had extra good and extra bad maps against some opponents.

#steamhammeroverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust0%0%0%0%0%0%0%0%0%0%0%
2bananabrain17%20%13%20%0%20%20%20%13%20%20%
3dragon63%47%53%73%60%53%87%47%67%73%67%
5mcrave54%73%60%53%47%47%73%27%60%40%60%
6willyt56%80%67%60%73%40%40%60%53%27%60%
7microwave73%80%87%67%73%73%53%67%67%93%67%
8daqin27%13%53%13%20%7%27%40%20%47%27%
9freshmeat68%60%73%67%80%67%60%93%73%60%47%
10ualbertabot92%93%100%93%100%87%100%80%93%87%93%
overall49.78%51%56%49%50%44%51%48%49%50%49%

The inconsistent results across maps may mean that bots are weak at adjusting their strategies to fit the maps. Steamhammer makes an attempt, but with 10 maps, it would take a very long tournament to gather the data to decide well. This is one of the issues that the opening timing data—the project I chose to delay—would address. It would at least help on BASIL maps, where there are enough games.

#mcraveoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust5%0%7%7%0%0%0%13%7%0%20%
2bananabrain17%33%20%7%20%0%20%27%20%7%20%
3dragon33%47%73%47%20%27%13%27%20%20%33%
4steamhammer46%27%40%47%53%53%27%73%40%60%40%
6willyt32%47%40%33%20%27%13%27%33%60%20%
7microwave60%40%47%40%67%67%67%73%73%67%60%
8daqin79%87%87%73%87%100%80%60%73%67%80%
9freshmeat65%53%47%80%60%60%60%67%60%73%93%
10ualbertabot37%73%67%40%47%7%33%13%47%40%7%
overall41.70%45%47%41%41%38%35%42%41%44%41%

As an example of the uninterpretability of the data, why did McRave do especially well against Dragon on Heartbreak Ridge? Is it because it was a 2-player map? No, the other 2-player maps Destination and Polaris Rhapsody do not agree. Was it because the map is flat, without a ramp? No, Dragon crushed it on Longinus and Empire of the Sun. Was it because of the short rush distance? I don’t think that matches McRave’s play style. It might be because Dragon makes specific mistakes in building placement or tactics, which McRave’s play is lucky enough to exploit on Heartbreak Ridge. The multiple paths through the center of the map might confuse Dragon into splitting its forces. To know for sure, we have to examine the games.

#willytoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust5%0%0%0%0%7%0%0%0%40%0%
2bananabrain7%7%7%7%0%7%13%7%13%0%13%
3dragon4%7%7%0%13%0%7%7%0%0%0%
4steamhammer44%20%33%40%27%60%60%40%47%73%40%
5mcrave68%53%60%67%80%73%87%73%67%40%80%
7microwave67%60%67%87%67%53%73%73%67%73%53%
8daqin38%40%33%40%20%33%47%27%47%53%40%
9freshmeat68%80%67%73%60%40%93%60%73%73%60%
10ualbertabot69%79%73%67%60%47%80%53%71%86%73%
overall41.05%38%39%42%36%36%51%38%43%49%40%

For bot authors, I think it’s likely to be more useful to look at weaknesses than strengths. The weaknesses with the greatest contrast with the bot’s other results against the same opponent may be worth figuring out. For WillyT, that is the 20% score versus Steamhammer on Destination, a map where the natural should be easy to defend thanks to the double bridges. The weak result might represent a systematic mistake, though of course it could also be something very specific to the map and opponent.

#microwaveoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust0%0%0%0%0%0%0%0%0%0%0%
2bananabrain14%13%0%20%13%13%27%7%0%33%13%
3dragon34%53%20%40%7%40%20%33%27%47%53%
4steamhammer27%20%13%33%27%27%47%33%33%7%33%
5mcrave40%60%53%60%33%33%33%27%27%33%40%
6willyt33%40%33%13%33%47%27%27%33%27%47%
8daqin81%87%100%67%93%80%60%67%87%67%100%
9freshmeat83%73%73%73%80%87%87%80%100%93%80%
10ualbertabot55%67%73%60%40%33%73%67%40%57%40%
overall40.70%46%41%41%36%40%41%38%39%40%45%

Strong and weak results could also be just luck, statistical fluctuations. It’s safe to promise that some seemingly meaningful numbers... aren’t, because they’re based on only 15 games.

#daqinoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust9%13%13%0%0%47%7%0%7%7%0%
2bananabrain10%13%0%7%20%7%20%27%7%0%0%
3dragon53%60%60%60%73%60%53%33%67%27%40%
4steamhammer73%87%47%87%80%93%73%60%80%53%73%
5mcrave21%13%13%27%13%0%20%40%27%33%20%
6willyt62%60%67%60%80%67%53%73%53%47%60%
7microwave19%13%0%33%7%20%40%33%13%33%0%
9freshmeat31%27%47%33%40%40%33%0%27%13%47%
10ualbertabot78%80%80%73%67%73%100%80%87%67%73%
overall39.63%41%36%42%42%45%44%39%41%31%35%

#freshmeatoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust1%0%0%0%0%7%0%0%0%0%0%
2bananabrain4%0%0%0%13%13%7%0%0%7%0%
3dragon61%53%60%40%67%60%53%73%80%73%53%
4steamhammer32%40%27%33%20%33%40%7%27%40%53%
5mcrave35%47%53%20%40%40%40%33%40%27%7%
6willyt32%20%33%27%40%60%7%40%27%27%40%
7microwave17%27%27%27%20%13%13%20%0%7%20%
8daqin69%73%53%67%60%60%67%100%73%87%53%
10ualbertabot52%21%67%80%50%43%64%57%33%47%53%
overall33.61%31%36%33%34%37%32%37%31%35%31%

#ualbertabotoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust1%7%0%0%0%0%0%7%0%0%0%
2bananabrain5%7%7%0%13%13%0%7%0%0%7%
3dragon17%0%27%7%33%20%7%13%13%33%20%
4steamhammer8%7%0%7%0%13%0%20%7%13%7%
5mcrave63%27%33%60%53%93%67%87%53%60%93%
6willyt31%21%27%33%40%53%20%47%29%14%27%
7microwave45%33%27%40%60%67%27%33%60%43%60%
8daqin22%20%20%27%33%27%0%20%13%33%27%
9freshmeat48%79%33%20%50%57%36%43%67%53%47%
overall26.70%22%19%22%31%38%17%31%27%28%32%

Next: I want to take a day to show off Steamhammer skills before I get back to AIIDE analysis.

AIIDE 2021 - summary tables

This year, for the first time ever, I did not have to update my parser to get results that exactly match the official results. Go stable tooling!

Here’s my version of the crosstable, identical to the official one except for the presentation. I have to produce the table to verify that I got it right, so I might as well show it. Also, for some people and some purposes, it’s easier to read than the original. For official results, it’s correct to use exact numbers, as is done. For general use, percentages are easier to interpret.

#botoverallstarbanadragsteamcrawillmicrdaqifresualb
1stardust95.63%84%98%100%95%95%100%91%99%99%
2bananabrain79.70%16%76%83%83%93%86%90%96%95%
3dragon51.19%2%24%37%67%96%66%47%39%83%
4steamhammer49.78%0%17%63%54%56%73%27%68%92%
5mcrave41.70%5%17%33%46%32%60%79%65%37%
6willyt41.05%5%7%4%44%68%67%38%68%69%
7microwave40.70%0%14%34%27%40%33%81%83%55%
8daqin39.63%9%10%53%73%21%62%19%31%78%
9freshmeat33.61%1%4%61%32%35%32%17%69%52%
10ualbertabot26.70%1%5%17%8%63%31%45%22%48%

And here’s my version of the bot performance per map table. I use red and blue colors, which means less trouble for people who are red-green colorblind (supposed to be 8% of men plus a few women). The official tables have a sharp color shift between red at 49% and green at 51%, which is good if you want to distinguish ahead from behind. I didn’t go to any special trouble to make perceptually accurate colors, but my color shift is pretty smooth anyway, good if you want to accentuate big differences. 49% is very pale red and 51% is very pale blue; they look nearly the same because the numbers are nearly the same. If you’re interested, compare Steamhammer’s rows in the two tables, all close to 50%.

#botoverallDestinHeartbPolariAztecLonginCircuiEmpireFightiPythonRoadki
1stardust95.63%96%97%97%98%90%94%98%97%94%96%
2bananabrain79.70%79%81%81%80%83%79%74%81%80%79%
3dragon51.19%50%47%52%50%50%55%56%50%50%51%
4steamhammer49.78%51%56%49%50%44%51%48%49%50%49%
5mcrave41.70%45%47%41%41%38%35%42%41%44%41%
6willyt41.05%38%39%42%36%36%51%38%43%49%40%
7microwave40.70%46%41%41%36%40%41%38%39%40%45%
8daqin39.63%41%36%42%42%45%44%39%41%31%35%
9freshmeat33.61%31%36%33%34%37%32%37%31%35%31%
10ualbertabot26.70%22%19%22%31%38%17%31%27%28%32%

Least but not last, the overall race balance. There is only one random bot, UAlbertaBot, and two terran bots, so the data is more sparse than usual. This table mainly tells us that the protoss participants were strong.

overallvTvPvZvR
terran46%20%57%76%
protoss72%80%74%90%
zerg41%43%26%59%
random27%24%10%41%

Finally, how each bot did against each race.

#botoverallvTvPvZvR
1stardust95.63%97%87%98%99%
2bananabrain79.70%84%53%87%95%
3dragon51.19%96%24%52%83%
4steamhammer49.78%59%14%65%92%
5mcrave41.70%32%34%57%37%
6willyt41.05%4%17%62%69%
7microwave40.70%33%32%50%55%
8daqin39.63%58%10%36%78%
9freshmeat33.61%47%25%28%52%
10ualbertabot26.70%24%10%41%-

Next: Map tables for each bot.

a first look at the AIIDE 2021 results

AIIDE 2021 results are out.

Ever since the unfortunate withdrawal of PurpleWave due to frame time issues, it was sure that protoss Stardust and BananaBrain would finish first and second—it seemed likely from the start, but without the other strong protoss it was inescapable. As it turned out, #1 Stardust was in a class by itself, scoring 96%, and #2 BananaBrain was in the following class by itself at 80%. #3 Dragon was only the best of the rest, the leader of the trailers, barely above breakeven with 51%. All others scored below even. I didn’t expect Dragon to place so high, because it was a holdover from last year and bots should have been prepared for it. I knew that #4 Steamhammer would outscore it head-to-head.

#4 Steamhammer did great at 49%. I met my goals of finishing above the middle and of murderfying #7 Microwave (73% score head-to-head). I had hoped to make third, but missed by about 1.4%. I expected to and did beat #3 Dragon, #5 McRave, and #7 Microwave, so I had some reason. I knew that Steamhammer risked a zero score against #1 Stardust—and it did happen—but the win count was going to be tiny no matter what so it wasn’t a big concern. I was worried about #6 WillyT because its big tank-infantry attacks are effective, but Steamhammer scored OK there too with 56%. Like last year, the trouble was a huge upset by carryover #8 DaQin. 2020 score 22%, 2021 score 27%—an improvement, but not by much. I had expected better.

#5 McRave scored better than the other zergs versus #1 Stardust and #2 BananaBrain, but it was not enough to move the needle. It was upset by #6 WillyT and, strangely, by #10 UAlbertaBot (last year it scored 89% against UAlbertaBot). #6 WillyT could not cope at all with #3 Dragon, and was upset by #8 DaQin too. #7 Microwave was little updated, according to the author. #9 FreshMeat, the new zerg by Hao Pan, scored 34% and was the tail ender of the submitted bots (those other than the holdovers). #10 UAlbertaBot’s upset of #5 McRave and stubborn ability to score some wins against every opponent kept it up at 27%, higher than I had anticipated. I guess UAlbertaBot will remain a usable benchmark for at least one more year.

The tournament ranks are similar to the BASIL ranks. BASIL has Stardust as the top among the AIIDE participants and BananaBrain as next. Microwave’s higher placement on BASIL is the biggest discrepancy. FreshMeat may be class B on BASIL and ranked 18 out of 86, but its BASIL rank still predicts its second-to-last finish.

This highlights that AIIDE 2021 was an elite tournament. There were few participants, and every submitted bot was already known to be highly ranked. 3 newcomer bots registered, and none was submitted. To me, it smells as though authors only want to submit if they believe they can do well. I see that as a mistake. From the author’s point of view, a tournament is a chance to gain experience, to learn about your own bot and others, and to show off your good ideas. From the community’s point of view, a tournament is an opportunity to invite new members in and to trade insights. In my experience, virtually every bot has good ideas that we can learn from. Many bots that perform poorly in games still have impressive skills in specific circumstances, not to mention other clever ideas. See for example my analysis of AITP, which scored 12% in AIIDE 2019.

Next: New bot Broken Horn. After that, stand by for more analysis of AIIDE.

AIIDE 2021 dropouts

The AIIDE 2021 list of entrants says that all 3 of the new names did not submit: Taiji, real5drone, and BlueSoup. That leaves 8 familiar names and 3 bots carried over from last year, 11 total. See AIIDE 2021 prospects.

Unfortunate but unsurprising. :-( Lately new bots have been dropping out of tournaments at a high rate. I will keep advising authors to participate if they can. Even if you think you’re not ready, it’s worth it. If your bot plays games without crashing more than occasionally, you have nothing to lose and experience to gain.

Steamhammer is ready for AIIDE 2021

Steamhammer is all set for AIIDE. I’m still making checks and running tests to be extra-duper-sure, but I’m convinced that this is the strongest and least-buggy Steamhammer ever. It hasn’t been uploaded anywhere, so nobody will be 100% ready for it... though I guess Stardust will be 99% ready. I plan to submit it today, a day ahead of time.

I’ll post the change list in a day or so, and the code after the submission deadline is safely past.

Steamhammer is frozen for AIIDE

Steamhammer is feature-frozen for AIIDE 2021, so that I don’t risk breaking my good version. Well, maybe not entirely frozen, but reduced to a low temperature (keyword simulated annealing). I will fix bugs and prepare for specific opponents, and I’ll probably also make small feature tweaks if they are safe.

My change list right now has 54 items on it, 14 of them marked as important changes that significantly improve play. With that many, obviously none of them is a big project—there hasn’t been time! But major weaknesses are fixed or reduced, affecting the whole range of strategy, tactics, and micro; all levels have important improvements. I’m pleased and optimistic. (Of course I was optimistic before AIST S4, and then Steamhammer lost 0-4, so....)

For one new feature, I ran a test that involved adding a spore colony at every base. At supply 7 (very early, before the spawning pool), Steamhammer started an evolution chamber and made a spore in the main, and then added a spore at every new base for the rest of the game. The opponent didn’t matter for the test, so I ran it against the protoss built-in AI. I was tickled that, even with the giant handicap, Steamhammer won several games in a row with apparent ease.

the sunken range bug and AIIDE 2021

In Steamhammer 3.5.1 (see the “zerg” section) I added a defense against cannon rushes which exploits the sunken range bug. The bug makes it possible, under specific conditions, for a sunken colony to target an enemy which is outside the sunken’s range. Exploiting the bug is allowed in human tournaments. In fact, it’s a standard defense against cannon rushes, one that players know and use. An example is ASL 11 Semifinal A, Mini vs Queen, game 1—see about 32 minutes into the vod for a complicated sequence where Mini eventually abandons the cannon rush knowing that it has been countered, and notice that casters Nyoken and Scan have little trouble understanding what happened and why.

At the time I wrote “Use of this bug seems to be universally legal,” but today I checked the AIIDE rules more closely. The rules include a list of allowed bugs to exploit, and add “All other bugs/exploits are forbidden.” The sunken range bug is not on the list.

I sent e-mail to Dave Churchill explaining the situation and its complexities. He’s busy and I don’t know if he’ll have time to look into it. Basically, I’m expecting to disable the behavior in Steamhammer for the tournament. I’m adding a configuration setting Config::Skills::UseSunkenRangeBug so I can turn it on and off.

Most likely no AIIDE 2021 protoss will cannon rush at all, so in a way the point is academic. But who knows?

what should the rules say?

It’s complicated!

The range bug is a game behavior, and it can happen unintentionally in real games, just because events happen to trigger its conditions. It’s fairly rare, but I expect all who play regularly have seen it (whether they recognized it or not). Bots should not be penalized for game behavior that they did not intend, and have no reason to even notice.

Steamhammer deliberately attempts to exploit the bug to beat cannon rushes. I have to interpret that as a violation of the AIIDE rules as they are written.

If you’re actively trying to enforce the rule, how would you do it? First, you’d have to examine the games, presumably with replay analysis software since there are too many to watch in person. Then you’d have to decide whether at least one instance of the bug was a deliberate exploit. That likely involves reading the code to be sure. Tournament organizers are not going to go to so much trouble, so probably the only practical enforcement would be for other authors or observers to point out possible infractions after the fact.

Then there’s the point that exploiting the bug is legal in human play, so presumably it should be legal in bot play. But that has a hidden assumption behind it: Humans can’t or don’t exploit the bug in any way that seems unfair, therefore bots won’t either. It might be true, but how sure are you? Bots with perfect timing and simultaneous view of all information might be able to exploit the bug in a way that feels unfair. Then the rules would be unfair.

Maybe it’s right to allow exploiting the range bug unless and until some bot implements an unfair exploitation.

Even if it may be a good idea to change the rules, it’s no good to change them close to the submission deadline. The rules for this year should stay put. Next year’s rules may be open to debate.

Update: I have mail from Dave Churchill. After some flip-flopping, the final ruling is “INTENTIONAL use of this bug via any specific code that invokes it is not allowed.” That follows the original rules.

AIIDE 2021 prospects

The AIIDE registration list is available today. There are 14 bots on the list, compared to 15 last year. I count 7 protoss, 5 zerg, 1 terran, and 1 random, though among the updated returning bots protoss and zerg are 3-4. Zerg seems to be gaining in popularity. (As it should, ahem. I have been ahead of the curve the whole time!)

The familiar bots, in order of their BASIL ranks today:

botauthor
StardustBruce Nielsen
BananaBrainJohan de Jong
PurpleWaveDan Gant
MicrowaveMicky Holdorf
McRaveChristian McCrave
Fresh MeatHao Pan
WillyTNico Klausner
SteamhammerJay Scott

Protoss dominance is showing, but it already cracked in CoG without the help of Monster. Terran shyness is also showing, but I notice that WillyT has improved a lot in the last year. I counted new zerg Fresh Meat as a familiar bot even though neither Fresh Meat nor its terran counterpart Halo by Hao Pan has participated in AIIDE before—Hao Pan is an old stalwart. It will be interesting to see how Fresh Meat performs in the different world of a long tournament. Steamhammer is last on this list, but I have already fixed key weaknesses. It will perform better in the tournament.

We have 3 newcomers, a fair number. Last year there were 4 newcomers, and unfortunately only EggBot ended up playing.

botauthor
Blue SoupEujain Ting
real5droneKim TaeYoung
TaijiWang Bin

Eujain Ting seems to have some experience. I found old repos related to Broodwar and BWAPI on Bitbucket: Eujain Ting repositories. And I see Eujain Ting registered for the 2011(!) AIIDE tournament without playing. The name “Blue Soup” (after the debris of a destroyed dragoon) suggests either low expectations or a sense of humor! Kim TaeYoung last year registered protoss DanDanBot (which did not play). This year, the zerg name “real5drone” suggests a 5 pool strategy, whether honestly or otherwise. Wang Bin last year registered Taij, which I’m guessing was a typo for this year’s allusive name Taiji (and which also did not play). From past experience I do not have high hopes for the newcomers, but occasionally a great one appears. And I think it’s valuable experience to participate no matter how good or bad your bot is.

Plus 3 holdovers from previous tournaments, with their win rates from last year.

botauthor
Dragon 62.38%Vegard Mella
DaQin 50.14%Lion GIS
UAlbertaBot 31.14%Dave Churchill

It’s too bad that we don’t get an updated Dragon. It’s a complicated and interesting opponent. UAlbertaBot is here for yet another year, I gather, mainly as a long-term baseline to measure progress. I forecast that it will score less than 15% against the updated bots, and this year or next it is likely to lose its value as a baseline. 2017 champion ZZZKBot scored 39.89% last year and is not being carried over—now that’s a sign of progress.