archive by month
Skip to content

starting base? what starting base?

In 2017, I posted breaking scouting assumptions about a crazy trick terran can play: Lift off the initial command center and land it elsewhere, so that the opponent does not scout you at any starting base.

Today will go down in history, because somebody played this trick versus Steamhammer on SCHNAIL.

The game is LiberTY_ vs Steamhammer on Fighting Spirit, at 21-05-19 08:40:04 in SCHNAIL server time. Let the timestamp be forever honored in memory. Some background: Liberty (under a couple of related names) has played many practice games against Steamhammer, having some trouble at first but steadily improving and now winning nearly all games. This is someone who has learned Steamhammer’s weaknesses and has exploited them with increasing efficiency. Maybe the game was to accept a handicap and make Steamhammer a serious challenge again. Or maybe it was just a fun try to see if the bot would do something silly in the silly situation.

Liberty started in the bottom left. Here the command center is approaching to land in the bottom middle base. See the worker counts—lifting off your base interrupts mining for a long time in the critical early game, so it is a severe setback. Terran has no chance unless the trick breaks Steamhammer somehow. Steamhammer’s overlord is only halfway across the top of the map on its scouting trip, so it would have been possible to lift off later, losing less ground. A little less.

the command center nears its goal

Steamhammer scouted the bottom right base last, so it believed that was Liberty’s start base. The drone scouted it thoroughly, over and over, even after finding it empty.

the scout drone sees nothing, nothing

Wasting time with the scout drone was Steamhammer’s only real misreaction to the trick. The scout manager does not update its idea of what location needs scouting merely because the location is empty, but the information manager understands that no enemy base has been found. The initial 6 zerglings set about scouting the map for the terran buildings that must exist, checking base locations first (including bases that were already scouted). At about 5:30, a watch squad zergling arrived to keep a watch on the empty enemy “main”, and the scout drone could return home. By coincidence, at the same time, the now more numerous lings found the terran encampment and did some damage.

oops, zerglings found the terran base

This was not a scouting squad, it was a combat squad, and it was only scouting because it hadn’t found a place to attack. The lings had speed and there were only 4 marines. The game could have been over right there, but Steamhammer controlled its units poorly (in part because of crummy code trying to join clusters that became separated), and terran suffered only moderately.

But zerg had made no fatal mistakes and was too far ahead. With little room on the tiny mesa for ground production, Liberty chose wraiths. The first 2 wraiths arrived at a zerg base and were scourged before they could do damage. Soon the wraiths did better with cloak, but paper airplanes were not going to stop any zerg army for long.

Steamhammer’s reaction was about as good as I expected, and easily good enough. Does your bot survive this crazy trick? Probably! The only critical skill is the ability to notice that an enemy base is not where you “knew” it was, and you need that skill in case an enemy command center lifts off or burns down, which can happen in a normal game.

Chobo-Steamhammer game redux

It’s been a while since my last post, but I’m still working, however slowly.

In today’s SSCAIT report “By the skin of your teeth”, the last game of the broadcast is the same game I analyzed last month, Chobo-Steamhammer on Python, the corsair-reaver game, with commentary by the player himself. His analysis of the game seems similar to mine, except of course with insight into the human player’s thoughts rather than the machine’s.

interesting Chobo-Steamhammer game

The SCHNAIL web site was updated as promised, and looks much prettier. On the leaderboard, “Download” is still spelled “Dowload” though.

Today is an interesting game from SCHNAIL, Chobo (P) vs Steamhammer on Python. Chobo played corsair-reaver with mass reaver drops to destroy bases and an eventual switch into carriers, a classic strategy I have never seen used against Steamhammer. It’s a demanding strategy for both players. Protoss must be fast and aggressive with drops, never leaving its expensive forces long at home because zerg grows back fast. And zerg must cope with the overwhelming splash damage of reavers on the ground and corsairs in the air—the units may not work against Monster, but they are deadly against Steamhammer. Classic zerg play against corsair-reaver includes burrowed zerglings around the map to see the reaver drops coming, a skill that Steamhammer does not have.

Corsair-reaver depends on a heavy force of tech units, so it launches slowly. Chobo curiously did not take the natural, but blocked the ramp with 2 zealots and a dragoon while teching, then took the nearby island base—which, by the way, I think Steamhammer never discovered. Steamhammer luckily started with a 3-hatch strategy before it scouted the protoss base, so it did not fall behind right off. A one-reaver probing drop did light damage at the zerg natural, then the reaver relocated to the morphing zerg third to try to prevent it. But scourge chased the shuttle away and hydra-ling chopped the reaver. Chobo learned a little caution.

Seeing the reavers, Steamhammer elected to make mutalisks despite the corsairs. It does understand the tradeoff risk... in a vague way. Soon protoss moved down the ramp to take the natural as a third base. By this point, Steamhammer was worried by the powerful-looking protoss force and went with army over economy, starting to fall behind. Chobo tried a 2-reaver drop at the zerg third and killed the defending sunken, but the drones burrowed and protoss left rather than risk the reavers. Still, with 2 bases and another coming versus 3 undersaturated zerg bases, protoss was ahead. Chobo dropped again, again killing the replaced sunken and again retreating. Seeing the drones instantly burrow and unburrow as the reaver appeared and disappeared must have been amusing.

protoss moves into the natural

At this point I think the human player began to go wrong. Chobo made defensive cannons and moved by air to take a 4th base, dedicating reavers and adding ramp cannons for its defense. Chobo was perhaps concerned about APM and reaction speed, limited resources if you’re human. But a human can’t outmacro Steamhammer without keeping pressure on. The reavers in speed shuttles are highly mobile. If protoss wants to take another base, I think it’s correct to airlift in minimal defense and rely on the main force to fly to the rescue in case of trouble. In case of no trouble, those reavers want to be blowing stuff up, or at least threatening to. Anyway, a zerg base was morphing below the new protoss base, and the hatchery did not last long. But Steamhammer, seeing more cannons and not seeing more army, correctly concluded that it could make drones and tech up. At the time of the picture, defiler consume is researching.

plus one protoss base, minus one zerg base

Steamhammer poked the protoss main with its mutalisks, and Chobo responded by chasing the mutas back to the zerg base and eradicating them, with minor corsair losses. Steamhammer does not understand that air units can scatter and keep fleeing, it believes they have to make a last stand to defend the base. Zerg was reduced to 15 army supply versus 52 army supply for protoss.

