archive by month
Skip to content

IceBot has become average

Just thought I’d point this out, to remind everyone how far we’ve come.

IceBot’s elo on SSCAIT today is 2032. The rating system is designed so that the average rating is 2000, so IceBot is not significantly different from average. 30 elo points corresponds to a winning rate of 54%, not enough to notice when watching games.

IceBot was the strongest player on SSCAIT for over a year, from late 2013 to early 2015. When I ran the numbers in 2016, it was the longest-reigning champion: See 7 eras of SSCAIT champions. (Since then Krasi0 may have surpassed it.) In only a few years, we have made such progress that the one-time grandmaster with exceptional skills that other bots could barely touch now stands in the middle of a crowded field, showing as many weaknesses as strengths.

Next: Proxy, honest. These little interruptions don’t count!

tactical analysis

Today there was a short string of comments about retreating. Steamhammer makes mistakes in retreating: It retreats to poor positions, and the purpose of retreating is to disengage so it issues a move command—but it doesn’t check whether it is moving to a safe place. It often loses by retreating into or through an enemy force without fighting. It’s such a severe weakness that I think almost every Steamhammer fork has implemented some improvement to the behavior.

I am planning a simple improvement for CIG. For the following AIIDE tournament, I should have time to implement actual factual honest-to-goodness tactical analysis and make maneuver decisions properly. So here are my thoughts on what has to go into tactical analysis.

1. Distinguish different kinds of tactics. Maneuvering is one kind: Deciding when and where to move, when and where to attack, what locations need static defense or mobile defenders or scouting units, all that stuff. In maneuvering tactics, you do planning to decide on goals, and then carrying out the goals requires reacting to the situation but doesn’t require planning as such. The planning is all up front; if the bot hits a problem, the solution is to replan and set new goals. There are also what I think of as sequencing tactics, like clearing a map block or carrying out a drop or performing a coordinated attack from different directions. They require a sequence of steps. The bot has to recognize a need—clear the neutral buildings to open the path to expand to that base; select and collect resources—use lurkers because the buildings are stacked, otherwise it will take too long; plan paths; and execute steps in order—first clear the neutral buildings, then send the drone to expand. The response to problems seems more complicated than “just replan from scratch;” minor problems can be solved by assigning more resources. I don’t think there’s necessarily a sharp line between maneuvering tactics and sequencing tactics, but they do seem to call for different planning skills.

2. I am increasingly thinking that the 3 levels of analysis strategy, tactics, micro are not enough. I think I want 4 levels, which I will call strategy, operations, tactics, and micro. To a certain extent, this already shows in Steamhammer, where decisions made at the squad level sometimes look like tactics and sometimes look like micro. Retreating is a perfect example: It is a tactical decision which is made by the squad based on local tactical analysis of the situation of that squad only. Roughly speaking, high level tactics are in the combat commander, low level tactics in the squad, and micro in the squad’s micro managers. I want to reorganize it somewhat so that it is clear what level each decision is made at.

Strategy is about resources. It decides on the drone count, how much gas to collect, when to expand, and what units to make. The unit count it achieves and the unit mix it chooses have big effects on the lower levels. Strategy also includes the highest-level understanding of the game situation, like “I need to stay defensive until I’ve built up a big enough force” or “I’m ahead in economy, so an even trade is good.”

Operations is about choosing high-level tactical goals and assigning units to achieve them. Raiding squad, go hit that enemy base; blocking squad, delay any enemies from arriving there. I think that attack and retreat should be decided at this level. Sometimes it is right to retreat the raiding squad when strong enemies arrive; sometimes it is right to sacrifice the raiding squad to do as much damage as possible. The decision is best made based on the whole situation. You want to be able to reason, “sacrificing the raiders to delay the enemy a little longer means the upcoming drop is more likely to work.”

Tactics tries to achieve the operational goals. It chooses roles for each unit in a squad, or perhaps subdivides the squad into subsquads with different subgoals: Hydras engage the cannons, lurkers run past and reach the mineral line as fast as possible.

