Matches, lockstep and replays
Matches between bots
ibf match hunter.py rival.py --map 1 --matches 5ibf match starts each bot as a process of its own on pads 0, 1, ... in the order given,
waits until each holds its pad, then sets the match up through the game's own menus and
follows it round by round. A versus match is first to three rounds; --matches N plays N
of them, with the game's Retry between them. An arcade game has no winner: it ends when the
last life goes, and its results carry the arcade score and the lives left;
--max-seconds ends a run that lasts longer, as cap, which counts too. At the end it
stops the bots and writes results\<time>.json: each match's rounds (winner and length),
its winner and scores, and each bot's latency and fault.
A bot's fault is null when it played to the end, or what went wrong with it:
no_connect (it never took its pad), crashed (its process exited, or a remote bot's
connection ended, before the end), hung (a remote bot went silent or stopped reading),
protocol (the TCP server closed it for what no client sends), or too_slow (it answered
late for more than --max-late of the game's polls). The arena and the website tell a bot's
own failure from their own this way.
| Option | |
|---|---|
--mode versus|arcade |
versus by default |
--players 1|2 |
versus: one versus one (2 bots) or two versus two (4 bots); arcade: 1 or 2 players |
--map N |
the map select's number, from 0 |
--out folder |
where the results go |
--lockstep |
the game waits for the bots (below) |
--lockstep-timeout MS, --lockstep-miss neutral|hold |
how long, and what a late bot plays (below) |
--record |
records each match |
--quit |
back to the main menu at the end |
--remote-wait S |
how long tcp:<token> bots have to connect and take their pads (default 60) |
--max-seconds S |
arcade: end a run after this long in play; it counts, with its score then |
--watch-token TOKEN |
let a spectator watch over TCP with this token while the matches play (again for more) |
--max-late SHARE |
a bot late for more than this share of the game's polls is marked too_slow (default 0.5) |
Bots are .py files, native programs (.exe, see C and C++ bots), or the
examples by name: idle, random_walk, hunter, painter. Two versus two needs four bot
pads: bot_pads = [0, 1, 2, 3] in ibf\ibf.toml.
A bot can also be tcp:<token> (or tcp:<token>/<label>, the label naming it in the
output): one that ibf match does not start, which connects by itself over TCP
(configuration, [bridge.tcp]) with that token, from another machine
or a container of its own. ibf match pins the token to the bot's pad
(tcp.allow), waits up to --remote-wait seconds (default 60) for it to take the pad,
and at the end revokes the token, which ends the bot's connection. Such a bot plays its
pad and nothing else; the results name it, without the token. The arena's launcher plays
every match this way (the arena):
ibf run random_walk --url tcp://gamehost:47800 --token 8f3a...e1 --wait 60 # on the bot's machine
ibf match tcp:8f3a...e1 idle # on the game'sWhile it runs, ibf match holds the match lock: commands from other programs that would
change the game, the pads or the run mode are refused, so a bot cannot spoil the match. Nor
can it open the game's pause menu: while the lock is held, Start and Back stay up on every
bot pad (Action(pause=True) does nothing then).
Real time and lockstep
By default the game never waits: each tick a bot answers late, it plays its last answer. A Python bot answers within a millisecond or two, well inside the 16.7 ms between ticks, but a slow bot (a large search, a neural network) falls behind.
In lockstep the game waits after each tick until every bot pad has answered that tick, up to a timeout (1 s by default). Every bot then sees every tick and answers it in time, however slow it is; the game runs as fast as the slowest bot, and never faster than real time. A bot that misses the timeout reads as neutral until it catches up. Lockstep is for training and debugging a bot under a debugger.
With a timeout of one tick and hold, the bots play in real time with a whole tick
for every move: each tick the game waits until every bot has answered or 17 ms have
passed, and a bot that answers later misses that tick only, holding its last input, as a
controller held still would. (A neutral pad would let go of a charging shot's button, which fires it.) This
is how the arena plays (Bots in a Linux container): fair to slow and quick
bots alike, whatever else the machine is doing.
ibf match hunter rival --lockstep --lockstep-timeout 2000
ibf match hunter rival --lockstep --lockstep-timeout 17 --lockstep-miss hold
ibf cmd mode lockstep timeout=17 miss=hold # by hand; `ibf cmd mode realtime` to go back[mode] run = "lockstep" in ibf.toml makes it the default. A snapshot says which mode the
game is in (state.lockstep).
The simulation can also pause and step: ibf cmd sim.pause, ibf cmd sim.step 1,
ibf cmd sim.resume (or self.game.pause(), step(), resume() from a bot). The
overlay stays live, so you can look at one tick at a time.
Recordings
ibf match hunter rival --record # one recording per match, next to the results
ibf cmd record.start # by hand: ibf\recordings\rec-<time>.ibfrec
ibf cmd record.stopAgainst a game over TCP (--url tcp://...), the recordings are on the game's machine, in
its ibf\remote\ folder: the results give that path.
A recording (.ibfrec) holds every tick's state and what each pad answered, compressed
(about 350 KB a minute of play). Read it in Python:
import ibf
rec = ibf.Recording("results/20261005-153000-match1.ibfrec")
for mark in rec.marks: # where the rounds start and end
print(mark.tick, mark.kind.name, mark.a)
for t in rec.ticks(rec.first_tick, rec.first_tick + 600): # ten seconds
ship = t.state.players.get(0) # player 1's ship
print(t.tick, ship.pos if ship else None, t.pads[0].lx) # and what pad 0 answeredReplays
A replay plays a recording to a bot as if it were the game, and compares what the bot answers with what was recorded. The recorded game does not change; the report says where the bot would have played differently.
ibf replay results\20261005-153000-match1.ibfrec hunter.py --pad 0
ibf replay game.ibfrec hunter.py --round 2 --min-same 0.99 # one round; fail below 99% the sameThe report (JSON, and a summary) counts the ticks compared, the same and the different
answers, and lists the first differences. With --min-same it fails below a share, which
makes recordings into regression tests: record your bot once, change its code, and replay.