It didn’t matter. Zerg had the economy and soon reached its drone limit of 75 (this was Steamhammer 3.4.8, not 3.5 which has a limit of 65). When the corsairs and shuttles moved toward the zerg army, they were plagued and then ensnared, so that the outnumbered mutalisks (11 corsairs with attack +2, 10 mutas with carapace +2) held the upper hand. Protoss dropped the zerg third again, this time with mass reavers and disruption web, and eradicated it. With an observer, the reavers wiped the burrowed drones too. Steamhammer countered in the protoss natural with dark swarm and zerglings and returned the favor. In the picture, Steamhammer has realized its mineral excess and is adding hatcheries to burn it off—and meanwhile, the overloaded human can’t keep up with macro (watch the APM figures).

ensnare and plague, ouchies

Carriers arrived and there was more fighting, but the strategic position was decided. Drones and hive tech beat reavers and carriers. The picture is shortly after a base full of drones was destroyed, but as soon as the drones visible in the production tab finish, Steamhammer will have re-maxed its drone count.

dark swarm everywhere

I wish the game had lasted a little longer, so that I could see how well Steamhammer’s endgame scouting and clean-up of islands works. It theoretically has the skills, but so few opponents take island bases that they are untested in real games.

An entertaining game. A lesson for zerg bots: Defiler skills count! Steamhammer needed both plague and swarm to win. A lesson for human players: Keep the pressure on! There is one regular SCHNAIL opponent for Steamhammer who seems to enjoy playing tower defense as terran: Stay alive as long as possible with bunkers and tanks and turrets. The two have played dozens of times, and Steamhammer has won every game. Defending does not keep you alive, attacking keeps you alive.

Steamhammer 3.5 change list

Steamhammer 3.5 is uploaded to SCHNAIL, and will hit SSCAIT when SSCAIT comes back up. I need to collect more opening data before I can work with it seriously; it is taking longer than I expected. But I still made Steamhammer better.

I have been feeling lazy and haven’t gotten much done, according to me. I’m not sure you’ll be able to tell from this change list, but then again, I haven’t posted to the blog recently or done any of the SCHNAIL analysis that I still want to do. It’s in accordance with my motto: All things in due time, or later.

code

Updated to VS 2019. When I upgraded to BWAPI 4.4.0, I followed the instructions, which said to use VS 2017. I didn’t realize until later that I could go further. This update was worth another 5% reduction in DLL size thanks to better optimization.

• Many files, not all of them, are reformatted with spaces instead of the grotesque mix of spaces and tabs that I’ve been living with out of indolence. Sometimes I think that the inclusion of the TAB character in ASCII in the 1960s has cost more confusion and dismay than the entire Unicode character set has since.

squad orders

• The flying squad has a slight preference to attack the enemy main over other bases. The ground squad continues to prefer to attack the natural, other things being equal.

infrastructure

The ground attack map the.groundAttacks now includes enemy sieged tanks and burrowed lurkers; formerly, it only counted attacks from static defense buildings. The idea is that units which often sit in place should be included in pathfinding for safe paths and minimum-damage paths (which are not implemented yet), while those that move around should be handled by reactive means, at least for many purposes. In the meantime, the attack map does affect other decisions, such as building placement, which will now become smarter (“doh, don’t place a building where it will be in tank range”).

UnitInfo adds a powered flag to keep track of whether a protoss building had pylon power when last seen.

• Unpowered cannons are excluded from the combat sim, using the powered flag. Steamhammer used to be afraid of them, if they happened to be out of sight so that their powerlessness was not apparent.

defense reactions

• Declare that the workers at a base are in danger if the ground attack map says that the command center/nexus/hatchery is in enemy range. This is in response to SCHNAIL games where protoss did the proxy pylon-pylon-cannon trick behind the mineral line (the pylons and minerals block zerglings from the cannon). Steamhammer pulled its workers to safety, but they remained assigned to the base and were not transferred out (they danced behind the hatchery getting in the way). When the workers at a base are in danger, Steamhammer assigns them elsewhere when it can and assigns no new workers to the base until the danger has passed. It should react better now.

• Don’t assign a worker to mine any mineral patch which is in enemy range, according to the ground attack map. It’s common for an enemy proxy to reach part of the mineral line and not the rest. Now Steamhammer will know to abandon the mineral patches that are in danger, releasing workers for elsewhere. If worker self-defense is not triggered, it will continue mining the rest.

• Cancellation of doomed unfinished buildings and units in the egg or cocoon is improved. I originally added this long ago, and it used a simple hitpoint limit because there was no infrastructure for anything better (too low on HP, cancel it). Now it is updated to use ExpectedSurvivalTime(), which adds up the damage rates of the attackers. I expect it to be more accurate at canceling things at the last second.

zerg

AbsoluteMaxWorkers is configured to 65 instead of Steamhammer’s traditional 75. I want to see how big a difference it makes. In the middle game, Steamhammer always aims for its max worker count, which tends to release pressure on the opponent until the late game—watch games and you can’t miss it. It needs the ability to do things like stop drone production at a point where the income and hatchery count are ideal for a given unit mix, and pour on the army production. This change is an experiment to gather some data and help me think about when and how to do that.

Queens are more responsive. Among other points, a queen which is in the process of casting is controlled frame by frame rather than only once every 12 (!) frames. Queens will less often blunder into fire and more often escape after casting. Queens have proved effective in games versus terran with many tanks, so it’s important.

• Defilers are a little more responsive too. The change is smaller. Improving the use of dark swarm would be more important.... Maybe this summer?

If a unit is under dark swarm and no enemy can hit it, it won’t retreat but will keep fighting, even if comrades outside the swarm run away. Units still don’t have the sense to run into dark swarm when they should, but this is a step along the way.

• Fixed: Defilers could repeatedly plague cannons, doing it again as soon as the last plague wore off. They got a constant bonus for plaguing a static defense building. Now they get a proper variable bonus depending on how many HP they expect to wipe off. There is a separate fix to prevent over-plaguing of terran buildings which are already in the red and burning down. Let terran repair it a little first.

Add sunkens or spores in the opening to hold vultures or air attacks. Due to a bug inserted last December, these defensive reactions didn’t happen until Steamhammer got out of its opening book, which could be too late.

