scmJS docs

Running a map in the game

These commands start the map in the game itself, the engine of the play page, with the game's files from your copy of StarCraft (what they need). Nobody has to play: the game runs as fast as the computer can run it. Or the players of a recorded game play it: --replay gives the game the commands from a replay, so the game in a bug report can be traced and tested. A map in the original StarCraft 1.00 form is run as Brood War, as the editor would convert it. The Blizzard campaign maps are not something the engine runs yet. Each command exits with 2 when the game could not start.

Which triggers fire#

scmjs map trace starts the map as a Use Map Settings game, with your seat left idle and the computers as the map sets them, runs it for a while, and records every condition the game checks and every action it runs. The report has a line per trigger, with how often it fired, when first and last, and whose it is. Then come the triggers that never fired, each with the condition that last said no.

map trace on a test map: one trigger fired once, two never fired

scmjs map trace ums.scx. Nobody brings a Marine to the middle, so the trigger waiting for one never fires.

The game runs the triggers about every two seconds; each run is a pass, and a trigger that fires on every pass is a preserved one. A count with +n after it is a trigger whose Wait held it and then let it go on n times.

Option What it does
--frames How long to run, at 24 frames a second. The default, 2,400, is 100 seconds of play; the game may end sooner if a trigger ends it
--seed Fixes the game's luck, so the same map and seed give the same run
--all Lists every trigger, when the map has more than forty
--json The same, per trigger, as data

Nobody plays, so nothing a player would do happens. A trigger waiting for a unit to be brought somewhere will say so, which is the point.

What the triggers cost#

scmjs map cost runs the map the same way and weighs every trigger the game passes and every condition and action it runs, as StarCraft: Remastered charges for them. The model is the one TrigScript's editor uses, measured on the real game: a Deaths condition costs about five nanoseconds, and the game stays smooth up to about fifteen milliseconds of triggers a frame. The report says what a frame costs on average where triggers ran and at most, how that compares with what a frame can carry, and which triggers cost the most:

✔ trigscript-frame-cost.scx: triggers cost 0.025 ms a frame on average where they ran (599 of 601 frames), 1.10 ms at the most (frame 2: 83,787 records, 53,192 triggers)
  Remastered on a fast computer: smooth to about 15 ms of triggers a frame, slows past 37 ms
  83,787 of the records ran in no trigger of the map — a program's own (TrigScript, eudplib), 1.10 ms in all

A TrigScript or eudplib program runs its own records through the EUD trigger chain rather than through the map's trigger list. For such a map the cost is in the first lines, and the triggers listed are only the ones that start the program. TrigScript's editor shows a program's cost line by line.

Triggers in a replay's game#

When someone sends a replay of a game where the map went wrong, --replay plays that game with the trace on. The players' own commands drive it, people and computers as they played, to the end of the replay.

scmjs map trace --replay report.rep --order

The report is the one above, with two differences. The first and last firings are given on the game's clock, m:ss at Fastest as the replay viewer counts it, and only the triggers owned by the players in the game are listed. --order adds every firing in the order the game ran them: the time, the frame, whose copy of the trigger it was, and the trigger.

$ scmjs map trace --replay 1631.rep --order
1631.rep 128 × 128, jungle · 4 players · 10:16 · 1,713 triggers (12 copies in the game) · Remastered files

