The problem in brief
Six robots live in small rooms: a 6 x 6 floor surrounded by walls, scattered with keys, balls and boxes of six colours, plus some coloured "tokens". Each robot is given an English mission of one of three kinds (go to an object, pick up an object, put one object next to another), phrased in many ways ("grab the blue key", "collect the blue key"...). You see one snapshot at a random moment of the robot's work: the room, the robot's position and facing direction, the mission and what it carries. You must predict its next action among six (up, down, left, right, pick up, drop). The robots do not always take shortest paths, and each robot has its own habits. You get 60 000 labelled snapshots (10 000 per robot); the notebook must run in 5 minutes on the hidden test set.
The organisers published the graded splits after the contest, so we report our final score on leaderboard B (3 600 snapshots, the split that decided the ranking). All choices were made on a 10 % holdout of the training set.
How you are scored
Accuracy is computed separately for each robot, then averaged, on a 0 to 100 scale:
Reference points, from the official grader's runbook (code/grading-original/RUNBOOK.md in the IOAI-2026 repository): the baseline that always answers "up" scores 20.58 on leaderboard B, and the authors' own solution, per-robot neural networks, scores 71.14. The official ranking then normalises each task (IOAI 2026 rules, technical appendix): , where Min is the baseline and Max the larger of 0.9 times the committee's solution and the best contestant's raw score. In the published results the best normalised score on this task was 86.2.
First look at the data
The first thing to understand is what the robot is trying to reach, because the mission alone does not explain the actions. Counting tokens gives the key: every room has either 6 tokens, exactly one of which has the robot's own colour (colour id = robot id), or 5 tokens with none of the robot's colour, and the robot is never seen carrying a token. So each robot first collects its own token, which disappears, and only then does its mission. In about half of the snapshots (48 %), the robot is still heading for its token.
This gives a sequence of sub-goals that you can compute from the snapshot alone:
- own token still on the floor: walk next to it, face it, pick it up;
- "go to X": walk next to X;
- "pick up X": walk next to X, face it, pick it up;
- "put X next to Y": pick up X, then walk until X can be dropped on a free cell next to Y, and drop it.
Then look at what the robots actually do in a clean situation. In the right panel below, the robot's own token is straight above, two or more cells away, with a free path. If the robot already faces up, it goes up 70 % of the time. If it faces down, it goes up 70 % of the time too, but otherwise mostly answers "left" or "right": exactly what a robot that can only turn by 90 degrees would do to turn around, once the turns are written as absolute directions. And there is real noise: "pick up" in front of an empty cell, or "left" when facing the token (the left panel's robot is next to its token, facing it, and moves "left" into it instead of picking it up). About 18 % of all "pick up" actions are performed in front of nothing that can be picked up.