• Don’t make scourge at all versus mass corsairs or mass battlecruisers. They’ll shoot down almost all of it, at huge cost in gas and larvas. There are disadvantages to this decision, like loss of drop defense, and I’ll relax it when scourge gets smarter.

• Battlecruisers more strongly call for a hydralisk answer.

• Tanks more strongly contraindicate lurker production. I was still seeing some lurkers even versus mass tanks.

openings

However long it may take, opening selection using data is coming. When it arrives, the advantages of having many builds will increase and the disadvantages will fade. I felt free to add as many ideas as I could come up with.

• Removed 4Scout, which is functionally identical to 5Scout, and added 7Scout 8Scout 8Overscout. 5Scout and 6Scout remain. These are fast scouting builds which leave all decisions to the strategy boss. They can be appropriate when the opponent is unpredictable but tends to play into the strategy boss’s strengths—it does happen.

• Added new 7PoolHarder 7Pool10Hatch 8PoolHard rush builds. Can’t have too many different rushes.

• Updated 8Hatch7Pool 8Hatch7PoolSpeed 8Hatch7PoolBurrow 8Hatch7PoolBurrowB to be more efficient.

Added 9PoolFastLair 9PoolFastSpire 9PoolFastSpireB 9PoolFastLurker, which get the fastest possible lair after a 9 pool. The 9PoolFastSpire variant is more suitable versus zerg, and 9PoolFastSpireB versus protoss forge expand.

Added OverpoolFastLair ZvZ_OverpoolFastMuta, which get the fastest possible lair after an overpool.

• Added Overpool14Hatch, which seems like a good opening stem versus protoss. After the spawning pool it makes 4 drones (not 3) and 2 pairs of zerglings (not 3), then the second hatchery. With appropriate followup, which doesn’t happen yet, it should be fine versus either 1 base or 2 base protoss play. If the strategy boss knew how to adapt properly, I would have marked this as a key improvement.

• Added 11Gas10PoolMutaB 11Gas10PoolLurkerB. When I first added the 11 gas 10 pool build, way back in Steamhammer 1.0, I debated whether to make 2 drones after the spawning pool and before the lair, or 3. I settled on 2, because it seemed safer in ZvZ; the third drone delays zerglings slightly. These minor variants make 3 instead.

• Added 12-11HatchStem, a 3 hatch before pool build which stops after the spawning pool finishes, without making any lings. Steamhammer already has a couple other variants, with specific continuations.

more notes on SCHNAIL

A few unrelated things I’ve noticed on SCHNAIL.

bad bots

There are a couple of bad bots that don’t start up. One is named “test”. Since the starting elo of 1500 is near the top elo for bots, and the elo falls slowly since nobody plays many games against a bot that doesn’t work, in ranked play the bad bots tend to be matched against the stronger humans. That’s unfortunate.

human habits

In practice games, most humans seem to have favorite bot opponents, and don’t experiment widely. That’s interesting, and to me a little surprising. Most pick opponents that they usually lose to. I think either they are truly practicing, or else they’re taking it as a challenge. The strongest humans don’t have bots that they usually lose to, though.

I have the impression that there have been more ranked games over time, as a proportion of all games. Maybe players are getting familiar enough to feel comfortable with it. Many humans choose practice games every time, though, which is of course a perfectly good choice.

It’s curious, but human games are more stereotyped and less varied than bot games. At least at this level on SCHNAIL. (It’s not so at the highest level of human play.) Most humans on SCHNAIL seem to play the same base strategy every game, like the bot SAIDA does, with adaptations to what they’ve scouted.

Humans quit the game before any fighting in a surprising (and irritating) proportion of games. It’s impossible to know how much it’s due to “Oops, I messed up my split, try again,” versus “I’d better take this call,” versus “ack, why’d it freeze now?” From my point of view as a bot author, these are noise games that make the data harder to interpret.

Steamhammer’s play

When Steamhammer wins, it’s not due to better strategy (it’s worse than humans), or better tactics (it’s far, far worse), or better micro (better in some aspects, but overall worse). It’s occasionally due to a rush, zerglings or lurkers breaking in by surprise, but not often; humans are better at adapting and more resilient in defending than bots. Usually it’s due to better macro: “I lost this base, I lost that base, I didn’t kill any bases of yours, but I’m still ahead in workers. Say goodnight.”

Steamhammer’s inability to foresee an enemy army’s intent is a severe weakness. “Your army is over there, near my expansion? It’s out of position, I’ll move right up in front of your natural. Wait, you’re attacking my expansion now? Run run run to defend! Oops, too late.”

In games versus terrans, it’s clear that Steamhammer is now stronger with lurkers than with mutas. Lurker play is still crude, but the recent improvements have paid off.

gg when losing

As mentioned in the post the HumanOpponent flag, Steamhammer behaves a little differently when facing a human opponent. In particular, it gives up much earlier when losing. The rule is explained in the old post.

When I wrote that surrender rule, I doubted that it was tuned well. I have been pleasantly surprised, because it works great. In actual games, Steamhammer only surrenders when it is truly losing, and the timing is almost always reasonable: Late enough that it is also clear to the opponent that Steamhammer is losing, and early enough not to waste too much human time. I wish everything worked that well!

Next: I played around a bit with the SCHNAIL api. It’s easy to extract data in JSON format. I want to analyze it a bit.

Steamhammer’s special preparation for AIST S4

AIST S4 starts tomorrow. AIST is a different style of tournament from AIIDE, and I prepared differently for Steamhammer’s specific opponents. From the competitor’s point of view, AIST is a sequence of best-of-3 matches until you lose 2 matches—or win in the best-of-5 final. Steamhammer is unlikely to face the same opponent twice. With only a few games per opponent, Steamhammer’s deep library of builds is irrelevant.

I could have tuned the learning system to work well in short matches, as PurpleWave has done. Instead, I decided it was simpler to disable learning and use Steamhammer’s classic enemy-specific opening selection, which has gathered dust ever since learning was implemented in Steamhammer 1.4. Once I decided, I completed all the prep in a matter of hours. For each of the 5 opponents, I selected between 3 and 5 openings that were different and had high winning rates (except versus Stardust, which Steamhammer almost never defeats, where I picked what I hoped might have a chance). To choose, I looked at saved learning data for Steamhammer and Randomhammer zerg on BASIL, and for Steamhammer on SSCAIT, because each instance came up with different winners thanks to random sampling. Steamhammer will choose randomly from the openings for each opponent, according to probabilities I set, which sometimes vary depending on the map size.