Micro is individual unit control. Where should the lurkers aim to kill as much stuff as possible?

As always, the different levels of analysis should ideally feed information to each other. See levels of abstraction.

3. How to analyze maneuver tactics efficiently? MaasCraft showed the way: Cluster units together and analyze at the level of clusters. At the operational level, it doesn’t matter exactly where individual units are. The analysis has to remain valid for at least a few seconds, and units will move around in that time. Draw a bounding box or a circle around each cluster and represent the contents of the cluster as summary measures of unit count, total hit points, speed or range of speeds, or whatever other values are useful. I believe many bots do this kind of analysis, but Steamhammer doesn’t.

With that information in hand, you can make quick estimates of when an army can arrive, how much force can be brought to bear on a given point, who wins the fight, and so on. The estimates may not be accurate, but combat simulators aren’t accurate either. You just have to be good enough to improve your decisions.

4. What should tactical analysis look like? MaasCraft uses a form of MCTS, a logical choice of search algorithm. A simpler search would already improve Steamhammer’s tactics. I expect that, with a smart enough evaluation function, a brief search that looks ahead a short time would produce strong tactical play. Surely machine learning should be able to produce an evaluator that smart. But I have ideas about how to write one by hand, and I may try that first.

Still next: Oops, somehow Proxy slipped down a slot.

on the CIG wheel

Still here. I’ve been hamstering on the codemill and not leaving myself enough time for anything else. It seems like the right thing to do, but based on history it probably isn’t. I usually go wrong when I rush for a deadline.

I want to make as much progress as possible by the CIG deadline on 10 July. And I have made several clear improvements. But my latest feature may be an overreach. After several days of work, the development version of Steamhammer lost every test game against the current release version. It will be an improvement if I can fix the problems....

Next: Proxy has improved fast.

a latent bug in squad updating

CombatCommander::updateIdleSquad() does this:

    for (const auto unit : _combatUnits)
    {
        // if it hasn't been assigned to a squad yet, put it in the low priority idle squad
        if (_squadData.canAssignUnitToSquad(unit, idleSquad))
        {
            idleSquad.addUnit(unit);
        }
    }

The code is correct as it stands. It cannot assign 1 unit to 2 squads. But I consider it a latent bug: Only the lowest-priority squad can be updated this way. It would be easy and natural to make changes to squad priorities and cause an error, or to copy and paste this code under the assumption that it would be correct in a different case. You would get an irritating bug with no obvious cause. In the spirit of defensive programming, I recommend changing it to this:

    for (const auto unit : _combatUnits)
    {
        // if it hasn't been assigned to a squad yet, put it in the low priority idle squad
        if (_squadData.canAssignUnitToSquad(unit, idleSquad))
        {
            _squadData.assignUnitToSquad(unit, idleSquad);
        }
    }

assignUnitToSquad() ensures that the unit is removed from any previous squad before it is added to the new squad. Any squad other than the lowest-priority squad might steal a unit from another squad, and you can’t steal it without taking it away. Assigning 1 unit to 2 squads would cause problems, and Steamhammer checks later and will throw.

At a minimum, I recommend commenting the code so you don’t copy-paste it heedlessly when adding a new squad type.

Randomhammer-ZurZurZur game with vulture-wraith opening

Randomhammer terran played its first public game with the vulture-wraith opening against zerg ZurZurZur. As I have mentioned, I wrote vulture-wraith originally as an extra test of Steamhammer’s anti-factory zerg opening. I found that the vultures and wraiths bring terror to unprepared zergs, so I made it a permanent part of the terran arsenal.

ZurZurZur, it turned out, was not prepared at all. It could not get a sunken colony up, and it did not get hydralisks into the fight until its natural was destroyed. By then it had lost all overlords so it could not reinforce and the hydras were quickly defeated. I can see how Tscmoo terran does so well with a similar strategy.

ZurZurZur loses its natural

I’m working on zerg stuff now for the upcoming tournaments, but I have a note to revisit this opening. It shows its legacy as a test opening; the execution and followup could be much stronger.