So the best possible score is far from 100: the behaviour is partly random, and the job is to predict the most likely action in each situation.
A first baseline
Always answering "up" gives 20.6 (the official baseline). Answering with the shortest-path move towards the current sub-goal is much better, but the hand-written rules quickly become a maze of exceptions (turns, blocked cells, ties between two equally short moves). It is more effective to compute features that describe the situation and let a model learn each robot's habits:
- the sub-goal type (token, reach, pick, pick-for-put, drop) and the mission type;
- the BFS distance (moves through empty cells) from the robot to a cell next to the sub-goal, and the change of that distance for each of the four moves (a move into an obstacle only turns the robot, so its distance does not change);
- what is in front of the robot and on each of its four sides; whether it faces its target; whether the cell in front is a valid place to drop;
- the offset to the target, its Manhattan distance, the direction the robot faces, what it carries.
A gradient-boosted tree model (HistGradientBoostingClassifier, 500 trees, robot id as a categorical feature) on these 37 features reaches 63.8 on the holdout and 61.9 on leaderboard B. Training one model per robot is worse (61.1 on the holdout): the robots share most of their behaviour, so splitting the data divides it by six for little gain.
The key idea
Turn a perception problem into a planning one. A raw 8 x 8 x 2 grid plus a sentence is hard to learn from: the model would have to discover on its own that a token of the robot's colour matters, that a "put" mission has two phases, and what a shortest path is. Computing the current sub-goal and the BFS distances by hand injects exactly the structure of the task. After that, what remains to learn is small: each robot's habits (how often it turns, which of two equally good moves it prefers, how noisy it is), which a model can estimate from 10 000 examples per robot.
The second ingredient is to keep the spatial picture as well, because features cannot list every relevant configuration (a box next to the target, a wall around the corner). The final model therefore combines both:
- a small CNN reads the grid as 26 one-hot channels: object type per cell, "is my own token", "is mission object 1 or 2", plus the direction, carrying state, mission type and robot id broadcast to every cell;
- its output is concatenated with the standardised hand-made features;
- a dense layer produces six separate 6-way outputs, one per robot, and we read the one of the robot in the snapshot. The robots share the whole network except the last layer, which is a cheap way to get "per-robot models" without dividing the data.
The full solution
- Parse the mission with a regular expression (colour + object, one or two of them) and its first word (go / pick / put family).
- Compute the sub-goal and the 37 features (BFS on the 6 x 6 floor, at most 36 cells, so this is instant).
- Build the 26-channel grid encoding.
- Train the CNN + features network for 18 epochs with AdamW and a one-cycle learning rate up to , batches of 256, on all 60 000 snapshots (about 6 minutes on 2 CPU threads, much less on the contest GPU).
- Predict the argmax of the robot's own output head.
z = self.f(torch.cat([self.c(grid).flatten(1), feats], 1)).view(-1, 6, 6)
logits = z[torch.arange(len(robot)), robot] # the head of this snapshot's robot
We also tried an egocentric grid (re-centred on the robot, 15 x 15), a common trick in gridworlds. It overfitted (holdout 65.9 at best, then falling to 64.8), probably because the walls' absolute position matters in such a small room.
Results
Mean per-robot accuracy x 100. Holdout = 10 % of the training set; leaderboard A and B are the official hidden test splits, scored with the official metric.
| approach | holdout | leaderboard A | leaderboard B |
|---|---|---|---|
| always "up" (official baseline) | 20.81 | 20.58 | |
| boosted trees on 37 sub-goal features | 63.8 | 61.7 | 61.9 |
| same, one model per robot | 61.1 | ||
| egocentric CNN + features (12 epochs) | 64.8 | ||
| CNN on the grid + features, one head per robot (final) | 66.5 | 63.2 | 62.2 |
| authors' solution (per-robot MLPs, from the runbook) | 72.03 | 71.14 |
Our final model scores 62.2 on leaderboard B, three times the baseline but 9 points below the authors' 71.1. With the official normalisation, if the committee's reference is the authors' 71.14 (so Max = 0.9 x 71.14 = 64.0), 62.2 would be worth about 96 normalised points, above the best contestant's 86.2; but the exact committee reference is not published, so treat that conversion as indicative.
Two honest observations. First, the official test splits are about 3 to 4 points harder than our random holdout (also true for the public test file), so the training rows are not a perfect preview of the test rows; we did not find out why (layouts never repeat between train and test, and the mix of sub-goals is the same). Second, the drop phase of "put" missions is our weakest part (about 50 % accuracy on leaderboard B against 67 % in the token phase): deciding where a robot will choose to drop its object is the hardest pattern to read from a single snapshot.
What to remember
- Find the hidden sub-goal first. Counting tokens revealed that every robot collects its own-coloured token before its mission; without that, half the snapshots look random.
- Inject the planning you know. BFS distances and "what each move would change" turn a hard perception problem into an easy tabular one; the model then only has to learn habits and noise.
- Know the ceiling. When behaviour is stochastic, 100 % is impossible: estimate how noisy the labels are (here, pick-ups in front of empty cells) before chasing the last points.
- Share, then specialise. One network with a separate output head per robot uses all the data for the common behaviour and still lets each robot differ, which beat one model per robot.
- Validate like the test. A random holdout overestimated the test score by 3 to 4 points; when the organisers warn about "new combinations", expect a gap and keep a margin.