
The simulator ignores floor flatness, friction and the energy actually transferred to the ball. Each deviation became a correction coefficient in the code, so the strategy tuned in simulation still worked on the field.


Every object on the field gets a repulsion coefficient. If something sits on the path, the next x/y waypoint is recomputed on the fly, so the robot rounds opponents on the shortest safe line.
Opponent near the ball: switch to defence and hold five seconds to see if it stays. Ball behind us: circle around it to line up with the opposing goal before the shot.
Two modes running in parallel threads sharing memory, so the team reacts to the ball and to the opponent at the same time without blocking either loop.
One process per team, one thread per role. Attack and defence read the same camera frame from a shared data object and write their own motor commands, so neither loop waits on the other.
# simplified strategy runner
class CommonData:
ball, robots, opponents = None, {}, {}
SPEED_K = 1.956 / 3.616 # field / simulator
def attack(cd):
while playing:
if behind(cd.ball, me): # circle to face the goal
goto(orbit(cd.ball, goal), avoid=cd.opponents)
else:
kick_towards(goal)
def defense(cd):
while playing:
if near(cd.opponents, cd.ball):
hold(5.0) # wait, see if it stays
block_line(cd.ball, own_goal)
Thread(target=attack, args=(cd,)).start()
Thread(target=defense, args=(cd,)).start()


A digital twin lets you break a strategy a hundred times before a robot ever moves.
Measured gaps became coefficients; the model got closer to the field every run.
Parallel tasks per RUN kept eight people moving; Kanban kept them aligned.