Next: A latent bug.

Steamhammer 1.4.4 change list

Steamhammer bugfix version 1.4.4 is uploaded to SSCAIT. It has only been a week since the last version was uploaded, so the changes are not many. The bug fixes probably mean slightly less inexplicable erratic behavior. Zerg is slightly smarter because of a few small tweaks, but it may not be easy to tell.

After rechecking the CIG publication dates, the next public release may not be until October, after AIIDE submission closes. We’ll see; there is plenty of time before then for me to change my mind.

the Bruce bugs

The bugs found by Bruce Nielsen (Locutus) and related fixes.

BuildingManager::validateWorkersAndBuildings() copied building objects unnecessarily.

BuildingManager::cancelQueuedBuildings() and BuildingManager::cancelBuildingType() unsafely deleted buildings from the _buildings vector while looping through it.

Squad::releaseWorkers() had a similar deletion bug.

MapTools::nextExpansion() had a typo that caused it to do a check with the wrong coordinates.

other code changes

UnitUtil::GetAttackRange() and UnitUtil::GetAttackRangeAssumingUpgrades() mistakenly returned the weapons range of a reaver or a carrier as 8 pixels, rather than 8 tiles (8 * 32 pixels). “We’re perfectly safe at this distance, folks!”

• I am reworking DistanceMap as the foundation of a range of classes that keep a number for each tile of the map. A new base class Grid provides the basic data structure, initialization to a constant value, and lookup. DistanceMap is renamed to GridDistances. I wrote a new class GridAttacks as a start on a kind of influence map; some code is there, but it is not yet tested or used, so exercise care.

• Removed the unused BuildingData class, and the unused method MicroManager::calcCenter().

• Removed more unnecessary includes.

zerg

• If an expansion hatchery fails to build, the building manager turns it into a macro hatchery instead. It prevents Steamhammer from sending drone after drone to expand and losing them all. It’s a low-level trick instead of the high-level tactical safety analysis that should be done, but it’s simple and it works moderately well.

• Versus protoss, count only corsairs, scouts, and carriers as reason to make devourers. Steamhammer played a game in which it made devourers to combat the enemy’s large number of observers. “I know how to beat that plan!”

• On 2 player maps, increase the chance of playing rush-safe ZvZ openings. Also rejiggered fast pool chances (suggestion by Antiga). A number of other probabilities are adjusted, mostly to make more diverse choices so that learning has data to work with.

new Steamhammer plan: win

In AIST S1, Steamhammer got to play only 5 games and lost 4 of them. It was a sad showing, though the field was strong. CIG is coming soon, and Steamhammer is registered. Then AIIDE is after that. Also, as I have mentioned, Steamhammer is unbalanced in its play skills, much stronger in strategy and macro than in tactics and micro. Looking at results in the past few months, I’ve concluded recently that it is so unbalanced that improving strategy no longer helps at all. When I fix a strategy weakness, even one which demonstrably loses games, the benefit is so small that there is no detectable change in skill, either objectively in elo or subjectively in my judgment. The imbalance is so extreme that nearly all losses, even in games affected by strategy problems, are due to mistakes in tactics and micro.

That means that if I work on the opponent model and opening selection as I planned, I won’t be able to tell if am doing a good job. No matter what I try, it will hardly affect results. It also means Steamhammer is at risk of getting whomped in CIG and AIIDE.

I have made a bunch of bug fixes recently (thanks Bruce!), so here’s my new plan. I’ll release 1.4.4 shortly with source, and to save time I’ll skip releasing the source of 1.4.3. (It takes more effort than you might imagine.) 1.4.4 includes bug fixes (one of which hasn’t been mentioned here), plus a start on an influence map for tactics, plus a zerg strategy fix that may save 1 game in 200 and a zerg tactics change that is more likely to save 1 game in 15 or 20. I think it’s all stuff that people should have.

Then the next public release will be after the CIG deadline next month (CIG releases source itself), and the following one may be after the AIIDE submission deadline in October. I will buckle down and make the changes that improve play the most in that time.