✔ 1631.rep: 1,713 triggers (5 owned by the players) · 14,662 frames (10:16), Ever)Z(bOy won · 473 passes · 4 fired · 1 never · 6.2 s

  #     Fired  First  Last   Players      Trigger
  1709  2      0:00   0:00   P3 P4        Elapsed Time(At least, 0) → Set Resources("Current Player", S…
  1710  2      0:00   0:00   P5 P6        Always() → Run AI Script("Turn ON Shared Vision for Player 1"…
  1711  0      –      –      P3 P4        Command("Current Player", "Buildings", At most, 0) → Defeat()
  1712  1      10:13  10:13  P3 P4        Command("Non Allied Victory Players", "Buildings", At most, 0…
  1713  4      0:00   0:00   P3 P4 P5 P6  Elapsed Time(Exactly, 0) → Display Text Message(Always Displa…

  Never fired (1 of 5)
  ▸ #1711  Command("Current Player", "Buildings", At most, 0) → Defeat()
      Command (condition 1) said no last, at frame 14,634

  In the order they fired (9)
  Clock  Frame   Player          #      Trigger
  0:00   2       P3 ErOs_Class   #1709  Elapsed Time(At least, 0) → Set Resources("Current Player"…
  0:00   2       P3 ErOs_Class   #1713  Elapsed Time(Exactly, 0) → Display Text Message(Always Dis…
  0:00   2       P4 Ever)Z(bOy   #1709  Elapsed Time(At least, 0) → Set Resources("Current Player"…
  0:00   2       P4 Ever)Z(bOy   #1713  Elapsed Time(Exactly, 0) → Display Text Message(Always Dis…
  0:00   2       P5 ErOs_Kyoung  #1710  Always() → Run AI Script("Turn ON Shared Vision for Player…
  0:00   2       P5 ErOs_Kyoung  #1713  Elapsed Time(Exactly, 0) → Display Text Message(Always Dis…
  0:00   2       P6 iCoN`MisO    #1710  Always() → Run AI Script("Turn ON Shared Vision for Player…
  0:00   2       P6 iCoN`MisO    #1713  Elapsed Time(Exactly, 0) → Display Text Message(Always Dis…
  10:13  14,603  P4 Ever)Z(bOy   #1712  Command("Non Allied Victory Players", "Buildings", At most…

With no map named, the game is played on the map inside the replay. Name a map as well to play the replay's commands on that map instead, such as the fixed version of the one in the bug report:

scmjs map trace fixed.scx --replay report.rep

The command says first how the map differs from the one in the replay. A replay is only the players' commands, so a changed map can make the game go out of step. When a unit a player clicked is not there, or a building cannot be placed, the game plays on with commands meant for another game. Changes to triggers alone usually keep it in step. Changes to units, terrain or resources usually do not. The game checks each command against what it has, and the report says when and where the game parted from the replay. What fired before that point is what happened.

scmjs map cost --replay weighs the triggers of the replay's game in the same way. --frames stops the game sooner. Left out, the whole replay is played. In a game that is not Use Map Settings, the game runs its own melee triggers rather than the map's, and the report says so.

Testing a map's triggers#

scmjs map test checks that a map's triggers do what you meant, in the game. You write scenarios in a tests file: what the game starts with, what happens at given times, how long to play, and what must be true at the end. Each scenario is one game. A scenario that fails says what did not hold and what was there instead, and keeps the game's replay so you can watch what happened.

map test on a test map: two scenarios pass, the third fails with five expectations listed

scmjs map test ums.scx, with the tests file kept beside the test map.

Here the third scenario found something real: the test map has no ground a Marine can stand on, so the Marine never appeared and the trigger waiting for it never fired. That is why a failed scenario lists the game's own warnings from its start.

The tests file#

The tests are read from <map>.tests.json beside the map (ums.tests.json for ums.scx), or from the files named after the map on the command line. The file is JSON:

{
  "scenarios": [
    {
      "name": "the alarm wins the game",
      "frames": "0:20",
      "steps": [
        { "at": 24, "chat": "glhf" },
        { "at": "0:02", "create": "Terran Wraith", "player": 1, "location": "Middle" },
        { "at": "0:05", "switch": "Alarm raised", "state": "set" }
      ],
      "expect": [
        { "trigger": "Opening", "fired": 1 },
        { "text": "Hold the base." },
        { "units": "Terran Wraith", "player": 1, "location": "Middle", "is": 1 },
        { "switch": "Alarm raised", "is": "set" },
        { "outcome": "victory", "player": 1 },
        { "ended": true }
      ]
    },
    {
      "name": "a Marine brought to the middle raises the alarm",
      "frames": "0:25",
      "setup": [
        { "create": "Terran Marine", "player": 1, "location": "Middle" }
      ],
      "expect": [
        { "trigger": 2 },
        { "switch": "Alarm raised", "is": "set" }
      ]
    }
  ]
}

A scenario has a name, and may have frames, seed, setup, steps and expect. frames is how long to play: a number of frames, 24 to a second at Fastest, or "m:ss"; 2,400 unless the file or --frames says otherwise. frames and seed at the top of the file apply to every scenario that does not set its own.

A scenario may name a replay instead of setup and steps. The replay's commands drive the game, played on the map being tested, and the scenario plays to the replay's end unless frames says otherwise:

{
  "name": "the bunker fills at 7:30",
  "replay": "reports/bunker.rep",
  "expect": [
    { "trigger": "Bunker full", "fired": 1 },
    { "outcome": "victory", "player": 1 }
  ]
}

The path is relative to the tests file's folder. seed does nothing here, because the game uses the replay's own. Such a scenario takes no setup or steps, since anything added to the game would take it out of step with the replay. scmjs map test record writes these scenarios for you (below).

Things are named as the trigger window names them:

Thing Written as
Player A number from 1 to 12, or "Player 1". Where a trigger would take a group, "All Players", "Force 1", "Foes" and "Allies" too
Unit Its name ("Terran Marine", or "Marine" when that is clear), a class ("Any unit", "Men", "Buildings", "Factories"), or its number in units.dat
Location Its name, "Anywhere", or its number
Switch Its name, "Switch 3", or a number from 1 to 256
Trigger The number scmjs map trace prints for it, or the text of its Comment action

A name the map does not have is an error that lists the names it does have.

setup is what the game starts with. Each entry does one thing:

Entry Does
{ "create": unit, "player": p, "count": n, "location": l } Creates the units (one unless count says)
{ "kill": unit, "player": p, "count": n, "location": l } Kills the units (all of them unless count says)
{ "remove": unit, "player": p, "count": n, "location": l } Removes the units, as Remove Unit does
{ "give": unit, "player": p, "to": q, "count": n, "location": l } Gives the units to player q
{ "switch": s, "state": "set" } Sets the switch ("clear" and "toggle" too)
{ "minerals": n, "player": p }, { "gas": n, "player": p } Sets the player's minerals or gas; "+100" adds, "-100" takes away
{ "deaths": unit, "player": p, "count": n } Sets the death count, with the same + and -
{ "action": "Set Countdown Timer(Set To, 60)" } Any action, in the trigger window's text form; a list of them works too

location is Anywhere when left out.

steps happen during the game. Each has at, a frame or "m:ss", and one of the entries above, or one of these, which are your own commands as a player would give them:

Entry Does
{ "at": t, "order": "move", "unit": u, "location": l, "to": m } Orders your units of that type in location l to location m: "move", "attack" (attack-move) or "patrol"; "stop" and "hold" need no to. unit is Any unit and location Anywhere when left out
{ "at": t, "chat": "text" } Sends a chat line, which shows on screen like any other

You play the map's first slot that is open to a person, as when you start the map alone, and the computers play as the map sets them. Your commands reach the game at the frame given. The other steps are carried out by the game's triggers, which run about every 31 frames (1.3 seconds), so they happen at the first pass after the game's clock reaches their time.

expect is what must be true when the scenario ends:

Entry Holds when
{ "trigger": t, "fired": n } The trigger fired that many times; true (the default) is at least once, false is never
{ "switch": s, "is": "set" } The switch is set, or "clear"
{ "deaths": unit, "player": p, "is": n } The player's death count for that unit
{ "minerals": n, "player": p }, { "gas": n, "player": p } The player's minerals or gas
{ "units": unit, "player": p, "location": l, "is": n } How many of the units the player has there, counted as the Bring condition counts them (as Command counts them when there is no location; player is All Players when left out)
{ "text": "words" } The words were on your screen at some point: trigger text, transmissions, chat. "shown": false for words that must not appear. In a replay's game, the screen is the replay viewer's
{ "outcome": "victory", "player": p } The player's game ended in victory ("defeat", "draw", or "none" for no outcome yet)
{ "ended": true } The game ended before the scenario's frames ran out (false: it was still going)

A count is a number, meaning exactly that many, or a comparison such as ">= 2", "< 5" or "!= 0". The text shown and how often a trigger fired cover the whole game; everything else is read when the scenario ends.

How a test runs#

The setup and the timed steps become triggers in a copy of the map. The setup is one trigger placed before the map's own, owned by the first player in the game, with the condition Always, so it runs first in the first pass. Each step is a trigger placed after the map's own with an Elapsed Time condition. Your map file is not changed, and the trigger numbers in the report are the map's own.

Because these triggers are part of the copy, the replay holds them. A failed scenario's replay is kept in <map>.tests/ beside the map (or where --out says) and plays back the scenario exactly as it ran. scmjs replay open opens it in the editor, where the test's own triggers show first and last among the map's.

Option What it does
--seed The game's random seed for scenarios that do not set one; the same map, scenario and seed give the same game every time
--frames How long to play, for scenarios and files that do not say
--out Where the failed scenarios' replays go
-u, --update For scenarios that play a replay: the expectations that no longer hold are rewritten to what the game gives now
--json Every scenario with each expectation, whether it held and what the game had
--format github, junit, sarif The failures in the forms CI tools read, as for map check, each placed on the scenario's line in the tests file

The command exits with 1 when a scenario fails, and with 2 when the tests file or the map cannot be read or the game could not start. It needs the game, so it does not run on GitHub's own runners; the workflow scmjs init writes leaves it out and says how to run it on a runner of your own that has the game.

A test cannot yet have a player leave (you are the only person in the game) or give the computers' own commands, except by playing a replay of a game where they did.

A test from a replay#

scmjs map test record turns a replay into a test. It plays the replay on the map, as map trace --replay does, and writes a scenario into the map's tests file with everything the game ended with as expectations:

$ scmjs map test record 1631.scx 1631.rep --name "2v2 to the end"
✔ Recorded "2v2 to the end" into 1631.tests.json · 14,662 frames (10:16) · 40 expectations · 6.5 s
  5 triggers, 1 line shown, 4 outcomes, 1 end, 8 unit counts, 4 minerals, 4 gas, 13 death counts

The scenario it wrote begins:

{
  "name": "2v2 to the end",
  "replay": "1631.rep",
  "frames": 14662,
  "expect": [
    { "trigger": 1709, "fired": 2 },
    { "trigger": 1710, "fired": 2 },
    { "trigger": 1711, "fired": 0 },
    { "trigger": 1712, "fired": 1 },
    { "trigger": 1713, "fired": 4 },
    { "text": "ErOs_Class has left the game.", "shown": true },
    { "outcome": "defeat", "player": 3 },
    { "outcome": "victory", "player": 4 },

The expectations are a snapshot of the end of the game: how often each trigger the players own fired (0 for the ones that never did), the switches that are set and the named ones that are clear, the lines shown that were not chat, each player's outcome, whether the game ended, each player's men and buildings, minerals and gas, and every death count. Triggers, switches and units are named as the map names them, where the map has a name for them. Each expectation is one line of the file, so delete the lines you do not care about and keep the ones the test is for.

Option What it does
--name The scenario's name; the replay's file name by default. A scenario with that name is replaced
--tests The tests file to write into; <map>.tests.json beside the map by default, made if it is not there
--frames How much of the replay to play; all of it by default
--json The scenario as written, with the game's end

From then on scmjs map test plays the replay against the map as it is now. After a change you meant to make, scmjs map test map.scx -u rewrites the expectations of replay-driven scenarios that no longer hold to what the game gives now. Only the values change. The expectations you deleted stay deleted, and a comparison such as ">= 3" that still holds is left alone. Scenarios with steps are never rewritten. If the map changed so much that the game parted from the replay, the report says when, as map trace --replay does.

Which triggers fired across many games#

scmjs map coverage plays a folder of replays on the map, each as map trace --replay plays one, and adds up what fired. It answers whether a trigger ever fires when people play the map. A trigger that never fired in any of the games is either one nobody reaches or one that is broken, so those are listed clearly.

scmjs map coverage td.scx replays/

Replays are files or folders, sub-folders included. Only replays of this map count: the same map, or one with the same name and size, as for map heatmap. Only Use Map Settings games count too, since in the other game types the game runs its own melee triggers. Every replay left out is listed with the reason. Several games are played at once: half the processors, at most four, or --parallel.

$ scmjs map coverage fs.scx replays/ --force
fs.scx · 4 triggers · 6 replays to play, 4 at a time
1/6 ▲ 1570.rep  25:24 · 6 players · 2 triggers fired · parted at 0:00, counted to there · 6.6 s
2/6 ▲ 1571.rep  31:59 · 6 players · 2 triggers fired · parted at 0:00, counted to there · 7.3 s
3/6 ▲ 1544.rep  49:11 · 4 players · 2 triggers fired · parted at 1:46, counted to there · 10.7 s
4/6 ▲ 1631.rep  10:16 · 4 players · 2 triggers fired · parted at 0:34, counted to there · 5.5 s
5/6 ▲ 1635.rep  37:42 · 3 players · 2 triggers fired · parted at 0:00, counted to there · 8.1 s
6/6 ✔ 1603.rep  34:08 · 4 players · 3 triggers fired · 30.5 s

▲ fs.scx: 3 of 4 triggers fired in 6 games; 1 never fired
▲ 5 games parted from the map: 1544.rep at 1:46, 1570.rep at 0:00, 1571.rep at 0:00 and 2 more. What fired after that is not counted: the players' commands were meant for another game.

  #  Games  Fired  First  Players            Trigger
  1  6/6    12     0:00   P1 P2 P3 P4        (no conditions) → Set Resources("Current Player", Set To, …
  2  6/6    15     0:00   P3 P4 P5 P6 P7 P8  (no conditions) → Run AI Script("Turn ON Shared Vision for…
  3  0/6    0      –      P1 P2 P3 P4        Command("Current Player", "Buildings", At most, 0) → Defea…
  4  1/6    1      34:06  P1 P2 P3 P4        Command("Non Allied Victory Players", "Buildings", At most…

  Never fired (1 of 4)
  ▸ #3  Command("Current Player", "Buildings", At most, 0) → Defeat()
      Command (condition 1) said no last, in 1603.rep at 34:08

--force took replays played on other maps, so five of the six games parted from this one early. Only the last game ran this map as it was played.

For each trigger the table gives the games it fired in, how often it fired in all of them, and its first firing on the game's clock. Only triggers owned by a player in at least one of the games are listed. The first line counts the rest separately, and --all lists them too. A game whose players' commands parted from the map, as explained above, counts only what fired before that point, and is marked ▲.

--tests counts the scenarios of the map's tests file as games as well, and a tests file named among the replays (any .json) counts its scenarios. Whether a scenario passes does not matter here; that is map test's job.

Option What it does
--tests Also count the games of <map>.tests.json beside the map
--strict Exits with 1 when a trigger a player owned never fired
--format github, junit or sarif: the triggers that never fired, for CI. Warnings, or errors with --strict, and a note for each game that parted
--all Lists every trigger, even ones no player in the games owned
--force Takes replays played on another map too. Expect those games to part early
--parallel Games played at once
--json Every game, every trigger with its counts, its text and why it never fired

Each replay takes about a second for every minute or two of play, so a folder of a hundred games takes a few minutes. Like the other commands that run the game, it needs your copy of StarCraft, so in CI it runs only on a machine that has one.

Rush distances#

scmjs map rush measures how long a rush takes between each pair of start locations. The game walks a unit from each start location to every other, along the path a unit sent there in a game would take, and times each walk. An SCV walks unless you name another unit with --unit: by its name in the editor ("Zerg Zergling"), without the race (Zergling, Dragoon), or by its number in units.dat. The rows of the table are the starts walked from and the columns the starts walked to, each with its place on the map as a clock face from the middle:

$ scmjs map rush "(4)Crossing.scx" --unit Zergling
(4)Crossing.scx 128 × 128, desert · 4 start locations · a Zergling walked 12 times · Remastered files
  1 → 2  4,376 px, 34.6 s
  …

✔ (4)Crossing.scx: a Zergling walked between 4 start locations by the game · 12 walks · 3.4 s

  from ╲ to                    1                 2                 3                 4
  1 11 o'clock                 –  4,376 px, 34.6 s  2,957 px, 23.5 s  3,826 px, 30.5 s
  2 4 o'clock   4,408 px, 34.9 s                 –  3,847 px, 30.9 s  2,871 px, 22.3 s
  3 1 o'clock   2,858 px, 22.7 s  3,847 px, 30.9 s                 –  4,535 px, 36.4 s
  4 8 o'clock   3,857 px, 30.7 s  2,937 px, 23.1 s  4,576 px, 36.8 s                 –

  Shortest 2 → 4: 2,871 px (90 tiles), 22.3 s · air 2,816 px, 1.02× the straight line
  Longest  4 → 3: 4,576 px (143 tiles), 36.8 s · air 3,806 px, 1.20× the straight line

The pixels are the distance the unit walked (32 to a tile) and the seconds are game time at Fastest. The unit sets out five tiles from its start toward the middle of the map and stops at the edge of the town hall at the other start: a neutral Nexus stands on every start while the walks run, so the walk ends where a real rush ends, at the base. The two directions between a pair can differ by a second or so, because the way out of each base is not the same. The shortest and longest walks are compared with the straight line between the two starts, the distance a flyer goes. When the terminal is too narrow for both figures, the seconds and the pixels come as two tables.

A walk that does not arrive is a finding too. Two starts with no ground between them, on an island map or where the terrain walls one off, are reported as having no way; on a map where no two starts are joined, the report says so and lists the straight lines instead. A unit that stops short (at a wall of minerals or buildings the map places, say), gets stuck, or is still walking after seven minutes of game time is named with how far from the base it ended.

The walks run in one game with nobody else in it: about four seconds for a four-player map and fifteen for an eight-player map of 256 × 256. --seed changes the game's luck, such as a unit stepping round another; the default is the one the editor's Walkability plugin uses, so the two agree. --json gives every walk and the table as data. A building or a flyer is refused (exit 2), as is a map with fewer than two start locations.

Is the map fair to every start#

scmjs map balance is a melee map's report card. The same two sides play game after game on the map, and the report has a row for each start location: how often the side that started there won, how long its games lasted and how soon it met the enemy, beside the numbers that need no game. The sides are your bots: one bot plays the game's own computer, or two bots play each other, as bot ladder plays them.

scmjs map balance "(4)Crossing.scx" --bots out/mybot.scmbot:p --races p --games 12 --parallel 4
scmjs map balance "(4)Crossing.scx" --bots mybot.scmbot,mybot.scmbot --games 12
$ scmjs map balance "(4)Test4.scx" --bots ualbertabot.scmbot:p,ualbertabot.scmbot:p --games 12 --parallel 4
12 games · UAlbertaBot vs UAlbertaBot (ualbertabot-2) · 4 start locations · 1248410ad2a0 · 4 at once
 2/12 ✔ UAlbertaBot (ualbertabot-2) P (start 4, 8 o'clock) beat UAlbertaBot P (start 3, 1 o'clock) · 9:39 · first contact 2:32 · seed 2
 …

(4)Test4.scx 128 × 128 · 12 games played · UAlbertaBot 8-4 vs UAlbertaBot (ualbertabot-2) 4-8 · 176 s

  Start              Games  Won  Lost  Win rate  Expected  Length  Contact  Rush    Minerals           Gas
  1      11 o'clock  8      1    7     13%       50%       8:10    2:22     32.0 s  9 × 1,500 · 6.4 t  1 × 5,000 · 7.8 t
  2      4 o'clock   3      2    1     67%       33%       11:05   2:16     32.7 s  9 × 1,500 · 6.4 t  1 × 5,000 · 7.8 t
  3      1 o'clock   9      7    2     78%       59%       7:40    2:26     32.0 s  9 × 1,500 · 6.4 t  1 × 5,000 · 7.8 t
  4      8 o'clock   4      2    2     50%       42%       9:13    2:27     32.8 s  9 × 1,500 · 6.4 t  1 × 5,000 · 7.8 t

  Pairs met: 1–2 ×3, 1–3 ×5, 3–4 ×4 · never: 1–4, 2–3, 2–4

▲ Start 1 (11 o'clock) wins only 13% where 50% was expected — its resources and distances are within 10% of the others', so the difference is in the ground or in how the games went.

The map above was made by map generate with its four quarters mirrored, so its starts differ only in where they face. Twelve games of one bot against itself are enough to see that this bot does worse from the top left; they are not enough to say why. A verdict to act on wants more games, and more than one bot.

Column Means
Games, Won, Lost The games a side started at this start location, and how they went. A game stopped at --frames is counted in Games only
Win rate Won out of won and lost
Expected What the two sides' own records predict for those games. With two sides of a strength it is 50%; when one side is stronger, a start it was dealt more often is expected to win more
Length, Contact The average game, and the average time the first unit was lost to the other side
Rush The average walk to the other starts, as map rush measures it; --no-rush leaves it out
Minerals, Gas The main's fields and geysers: those nearest this start and within 12 tiles of it, the amount each holds and their average distance in tiles

The verdict compares each start's win rate with what was expected. Within 10 points everywhere, the map is called balanced within the largest difference. A start further off is named, with the number that sets it apart from the others when one does (less gas, a mineral line further away, shorter walks to the enemy, earlier fights). When the games are too few to tell the difference from chance, the verdict says so and how far chance alone would move that start. When one side won every game, the games say nothing about the starts, and the verdict asks for two sides of a strength: the same bot twice does that.

The game deals the start locations itself, as it does in any melee game, and there is no way yet to ask for a particular one. So each game's replay is played back afterwards to see which start each side was dealt, and the rows are filled from those. Enough games give every start its turn; the line under the table lists the pairs of starts that met and those that never did. A bot playing itself plays each seed once, since the other lobby order deals the same starts and gives nearly the same game.

A game takes from ten seconds to a minute or so, depending on the bots and how long it lasts; --parallel plays several at once (by default up to four, as the computer's cores and memory allow; a game takes about 1.5 GB for each bot in it). The replays are deleted afterwards unless --out names a folder to keep them in, for scmjs replay watch. --json gives every row, every game with the starts dealt, and the verdict as data. The command exits with 1 when a game could not be played, and with 2 for a map with fewer than two start locations.

scmJS 0.12.1 · Generated from the repository. The source is under the MIT license, which does not cover what ATTRIBUTION.md lists. StarCraft and Brood War are trademarks of Blizzard Entertainment; this project ships none of their data.