For those who examine the tournament games, these lists may make the builds easier to interpret.

Stardust

  • 9PoolHatchSpeedAllIn (Styx build)
  • 10Hatch
  • 3HatchLingExpo
  • 3HatchHydra
  • 2x10HatchAllIn

BananaBrain

  • 2x10HatchSlow
  • 10Hatch
  • Over10HatchBust (zergling bust)
  • 3HatchMutaPure
  • OverhatchExpoLing

PurpleWave

  • ZvZ_Overgas9Pool (one hatch muta)
  • 11HatchTurtleHydra
  • 9PoolHatchSpeedAllIn (Styx build)

Dragon

  • AntiFact_2Hatch (2 hatch muta specialized to beat factory builds)
  • AntiFactory2 (3 hatch hydra into muta)
  • 2HatchLurker
  • 3HatchLurker

WillyT

  • 9PoolLurker
  • OverpoolLurker
  • 11Gas10PoolLurker
  • 11HatchTurtleLurker
  • ZvT_3HatchMutaExpo

Steamhammer has virtually no chance to beat Stardust. I’m guessing that Steamhammer has maybe a 20%-30% chance against PurpleWave or BananaBrain, and a better than even chance against each of the terrans. Notice the lurker builds, making use of Steamhammer’s stepped-up lurker skills.

Steamhammer’s combat sim smoothing

UAlbertaBot has a simple mechanism for hysteresis in combat: After retreating, it has a time limit before it will attack again. Combat sim results are unstable, and using the raw simulation results can cause vacillation from one frame to the next, which is Not Fun. When I changed Steamhammer’s tactics to cluster units and do combat simulation separately for each cluster, I had to give up UAlbertaBot’s mechanism. The clusters have to be formed anew every frame, so they cannot carry over data from frame to frame. Ever since then, Steamhammer has had jittery tactics, often engaging and disengaging its units at computer speed. It hurt in all matchups, and to me it was most obvious in zergling on zergling micro in ZvZ games. Combat sim smoothing is my first attempt to pull myself back onto solid ground.

It seems to me that Steamhammer’s smoothing is a complicated idea, but the implementation is short and hides its internal details well. It’s a similar idea to one in PurpleWave: Instead of attaching data from previous frames to the cluster, which won’t exist in the next frame, attach it to the individual units. Then the next cluster, freshly created in the next frame, can extract the data from its own units, which are often different from the units in any older cluster.

The only alternative I see is to attach data to locations on the map instead. Both schemes rely on locality, meaning that units move around relatively slowly and affect only their immediate surroundings. Both schemes risk breaking down if units are suddenly transported, recalled by an arbiter or sent through a nydus canal. In those cases you should probably erase their saved data.

Here’s how I chose to do it. A cluster can do at most one combat sim per frame, and no unit participates in more than one cluster. For each cluster that does a combat sim, it sends the cluster’s set of units and the combat sim results (“we’ll win” or “we’ll lose”, nothing more) to Micro::setAttack(). The Micro module already keeps info about each unit’s orders and other info in a map of MicroState objects, so I thought that was the logical place. For smoothing, MicroState stores one floating point number for each unit, which is a smoothed average over time of the unit’s “we’ll win” (represented as 1.0) and “we’ll lose” (represented as 0.0) values. The algorithm is exponential smoothing, which is the next closest thing to trivial.

The cluster, having informed Micro of its latest combat sim result, then calls Micro::getSmoothedAttack() to get the smoothed combat sim result, which is what it actually relies on. Micro simply averages together the smoothed values for the cluster’s units and compares to 0.5. Done.

It’s smoothing only, there is no hysteresis. If the fight is a close call and remains so over time, Steamhammer may still oscillate between engaging and disengaging. But I think it is much rarer than before. I see the smoothing as an important improvement.

Many variations are possible. This is my first cut, and I did no testing of alternatives. Maybe it is better to smooth the actual combat sim scores, rather than only the binary win/lose results. There are ways to add hysteresis to the scheme. I’m sure improvements are waiting to be found. I may look for them.

Next: Steamhammer's special preparation for AIST S4.

Steamhammer’s pathfinding

UAlbertaBot comes with distance maps: Give a destination tile position, get back a grid with the approximate ground distance from each tile on the map to the destination, or -1 if you can’t get there from here. Since forking Steamhammer from UAlbertaBot, I’ve added to and refined the subsystem, and used it for more purposes. For example, Steamhammer uses the ground distance to decide when to transfer workers to a new base so that they arrive just after it finishes. Pathfinding is an obvious use of distance maps, so maybe it’s surprising that I didn’t implement it until now.

the implementation

The low-level implementation is simple. The move command the.micro.Move() accepts an optional distance map. If provided, it calls on a simple algorithm that searches the distance map and finds a shortest-distance path segment 8 to 12 tiles ahead of the moving unit. MoveNear() and MoveSafely() also accept the optional distance map and pass it on to Move(), which does the work.

Plumbing the distance map through the abstraction hierarchy is the hard part. I chose to allow the squad’s order (class SquadOrder) to optionally keep a distance map. Basically, at any given time a squad may either have a distance map to its target, or not. If it has a map, then it is passed along on every movement order for the squad’s units. CombatCommander decides on the squad orders and generally gives a distance map to ground squads, and none to air squads which can fly straight to their destinations. Most squad targets are bases, and it happens that Steamhammer precalculates a distance map for each base. So a squad order to target a base uses the base’s distance map for free, while a squad order to target an arbitrary location calculates a new distance map which belongs directly to the squad order. I’ve been planning that detail of the implementation for years.

Pathfinding is used for squads with ground units, for the scouting worker, and for sending a worker to construct a new base. It is not used for constructing other buildings (which are usually nearby though not always) or for transferring workers between bases.

If the debug flag to draw squad information is turned on, a squad which has a distance map is listed with a “#” symbol before its name. The “#” represents the grid of distances.

results