PurpleSwarm vs human show match

Two short posts today.

The human-machine show match from AIST S1 was streamed today, showing PurpleSwarm against a protoss.

On Sparkle, PurpleSwarm was able to take the crystal gas, unlike Steamhammer. Except for that, the two bots seemed to have similar skills and play styles on the map. One extra gas is a big difference, though.

2 games were on Third World. PurpleSwarm showed no sign that it knew how to mineral walk through the gates on the map. Steamhammer doesn’t know either. From past comments, it sounds like McRave is ignorant too, but Tscmoo has the skill. Does anyone know about MadMix? Just curious.

Hao Pan’s bunker rush

Hao Pan has started to play a cheese opening: Barracks in the center of the map at 6 supply, followed by a bunker at the enemy base, usually near the entrance. Many bots seem unready for the trick and react poorly. Krasi0 has been losing games. Bereaver stopped the bunker with skillful probe micro, but then forgot to make gateway units and suffered for it.

If the bunker fails, then Hao Pan’s marines will scatter and try to lure the enemy units all over the map like the Pied Piper. Most bots fall for it, including Steamhammer and forks. The game Hao Pan-Antiga is entertaining to the end.

LetaBot used to play a similar opening, though not with the strong basics of an Iron fork. There are still new cheeses to try, and old ones to improve on, and every time somebody does it, they catch unsuspecting victims. I have some cheesy ideas in mind; maybe I’ll get one ready in time for AIIDE.

new bot UITtest

New protoss UITtest is from Vietnam. The name “UIT” suggests that it may be from the University of Information Technology at Vietnam National University, Ho Chi Min City. UITtest plays a fixed opening, 1 gateway gas with exactly 4 zealots before the first dragoons. It seems like a fine bot opening. UITtest and its sibling UITtest2 both have a win rate around 50%.

UITtest is a fork of UAlbertaBot. The .dll is larger by 2K out of about 1900K, which means it was recompiled and probably has minor changes. In a quick look through the binary, I see a change in StrategyManager. UITtest2 is similar, with a .dll only 1K larger than UAlbertaBot and a different configuration. In watching games, except for the opening line I wasn’t able to see any changes from UAlbertaBot’s play.

AIST S1 results announced early

The AIST S1 results are out far ahead of schedule. I called the favorites as #1 McRave and #2 Tscmoo protoss. The winners turned out to be #1 PurpleSwarm and #2 McRave. Go zerg! But Steamhammer, the other zerg competitor, was the first to be eliminated.

AIST S1 bracket with results

We don’t get to see the games themselves until later. We do know the match results and the map order, so we can infer some of the specific game results.

The rules said that maps would be chosen randomly. I assumed that meant that the map order for each match would be chosen randomly, and games would cycle through the maps. But no, it was literal: For each game, a map was chosen at random. Some matches had repeated maps. For example, in the match McRave 2-0 Tscmoo, both games were played on Third World.

5 is an awkward number for an elimination tournament, as I said before. I expected 1 bye in the first round and 1 in the second, giving an advantage to the bye players. The bracket software instead gave 3 byes, so that the first winners round consisted of only 1 match, MadMix versus Steamhammer. These 2 players started at a disadvantage; if one of them was to come out on top of the winners bracket, it would have to win 1 more match than the 3 bye players. With 1 player in the losers bracket after 1 round, and 3 after 2 rounds, the losers bracket also needed to be split into 2 rounds, disadvantaging another player. MadMix could not be given another disadvantage, so Tscmoo got it.

The rules call for random seeding, so at least it’s fair on average—nobody has an advantage before seeding (only after). But there is no fair way to pair an elimination tournament with 5 players. Some competitors will always have an easier path than others. It’s hard to know when the byes make a difference in practice, but I notice that the 2 winners were the 2 players who were given no disadvantages.

Steamhammer bug fixes

Yesterday was a productive day for fixing bugs, thanks to info from Bruce Nielsen, author of Locutus. See comments to Steamhammer 1.4.3 change list. Because tournaments are coming up, it may be a while before I release another public version of Steamhammer, but bugfixes are important for everyone with a fork, short of the devil.

