Nexto Bridge
A reinforcement learning policy driving a live Rocket League client at frame rate
A neural network policy takes over the player’s own car in a running Rocket League session: a BakkesMod plugin inside the game process streams state over localhost to a Python inference loop and applies the returned controller input, with the handover gated so it can never reach an online match.
01The problem
Reinforcement learning agents for Rocket League normally run through RLBot: a dedicated framework, a bot slot, a match built for them. That is a laboratory. The interesting version is the agent driving the same car a human was driving a second ago, in a session the human started, with the camera, name, boost and scoreline untouched.
Doing that means reaching into a running game process, reading its state fast enough to be useful, and writing controller input back - without ever letting it touch a public online match.
02What I built
A C++ BakkesMod plugin hooked into Rocket League’s state and input path, talking to a Python process over two localhost sockets with a fixed binary layout - one direction for game state, one for controller output.
A Python inference loop that loads the TorchScript policy, pins torch to a single thread, builds the observation tensors and samples an action every tick, with no allocation surprises in the hot path.
A handover triggered by holding both stick buttons for 0.4 seconds, switching between MANUAL and agent control. In MANUAL the plugin does not touch the game’s input at all.
03The safety layer
The agent is only allowed to act when Rocket League itself confirms free play, custom training, an offline match or LAN. If the client reports a public online match, or fails to confirm either offline or LAN, control never transfers.
Beyond that: localhost binding only, the locally reported car rather than a player name, rejection of stale packets and wrong sessions, rejection of NaN and infinity in the policy output, and a 250 ms watchdog that hands control back to the game if a fresh command does not arrive.
04Decisions worth explaining
A fixed binary layout, verified from both sides
The two halves of this system are written in different languages and compiled by different toolchains. A struct layout mismatch would surface as an agent that drives into a wall for reasons no log explains. The repository carries a C++ test that asserts the protocol layout and Python tests that assert the same thing from the other side.
Failing to manual, never to nothing
Every failure mode in the bridge - a dead Python process, a stalled socket, a malformed packet, a non-finite tensor - resolves to the game’s native input handling rather than to a car that stops responding. Degradation should be invisible, not catastrophic.
Deleting the weaker models on purpose
An earlier version bundled several bot ports and a controller chord to switch between them. An accidental chord could silently swap the model underneath you. The bundle now contains exactly one policy and the switch is gone: fewer ways to be surprised is worth more than a feature nobody asked for.
Questions about this project?
I am happy to walk through the architecture, the parts that did not work, or the code itself.