Pathfinding works well in test games, though not perfectly. The paths are not true shortest paths (see “disadvantages” below), but close enough for now. In test games on Medusa, units of the Ground, Recon, and Watch squads navigate well, no longer getting stuck in the dead end back doors of the bases. On the other hand, I commonly saw a small number of transferred drones stuck there. One oversight is that some units still tend to get stuck behind the neutral buildings on Benzene. Benzene is not in the AIST S4 map pool, so I didn’t notice.

I also need to update the precalculated maps when map obstacles are destroyed. I’m sure I’ll iron out the details in time. It didn’t seem vital for now.

advantages

Once a distance map is calculated, it’s cheap to follow not only a shortest path for one unit, but any shortest path for all units. If you like one shortest path better than another for any reason, you can choose it on the fly.

A distance map need not be calculated from a single destination tile. It can start from any set of destination tiles, so that you can do things like find the shortest path to enter a region, or choose the closest of 2 equal goals. A distance map need not record literal distance; it can pretend that a dangerous tile is larger than others so that you tend to path around it when you can. Distance maps of different kinds have many potential uses.

  • Ground unit pathing through terrain, the implemented feature.
  • Ground or air unit safe paths, to avoid all known risk for scouting, or to discover containments.
  • Ground or air unit least-danger paths, to reach a defended target with minimal damage.
  • Ground or air paths which are out of sight range of known enemies, for sneaking around.
  • Paths to safe firing zones, so we can attack a defended target from outside its defences.
  • Maintaining a contant distance from an objective, such as the convex hull of a group of enemy units, for arcs and surrounds.

disadvantages

As implemented, the distances mapped are not true unit movement distances, but Manhattan distances. Units moving diagonally, say from bottom right to top left, tend to move not diagonally, but first to the left and then upward. I intend to correct it, but haven’t decided on a plan yet.

Distance maps are a bit bulky and slow, and usually involve calculating a lot of paths that you won’t go anywhere near, so you don’t want to calculate too many maps each frame. It’s better to reuse them as much as possible, which adds complexity. UAlbertaBot comes with a distance map cache, but its replacement policy is “periodically throw out all entries in case some may be unused.” Something a little more sophisticated is called for, especially if you want to store different kinds of distance maps in the same cache.

If you want to map something that changes frequently, like detailed enemy firing zones, you can either recalculate the map frequently—which is fine up to a point—or accept that it will often be a little outdated—also usually fine.

The disadvantages don’t seem severe to me. Distance maps gain their flexibility by doing more calculation than is necessary for any one use case—guess what, work harder, earn more. It does call for a little attention to efficiency.

Next: Combat sim smoothing.

Steamhammer 3.4.8 for AIST S4

I have submitted Steamhammer 3.4.8 to AIST S4. Here is the change list, nice and long considering how long there was to work on it. The deadline isn’t for hours yet, so revealing my Dark Secrets early might theoretically come back to bite me, but... eh, it won’t bite hard. I’m still concealing my specific preparation for these opponents.

I tried for important advances that wouldn’t take long. I think I met my promise that Steamhammer will play a more “lucid” game, but let’s see how many fresh bugs I didn’t notice! The biggest improvements are pathfinding for ground units, smoothing of combat sim results, and more supple lurker behavior. My intention has always, ever since I started Steamhammer in December 2016, been to work on mutalisk micro before lurker micro, because it is more important. But I let it pass for too long, and now other bots have strong muta micro. I’ll get back to mutas (they are still more important), but I always want to do something different from everybody else, and now I want to work on lurkers first.

The irradiated squad is a nice improvement, too. Anyway, read on.

map analysis

• Reject starting bases (as identified by BWAPI::Broodwar->getStartLocations()) which have no minerals; they are no longer considered bases, much less possible starting bases. I think these are always observer slots. The AIST S4 version of Aztec has observer slots. Formerly, Steamhammer accepted whatever BWAPI told it and created a Base object for each one, marked as a starting base. The error had surprisingly little effect on play (Steamhammer is not tempted to expand to valueless “bases”), but it did cause sneaky mistakes. One is that a starting base is assigned a natural if conditions are met, with the result that the center bases on Aztec were considered naturals of some of the observer slots, and that information could be used for certain decisions, like where to make a hidden base. I’m glad I found this by looking at the maps, because the mistaken decisions might have been a nightmare to diagnose!

scouting

• The scout worker is released whenever friendly combat units are near. This is the final relaxation of the originally tight conditions for releasing the scout worker early, which I’ve been progressively relaxing.

• If the enemy has no known anti-air units, send an overlord to each base on the map to keep watch. Formerly, the rule was “if the enemy is not known to have tech to make anti-air units”, which was rarely satisfied outside the early game when there weren’t overlords available to distribute.

squads

An irradiated squad centralizes the code for managing irradiated units and implements fancier behavior. Every irradiated organic unit (the ones harmed by the irradiate spell) goes into the squad, except for queens which have enough energy to cast and defilers after consume is researched—they will try to cast something before dying. Formerly, an irradiated unit that could burrow would, and that was the extent of the reaction (the successful reaction, anyway; mutas had code to separate out an irradiated muta, but it mysteriously broke). Now, an irradiated unit that is near friendly units will burrow if it can, or try to flee from its friends if it cannot. If it is near enemy units, it will approach to expose them to the radiation, and also attack if it can. If neither, it will run and do what scouting it can. Some of the behavior looks questionable, and I suspect bugs, but it’s good enough for now. The reaction of unirradiated units to nearby irradiated units is unchanged—only workers try to protect themselves, others carry on as usual.

• A newly-created squad did not pass along its order to its own micromanagers until it was updated. It was an oversight inherited from UAlbertaBot. The main effect is that a defense squad would not begin to act until exactly 8 frames after it was created, so defense was always a little bit late.

• The debug display for squads now shows two new pieces of information: A # symbol if the squad is using pathfinding, and the squad’s status string. The status string is sometimes unchanging, sometimes informative. For example, the Scourge squad has status Attack or Stand By, depending on whether enemy flyers are targetable.

• There was a minor bug in deciding which enemy unit last-seen location to visit next after all known enemy buildings are destroyed. The fix has little effect.

other ops and tactics

Smooth attack/retreat combat sim decisions over time. I think this is the single most important change. Micro is less jittery and more decisive. I’m leaving out details for today; it’s worth a separate post.