After fixing these bugs, I can no longer reproduce the “wrong reserves” error messages that the building manager used to kick out several times per game. Other mysterious misbehavior is probably fixed too, though I’m not sure what.

BuildingManager shouldn’t copy buildings

The methods BuildingManager::validateWorkersAndBuildings() and BuildingManager::checkForCompletedBuildings() loop through the vector of Building data structures and delete those that are no longer needed. To avoid deleting from the vector of Building objects while in the midst of looping through it, it stashes the buildings to delete in another vector toRemove and passes toRemove to a deletion method.

    std::vector<Building> toRemove;

Well, it’s copying the building data. I haven’t uncovered any bug that this causes, but it is wasteful at best and at worst an invitation to errors. If code updates the copies, or updates the real buildings before the copies are sent in for deletion, no real building or the wrong real building might be deleted because of how equality is defined in the Building class. undoBuildings() has to undo data structure dependencies and could easily make such a mistake.

There are several steps to the fix, but the idea is to keep a vector of references instead of a vector of copies. To keep references in a Standard Template Library container, we have to uglify the type signature with a wrapper.

    std::vector< std::reference_wrapper<Building> > toRemove;

The rest of the code doesn’t change, only the declaration. Then we also have to update the declarations of the deletion routines that undo data structure dependencies and perform the deletion.

    void undoBuildings(const std::vector< std::reference_wrapper<Building> > & toRemove);
    void removeBuildings(const std::vector< std::reference_wrapper<Building> > & toRemove);

canceling buildings is incorrect

BuildingManager::cancelQueuedBuildings() and BuildingManager::cancelBuildingType() also delete unwanted buildings, and do it while looping through the vector of buildings. It is unsafe and can cause errors. It’s my fault, I wrote these. I rewrote them like this:

// It's an emergency. Cancel all buildings which are not yet started.
void BuildingManager::cancelQueuedBuildings()
{
	std::vector< std::reference_wrapper<Building> > toCancel;

	for (Building & b : _buildings)
	{
		if (b.status == BuildingStatus::Unassigned || b.status == BuildingStatus::Assigned)
		{
			toCancel.push_back(b);
		}
	}

	for (Building & b : toCancel)
	{
		cancelBuilding(b);
	}
}

and

// It's an emergency. Cancel all buildings of a given type.
void BuildingManager::cancelBuildingType(BWAPI::UnitType t)
{
	std::vector< std::reference_wrapper<Building> > toCancel;

	for (Building & b : _buildings)
	{
		if (b.type == t)
		{
			toCancel.push_back(b);
		}
	}

	for (Building & b : toCancel)
	{
		cancelBuilding(b);
	}
}

releasing workers from a squad

I realized I was not sensitive enough to this category of bug, and surveyed the codebase to see whether there were any more. Almost all risky loops were correct, whether written by Dave Churchill or by me, but I found 2 more. One was inconsequential because it was in the BuildingData class, which is unused (so I deleted it). The other was in Squad::releaseWorkers() and should be fixed.

// Remove all workers from the squad, releasing them back to WorkerManager.
void Squad::releaseWorkers()
{
	for (auto it = _units.begin(); it != _units.end(); )
	{
		if (_combatSquad && (*it)->getType().isWorker())
		{
			WorkerManager::Instance().finishedWithWorker(*it);
			it = _units.erase(it);
		}
		else
		{
			++it;
		}
	}
}

finding the next expansion to take

Bruce also pointed out an unrelated typo in MapTools::nextExpansion(). It's a serious bug. Here is the Locutus commit fixing it.