• I found and fixed several more bugs related to spell units. The most important misbehavior was that Steamhammer treated an enemy comsat scan of a base as an attack that needed to be fended off. Every time terran scanned a base, a couple of mutalisks might peel off the flock and head there to defeat the scan. They never failed!

• I added time hysteresis to the defense squads, on top of their existing range hysteresis. After the enemies have been seen off, the squad waits out a time limit before it is disbanded. I was not convinced that the feature helps play, so I set the time limit to only 1 second.

• When a cluster of units is ordered to retreat, it may decide—depending on a simplified geometry calculation involving a risk radius—to “retreat forward” to join with friends. This helps a small cluster join up with a big cluster that is already in a fight. I decreased the risk radius, except in the case where the enemy is terran and has sieged tanks. It should help small clusters retreat forward more often.

• Earlier, I introduced a bug into FAP by adding an incorrect MAX_DISTANCE constant. I had forgotten that the distances are squared. Fixed.

micro

Pathfinding for ground units for calls to the.micro.Move(), the.micro.MoveNear(), and the.micro.MoveSafely(), (but not the.micro.AttackMove()) at the option of the caller. I’ll post details tomorrow or so. It was easy to guess I would do this now, because Medusa is in the AIST S4 map pool, where Starcraft’s built-in pathfinder likes to pile up units at the blocked back doors of the bases.

• A unit trying to move safely (with the.micro.MoveSafely()) does not try to avoid interceptors, only carriers. Trying to avoid swooping interceptors causes erratic movements, not escape movements.

zerg

Lurker behavior is smarter. When I first implemented lurkers in Steamhammer 1.3 in 2017, I found that if they obeyed the combat sim like other squad units, they were nearly useless; they did not understand when to burrow, or where to burrow, or when to unburrow. The SparCraft combat simulator I had at the time did not support lurkers. Steamhammer could not tell when the enemy had detected them. So I gave lurkers spartan hyper-aggressive tactics: Always attack, burrow at max range versus a target that can shoot back and directly next to a target that cannot, and unburrow when no target is in range. I’ve made only minor refinements since, because it worked surprisingly well, especially against terran bots. But the crudeness shows, and hyper-aggressive is often hyper-stupid. A lone lurker would boldly attack a line of cannons. Steamhammer has lost a lot of lurkers for free.

Today Steamhammer is a capable squad commander, and it can judge pretty faithfully when a lurker remains undetected by the enemy and should remain safely burrowed despite (meaning because of) the large enemy force on hand. Lurkers in a defense squad remain hyper-aggressive, so that they do not hesitate to eliminate enemies from the zerg base. Other lurkers now advance or retreat together with the units in their cluster, with some exceptions for retreat (see below) so that lurkers don’t unburrow too often. Lurker play remains clumsy, but it is far more flexible than before, and better overall.

• A lurker that Steamhammer believes is undetected will not retreat as long as it is in range of a target. It will remain burrowed, or it will burrow then and there instead of retreating. I made this change in an earlier version, but it had no effect until now because lurkers did not retreat.

• Failing the above check, if a burrowed lurker is asked to retreat then there is one more check: Will it survive long enough to unburrow? It calculates an expected survival time in frames and compares it to the time to unburrow plus a safety margin. If it won’t live that long, it doesn’t bother unburrowing; maybe it can get a last shot in. I do the same calculation for sieged tanks, with the unsiege time, so that a tank beset by zealots still has a chance for a last shot at some distant dragoon.

• I fixed a bug in the hidden enemies check; it handles an invalid missing ui.unit correctly. I don’t think it makes a practical difference with the existing codebase. The hidden enemies check prevents unburrowing when the lurker is undetected and, if it unburrows, at risk of dying to a cloaked unit, or a known unit out of sight on high ground. This check often (not always) prevents the cycle: Burrow on low ground just in range of a bunker up the ramp -> bunker stops shooting because the lurker is no longer visible -> the bunker is no longer visible to the lurker -> the lurker unburrows -> the bunker starts shooting again and becomes visible -> the lurker burrows....

• When possible, a lurker, guardian, or devourer will morph out of enemy view. When within enemy static defense range, it will not morph at all—at least theoretically; I’ve seen it happen so I think the check is not accurate. Lurkers should more often surprise the enemy, and cocoons should be shot down less often.

An urgent sunken to stop vultures, or an urgent spore to stop wraiths or corsairs, is made quickly, rather than, oh, whenever the bot happens to get around to it, might be any minute now if we’re still alive then. The slowness was due to a bad design decision I made in reworking the code in the SSCAIT tournament version.

Insert a fresh overlord after a spore colony, not before, when making supply and a spore is next in the queue, and similarly for the evolution chamber prerequisite. Formerly, when corsairs ravaged the overlords, Steamhammer gave priority to getting its supply back into the green. The corsairs had only to camp the zerg base for protoss to win.

• When scourge finds that its target is very close, it attacks even if the combat sim says to retreat. Scourge uses a combat simulation, excluding air units, to avoid ground fire. Often it would be on the verge of shooting down a corsair, but a dragoon was a little too close.... Steamhammer has missed a lot of kills that way.

• The defensive sunken versus cannons is tuned slightly.

openings

• There are no new or changed openings, only a change to how one opening is configured to be used. I saw the OverpoolTurtle build, a bot exploit opening that is objectively horrible, played in one game against a human on SCHNAIL, and that was one time too many. I removed it from the matchup and counter configurations, so it should be played very rarely unless it is found to succeed, and almost never against a human.

configuration

• New flag for Crazyhammer. Setting Config::Strategy::Crazyhammer to true causes Steamhammer to choose its openings purely at random from its large library, ignoring anything it may have learned. It’s used for Crazyhammer on SCHNAIL.

• New debug flag Debug::DrawLurkerTactics draws the name of the current default lurker behavior, either “Aggressive” or “With squad”. It doesn’t convey any information at the moment, since whenever there are lurkers they are “With squad”. I plan for lurker behavior to become more varied and complex, and then the debug flag will be useful.

unused stuff

• The movement sim is included and potentially valuable, but unused. There is code in FAP and in CombatSimulation.

• Fixed a primordial crashing bug in enemy unit clustering, making it also potentially valuable. But it’s turned off for now.

Next: Pathfinding.

movement simulation

Steamhammer is nearly prepared for AIST S4. I made 3 major improvements, plus smaller ones. Not everything I tried succeeded, though. Today I want to write about a new feature I implemented that is definitely valuable, and that Steamhammer will find uses for in the future even though my first use case didn’t work out as I hoped.

Steamhammer inherited from UAlbertaBot a weakness in retreating. It is Steamhammer’s longest-standing serious weakness. The point of retreating is to get away from a fight, so the units get move commands and ignore enemies they meet on the path to their retreat point. Nearly all cases where Steamhammer’s units run into or through or past enemies without fighting are because it is trying to retreat them. There are spectacular failure cases, like running ultralisks through a field of cannons and losing them for nothing. My past attempts to fix it have been half-hearted, since I want to delay coding a full tactical analysis until later. I thought I’d try something more courageous.

So I implemented retreat simulation in FAP. Moving is a lot easier than fighting, and it didn’t take much code. Pass in a target position, and our simulated units move to the position while the enemy’s keep chasing and shooting as usual, with no change to their side of the simulation. We’ll say the retreat “succeeded” if the fleeing units’ losses were lower than some percentage.

My idea was: If the regular combat sim said to retreat, then I ran a retreat sim in the same frame. If the retreat failed, then don’t run away, keep fighting regardless. The retreat sim runs fast because it is simpler, and also I cut its duration to only 2 seconds. I didn’t meet any problems with the frame time limit. In principle, the retreat sim can conclude “the cannon will kill us even if we run away, so keep hitting it,” and notice when the retreat path is blocked, and distinguish a faster force that can escape from a slower force that the enemy will chase down (yes the speedlings will kill your marines for free if they run, better to make a last stand).

In practice, the retreat sim might be worth it on average, but I was not convinced. The sim results were as noisy as ever, and there were times when it hurt. Also, in some situations it’s conceptually the wrong thing to do. Having the “retreating” ultralisks fight cannons is better than running past the cannons, but better yet is to understand the situation as an attack rather than a retreat, and to stand aside until you can win. And that seems to call for the tactical analysis that I was saving for later.

I called the internal routine in FAP not retreatsim() but rather movesim(), because it can answer many questions about when movement is safe.

  • Will this runby succeed in running by?
  • How far can the transport go before it must unload?
  • How many scourge will survive to hit their target?
  • Will this plague/broodling/etc. succeed, or will the caster be killed first?
  • If I mind control that unit, will it live to join my army?

And with a little more elaboration, questions like

  • If it lives to cast, will the defiler/queen/etc. also escape safely?
  • Is there time to unburrow/unsiege, or should the immobile units remain as they are?
  • Can the mutalisks pick off another marine and still get away without losses?

I think it’s a safe bet that Steamhammer will be answering some of these questions before AIIDE this year.

another Steamhammer-Krasi0P proxy game

Back in last July and August, I wrote up a couple of Steamhammer-Krasi0P proxy games, proxy vs proxy just to show off the new skill, and proxy vs turtle, which was a festival of absurdity. A few days ago, Steamhammer played another proxy hatchery against Krasi0P, this time one that looked almost tailor-made for the opponent. In reality the build was tailor-made to defeat TyrProtoss’s cannon-and-build-up-at-home strategy, although Steamhammer has never tried it against TyrProtoss. It was sheer coincidence that it was also suitable to counter Krasi0P’s cannon-contain strategy (not its cannon-at-home strategy this game). Though I should add, I keep specialized builds around in part to discover coincidences like this.

both proxies are up

The game is already 6 minutes old. Krasi0P, seeing the proxy, built only one containing cannon, which you may be able to make out as a purple dot on the minimap. That may be why protoss has extra minerals. Steamhammer, when it saw the cannon in turn, decided not to take its natural but to send a drone to the upper left, where it took a “hidden” base. Steamhammer does not want to be stuck inside a containment, it wants to be on both sides, and here it forgot that it already was on both sides. Protoss chose to tech up and defend itself with more cannons before moving out with its first dragoon, and zerg is just starting its first sunken with good timing. The timing is an accident, since the build was made for a different opponent playing differently!

hitting that pesky cannon

Steamhammer made lurkers and, before anything else, sent them to clean up the isolated containing cannon. It would have been more efficient to send the hydras before morphing them into lurkers....

moving in for the kill

Steamhammer added more sunkens and a spore to prepare for TyrProtoss’s move out with a large army including an observer. Krasi0P did not have a large army or an observer, and soon fell to three hatcheries worth of zerg onslaught (on the minimap you can see units approaching from both other bases). Krasi0P seemed to try to adapt by cutting its containing cannons down to one, but did not understand the situation and reacted slowly. Steamhammer didn’t understand either, and only chose an appropriate plan by chance, but did at least execute its build efficiently.

They aren’t visible, but in each picture, outside the frame, there are drones mining at the proxy. Even after making 4 sunkens and a spore, 3 drones remained to mine there. Another funny game between these two.

the kitchen sink

A SCHNAIL game. This human player was able to cope with every aggressive trick Steamhammer tried, and it tried several, but not able to keep up macro at the same time.

finishing moves

Zerglings and ultralisks are under the dark swarms, invulnerably shredding everything. Of the ensnared marines to the left of the swarm, the majority are also plagued. Only the most recent spell shows.

When its economy is strong enough, Steamhammer switches quickly from midgame play whose goal is to hold on and not fall behind, into the endgame with power moves. As a human player, how does it feel to face so-so play for most of the game, holding after no worse than a bit of a scramble, and then be abruptly overwhelmed by a prodigious army that seems to have all the tech and be able to do everything simultaneously?

In the following game between the same players, the human was the aggressive one, moving out and repeatedly destroying zerg expansions. Steamhammer never built up its endgame army. But with the terran army away from home, mass zerglings overran the terran bases. Keeping up with AI macro is mechanically demanding for the human player. I expect it’s good practice, though.

tuning against humans

Check out this event in the game webgames vs Steamhammer on Sylphid from SCHNAIL.

mutas over the barracks

Human players of terran and protoss normally group their production buildings. It’s pretty much a requirement for efficient macro. It also introduces a vulnerability: If the opponent maintains control over the area of your production and you don’t already have enough army to force them away, then you are in deep trouble. Any new units you produce will die, so you cannot regain control of your production. Here, terran has built turrets to protect its main and natural mineral lines, but none next to the barracks, an oversight that invites attack.