Change this:

        for (int x = 0; x < player->getRace().getCenter().tileWidth(); ++x)
        {
			for (int y = 0; y < player->getRace().getCenter().tileHeight(); ++y)
            {
				if (BuildingPlacer::Instance().isReserved(x,y))
				{
					// This happens if we were already planning to expand here. Try somewhere else.
					buildingInTheWay = true;
					break;
				}

To this:

        for (int x = 0; x < player->getRace().getCenter().tileWidth(); ++x)
        {
			for (int y = 0; y < player->getRace().getCenter().tileHeight(); ++y)
            {
				if (BuildingPlacer::Instance().isReserved(tile.x + x, tile.y + y))
				{
					// This happens if we were already planning to expand here. Try somewhere else.
					buildingInTheWay = true;
					break;
				}

x and y were treated as absolute map coordinates instead of relative to the building location, so the check gave completely wrong results. The next check, immediately below, is correct; the bug is a typo. But the social status of the bug does not matter. What matters is that it broke stuff.

Proxy updated; Locutus vs. Krasi0

updated Proxy

New bot Proxy is—I can almost add “of course”—updated already. The obvious change is a zergling build if the opponent is zerg; the hydralisk build was too slow for ZvZ. The zergling build is successful, and Proxy’s elo has climbed over 2000. It is now an average bot, maybe better than average. That is excellent for a newcomer.

Locutus and minimum tank range

When Locutus runs dragoons by Krasi0’s bunker, Krasi0 (in games I’ve seen by the current version) has 1 siege tank available as its main interior defense. Terran sieges the tank and Locutus loses its dragoons, doing less damage than it should.

dragoons keeping their distance

In a broad sense, this is why I haven’t implemented runby in Steamhammer. Running by fixed defenses is easy, but playing well after you have run by is not so easy. The runby units become desperadoes, expecting to die and seeking to deal as much destruction and distraction as possible until then. Steamhammer’s units normally retreat from too much danger; desperadoes may be able to retreat, but can’t count on it. They have to make different decisions about what to shoot at, when to run away, and where to run to.

In this case, the important thing to shoot at is the tank, which limits the dragoons’ freedom too much. The dragoons should rush inside the sieged tank’s minimum range. They’ll win the fight and live to cause more trouble. Dragoons that wander too far off or take potshots at SCVs are not contributing as much as they could.

I think it’s complicated to handle all the defenses terran might try. I guess it’s a matter of taking one step at a time.

the ordeal of Sparkle

Mark Twain advised writers that every time you want to write “very” you should put “damn” instead: “Supporting Sparkle is damn hard.” Your editor, he said, would remove the word “damn” and your writing would be as it should. Well, I don’t have an editor. Supporting Sparkle is damn hard, and my writing is as it should be.

When I first looked at the AIST competition, I posted “I will have to spend time surviving tricky map features and won’t have much for thriving with the important play features like mineral-walking drones into the third world, or linking up island bases with nydus canals.” I was right. Steamhammer can survive on Sparkle and Third World, but it doesn’t have the skills to play the maps as they were meant to be played. I did not have the time to implement them. That’s the theme of this post: Not enough time.

the Require: feature

Since Sparkle requires specialized openings, I wanted to implement at least Require: { “map” : “Sparkle” } for openings in the configuration file. See yesterday for the many ways in which my plan was flawed. I invested too much time before I realized that it would not pay off, and I had to put in a hack and move on to other stuff. It was not fun. I’ll rethink this completely for the next version.

the opponent model

You can’t get 5 pooled on an island. You don’t have to do anything special if the enemy makes a fast factory—you expect that on an island map. The plans that Steamhammer knows how to recognize don’t matter on island maps, and I had neither time nor test opponents to work on recognizing island plans, much less to counter the plans. It made more sense to short-circuit the plan recognizer, or you might say, to correctly recognize that none of the known plans mattered.

Still, Steamhammer does know 3 Sparkle openings, and it would have been nice to rely on the opponent model’s opening selection to pick the ones that worked better. But that was problematic too, because of how the opponent model tries to generalize across maps. Most maps you can generalize across: If this opening worked well against this opponent on Circuit Breaker, it has a good chance to work on Roadrunner too. But island maps, and concept maps in general, call for specialized choices; you can’t generalize from a concept map to any other map, or vice versa. I’ll remember that when I work on opening statistics, one of the next planned features. I learned that Steamhammer should be able to measure how much a map is like other maps, and generalize appropriately.

island transport

When I surveyed the AIST S1 map pool, I thought: Hmm, Sparkle is an island map and Third World is a kind of semi-island map where workers can pass between the map halves by mineral walking. The important skills are mineral walking, drop, and placing nydus canals. What combination of skills should I aim for first? Drop is the most flexible and the most useful overall, but it will take a ton of work to implement in Steamhammer because the existing infrastructure is weak. Also drop is slow and awkward for moving units around. Mineral walking drones is comparatively easy, and it is useful on a modest variety of maps. Also mineral walking doesn’t require research; when you can use it at all, you can use it from the start of the game. Nydus canals are the best way to move units between islands in the late game, they’re useful on almost all maps, and they’re related to pathfinding which I want to work on, so now is a good time to work on nydus. Plus Sparkle provides neutral zerg buildings on islands, so zerg can create nydus connections without needing drop. Mineral walking and nydus were my choice; drop is not required for zerg to play either map.

As it turned out, I didn’t have time to implement either feature. Steamhammer is restricted to its starting island and cannot move land units to another island. Zerg will win with air power, or it will not win. On Third World, Steamhammer is restricted to the starting half of the map, which has only 5 bases including the enemy main and natural. It’s disappointing.

island strategy

Ideally, I wanted to provide an air strategy with an optional switch into a late game ground strategy of securing islands with hydralisks by nydus. As the deadline approached, I foresaw that I would not have time for nydus, so I spent a few days getting the air strategy tuned up. I optimized the 3 Sparkle openings (1 hatchery muta, 2 hatchery muta, and 3 hatchery muta) until improvements became small. Once I realized that I could not finish the Require: feature and that the opponent model had to be more or less turned off, I simply set each opening to be played 1/3 of the time. It’s as good a guess as I can make.

At first, the mutalisks were uselessly weak. To get anywhere, I had to make changes to the strategy boss to give it some understanding of island maps. First, correct the unit mix so Steamhammer sticks with air and doesn’t decide to switch to lurkers or ultralisks, which without drop or nydus have no offensive value. Even after that, Steamhammer often lost against the weak built-in AI. I continued: Reduce ground upgrades and eliminate unnecessary research to save gas for mutalisks, and get air armor upgrades. Now Steamhammer can defeat weak or unprepared opponents. Improving mutalisk and scourge micro would help more, but that would have taken way too long.

In Candide’s best of all possible worlds, meaning several versions from now, Steamhammer should support drop and have the option of following a ground strategy on an island map. Ideally.

ground units

Steamhammer still tends to make many zerglings with its leftover minerals, which it accumulates because it has too many drones for what it is doing, which is because it doesn’t understand that it is stuck on one island. Well, its economy control skills are lacking overall; it only wants to make drones up to a limit depending on the number of mineral patches and geysers it has. The zerglings are at least useful for keeping an eye on the island and stopping drop attempts.

At first, the zerglings tried to reach the enemy base, piling up at the island edge. I expected that; it is part of what MapPartitions is for. I updated the tactical analyzer (primitive as it is) to only send ground squads to places they can reach. Now the zerglings understand that they can’t get to any enemy base, so they “explore the map,” running back and forth between corners of the starting island to keep an eye on things. Incursions should be spotted right away.

There is a bug with assigning air units to a ground squad. Despite a fix in the previous version, it still happens sometimes; it must be a second bug. I’ve seen guardians assigned to the ground squad when they should not be, which on Sparkle causes this weird behavior: The squad is sent as usual to explore the map, but because it includes air units it knows it can explore the entire map. The zerglings again gather at the edge of the island, while the guardians slowly drift from empty point to empty point. The bug doesn’t cause much trouble on land maps, but on islands it’s serious.

Steamhammer still makes defensive sunkens as usual when it sees a scary enemy ground army. It doesn’t know whether the army might appear at its base.

At some point in the 1.5.x series I will make squads more dynamic. I plan to make it illegal for a squad to have ground units in more than one map partition; if a ground unit moves from one partition to another, it will be put into a different squad which belongs to that partition. Ground units in transports will belong to a different squad yet. Squads won’t have to worry about their units being stuck in separate places, and should have an easier time making decisions.

island scouting

Steamhammer’s original Recon squad is a small ground squad that scours the map for enemy bases, and when it finds one, may attack if the base is poorly defended. On Sparkle, Steamhammer only found a new enemy island base when it happened to stumble across it while doing something else. I changed the squad assignment so that on an island map, the Recon squad consists of 1 overlord. Before overlord speed finishes, the overlord moves so slowly that its recon target often times out before it gets there, switching the overlord to a new destination, but the overlord still crisscrosses the map and sees stuff that wouldn’t otherwise be seen. Once speed is done, the overlord scouts nicely. Steamhammer has no overlord survival skills, so the overlord will happily fly over turrets and generally has no fear. I’m hoping that the scouting info makes up for the vulnerability. But in test games, Steamhammer loses overlords at a high rate.

destructible blocking buildings

The low ground natural base on each starting island has a psi disruptor building on top of the geyser. Every race can build a refinery building under the psi disruptor, but none can mine the gas until the psi disruptor is destroyed. I found by experience that building an extractor before destroying the psi disruptor was a poor idea. First, the extractor apparently sticks out beyond the edges of the psi disruptor at some points, so that zerglings attacking the psi disruptor have to choose their target carefully and it takes longer to finish off the neutral building. Second, when the psi disruptor is destroyed, usually—though not always—the extractor is destroyed at the same time. I’m not sure how that happens, but it explains why I’ve always seen pros destroy the building first and take the gas after.

Yesterday I described Steamhammer’s new multi-part system to destroy the psi disruptor, and how the openings are adjusted to get the right number of zerglings at the right time to take the gas when needed. It’s complicated and it is needed only on occasional concept maps like Sparkle.

the geyser with a crystal on it

The high ground natural has a geyser with a big crystal on it. Only zerg can mine from that geyser. Steamhammer does not believe that it can take this gas, and never tries. I have a theory about why, but I haven’t looked into it; I chose to deal with the destructible buildings first, and ran out of time. It may have been the wrong decision. Maybe I should have skipped the destructible psi disruptor and figured out how to take the crystal gas.

As zerg, Steamhammer should be able to mine 3 geysers on its starting island. It is only smart enough to get 2.

taking the high ground natural

BWTA believes that the high ground natural base does not exist; it does not locate a base there for any race. Maybe it understands that the neutral zerg lair spreads creep which blocks the base placement. If so, it is a mistake: Zerg can build on the creep and mine the gas, and terran and protoss can either destroy the neutral lair and wait for the creep to dissipate, or (since they can’t mine the gas) locate a base on the opposite side of the minerals. Pros commonly take the opposite side of the minerals. To figure all this out needs a sophisticated understanding of the situation.

Steamhammer’s map analysis doesn’t understand the base either. It doesn’t realize that the neutral lair spreads creep, and tells all races they can build a base in the normal spot. Or, in some starting positions, near the normal spot; it sometimes displaces the base (maybe there’s an off-by-one bug). Since Steamhammer will be playing zerg in AIST and no other map has the weird features, it was good enough and I left it alone.

Steamhammer recognizes the neutral lair as a “blocker” that should be destroyed, but does not destroy it. I think the bug happens because of a use of BWTA that I didn’t remove. It does no harm in practice, so I left this one alone too.

I decided to do all this stuff and I’m not convinced that it was worth it. I could have concentrated on Third World instead. I would now have the mineral walking feature at a minimum, and it could share some pathfinding code with nydus canals. On the downside, I would have done less to improve Steamhammer’s play in AIST, because moving Sparkle from can’t-win to can-win is a bigger jump than moving Third World from can-win to plays-nicely. On the upside, I would have had a feature that is more widely useful. I feel some regret. I could have chosen to play somewhat better on a variety of maps, and instead chose to play substantially better on one map, Sparkle, that may never matter again. And that substantially better play is still painfully weak.