In the picture, I judge that Steamhammer does not hold enough control over the terran barracks to win, even though the selected barracks is in the red and in danger of burning down. It did hold that much control earlier and had strong winning chances, but frittered it all away: Mutas attacked the barracks, then were distracted away, then attacked the barracks again, then were distracted, and so on, losing mutalisks and allowing the marine count to build up. Bots rarely group their production buildings, so I never taught Steamhammer about the leverage of controlling the enemy production. In the picture, the mutas are about to be distracted away for the last time. Terran repaired the burning barracks, had unhampered production, and won easily with their huge economic advantage.

Distractibility is an issue in itself. I need to improve tactical target selection so that it doesn’t change its mind so frivolously. The skill of controlling the enemy production is a harder question for me, from a development point of view. The skill would be just as valuable in bot-vs-bot games when it applied, but it would not apply often. I wouldn’t be tuning specifically against human play, but it leans that way.

There are specific anti-human skills that don’t help against bot opponents. Humans often try to overwhelm the opponent’s multitasking with threats and feints and multi-point attacks. Multi-point attacks in themselves call for multitasking ability. Bots can do the same much better. I don’t have any plans to tune to exploit human weakness in multitasking, though I suppose I may come around to it in the distant future.

follow-the-leader doesn’t always work

Steamhammer divides its units into squads (like many bots), and gives each combat squad orders to try to reach a target position, such as an enemy base. The system is inherited from UAlbertaBot. A squad may have units all over the map, such as some at the front line and some just spawned, and units in different places face different situations. So Steamhammer divides a squad into clusters of units and tries to make appropriate decisions for each cluster, depending on whether enemy units are near and so on. The clustering has been in place since fall 2018; see Steamhammer 2.0 squad unit clustering for the description, which is still up to date in everything it says, though some details have changed behind the scenes.

I want Steamhammer’s clusters to join up when they can, so that the army fights together. So it uses a follow the leader rule: The frontmost cluster, the one closest to the target position, is the leader. Other clusters, if they are not distracted by enemies nearby, try to join up with nearby clusters that are more forward, and ultimately with the leader. In other words, clusters don’t independently seek out the target position; their first priority is to merge into a big scary army that will win. Compare Steamhammer’s tactical habits with, say, Dragon, which feels freer to scatter its units across the map.

Thinking about nydus canals, I noticed that following the leader doesn’t work in all cases.

sketch of situation with nydus canals and enemy attack

The enemy is attacking a base with a nydus. A squad is ordered to defend. Cluster 1 is closest, because it can jump through the nearby nydus to reach the base. Cluster 2 does not want to follow the leader; it wants to travel by ground, not by nydus, because that way is closer for it. Cluster 2, to follow its defense order, should attack (or at least threaten) the enemy from the rear.

Nydus canal is not the only way to break follow-the-leader. Imagine a target position with two separated entrances, like an edge base on Fighting Spirit. If two clusters find themselves at different entrances and want to attack, each should probably attack its own entrance.

I don’t know how I’ll end up fixing this. There is time to think about it. A related and more immediate issue is how to attack the same enemy position from two directions at once, which Steamhammer currently can’t do except by accident. I’ll solve that by analyzing which clusters are attacking the same enemy units and doing a joint combat sim, so that all attack together or all retreat.

AIST S4 prep

Today I received my acknowledgement that Steamhammer is registered for AIST S4. It will play, if the creek don’t rise.

I haven’t been posting about Steamhammer progress because I’m preparing (ssh! it’s a secret!) Secret Improvements. When the list of participants is announced, I expect I’ll make special arrangements for some of them. Steamhammer should be a moving target so that the same doesn’t happen to it.

The improvements I’ve made already are significant, and I’m finishing up another good one today. I’m hoping that, unlike the other two times Steamhammer participated (S1 and S2), it won’t be knocked out at the first opportunity and end up in last place, or tied for it, with 2 match losses. (In S3, the rival Microwave played instead and got the same result.) I don’t want to get used to being pushed over like a cardboard cutout.

I’m also hoping that by submission time at the ominous Ides of March, Steamhammer will have collected enough opening data that I can decide what to do about it next. I’m still forecasting that I’ll need a second collection phase to fill in gaps. Plus I’ll need a long-term plan to keep the data updated as Steamhammer’s skills progress.

practice games on SCHNAIL

Recently a not particularly skilled protoss played more than 20 practice games versus Steamhammer on SCHNAIL, winning 3 and losing the rest. I went through the games in sequence. Steamhammer put its variety of builds to good use, sometimes 4 pooling, sometimes busting with one or another kind of all-in, sometimes defending then countering, sometimes building up then rolling everything down with hive tech. I was mostly pleased, though tactical clumsiness was an issue. In the most spectacular game, protoss aggressively attacked, destroyed and held the zerg natural, built a gateway and nexus there and started mining the zerg’s minerals, and found and cleared the only other zerg expansion. Steamhammer held its main with sunkens and units, used an escaped drone to lay down another expansion that protoss did not find, teched to lurkers, pushed down its ramp to free its natural, defeated the dragoon army, and won with the one big counter. That must have been frustrating for the human player, who apparently did not have the multitasking ability to both micro the dragoons and get observers in time (a difficult skill to learn, if you ask me, because multitasking is not natural for humans). Yet game after game followed.

Looking at it from the human player’s viewpoint, protoss started with a not very convincing gateway and forge build with no cannons. After Steamhammer easily busted it a few times, protoss started experimenting with other plans. Some were defensive, like holding with cannons and trying to expand by shuttle. The more successful were aggressive, like the game above. Over the long sequence, I thought I could see the human player’s skills slowly sharpening, with more consistent macro and more appropriate strategies. Stronger players quickly notice Steamhammer’s weaknesses and exploit them, but this was a player who hadn’t reached that level of knowledge yet. It takes experience to build skills, you have to work through all the stages.

SCHNAIL is great for this, I conclude. If you can find a bot at your strength level or above, but not hopelessly above, and with some variety in its play, you may have a good practice partner. You can polish your basics and learn what kinds of plans work and try out new ideas. If you find yourself winning most games, then I guess either you’ve improved that much or else you’ve learned to exploit the bot’s weaknesses. I’d say if you want to keep improving, it has become time to look for your next opponent.