SOLIDWORKS - MUJOCO - ROS2 - PPO - UBUNTU - RVIZ - GITHUB
DEGREES OF FREEDOM
10 DoF
TARGET MASS
~10 Kg
JOINTS PER LEG
5
PARALLEL ENVS
2,048
RL ALGORITHM
PPO
CONTROL RATE
250 Hz
VESTO is a wheel-legged robot with two articulated legs, each ending in a driven wheel. The idea is simple: combine wheeled efficiency on flat ground with legged adaptability on rough terrain. In practice, that combination is anything but simple to build.
Nothing here was bought off the shelf. The geometry, the simulation environment, the reward functions: all of it was worked out from a blank SolidWorks file. The project covers mechanical design, physics simulation, and machine learning, and every decision is documented.
A note on names, since three show up below and each means something different: VESTO is the public project. VLR-01 is the robot itself, the designation on the CAD files, the MuJoCo scene, and every figure caption. The v1–v18 labels further down are neither of those: they're successive reinforcement-learning policies trained on VLR-01, the way a chess engine gets versioned separately from the hardware it runs on.
The starting question was: What would a wheel-legged robot look like if it were designed specifically for reinforcement learning, not retrofitted to support it? Most platforms get adapted after the fact. This one was designed with training constraints in mind from the first sketch.
Two existing robots shaped the direction. The Rai robot showed that a minimal two-leg platform with wheels at the tips does not need to be mechanically elaborate to perform well. Boston Dynamics' reverse-knee leg geometry, visible on Spot and Atlas, showed how the shape of a leg alone changes how a robot absorbs impact and uses energy during movement.
VESTO takes the Rai robot's compact two-leg structure and applies that digitigrade leg profile to it. The result is a platform that is light, mechanically clear, and built to train well.
Pure-wheeled robots fail on stairs. Pure legged robots are slow and heavy. Wheel-legged hybrids sit between those two failure modes, and that is one of the most active and underexplored areas in robotics research today. That is why this robot exists.
The robot has five joints per leg. Each one has a specific job, and the five together give the robot a range of motion that a simpler design cannot produce.
Joint Anatomy
Starting at the torso and working down: J1 (hip pitch) swings the leg forward and backward. J2 (hip roll) tilts the leg sideways. J3 (thigh rotation) is the most distinctive joint in the design. Its axis runs straight down the shank, so spinning it re-aims the wheel in the ground plane without touching torso pitch or leg height at all, steering decoupled from balance, which is the whole point. J4 (knee pitch) bends the shank, controlling how high or low the body sits. J5 (wheel drive) is continuous and propels the robot forward.
Most wheel-legged platforms, including commercial ones like the Unitree B2-W and ANYmal-W, have three or four joints per leg and steer by tilting or differential-driving the wheels. The fifth joint, the axial thigh rotation, is what makes steering on this robot qualitatively different: no existing open wheel-legged biped separates the steering primitive from the balance primitive this way.
Thigh link: 215.6 mm, shank: 213.5 mm, wheel radius: 80.5 mm, hip-to-hip track: 90 mm. Standing height at the 60° knee pose used for training: 438 mm. Two configurations make the kinematic chain lose rank: hip alignment (J1 = J2 = 0) and knee lock (fully extended or folded). Both are penalized directly in the RL reward function, not just noted as a kinematics footnote.
3D Robot Viewer
Every color on the robot maps directly to the VESTO brand palette. This was not a stylistic choice made after the fact. The robot was modeled in these colors from the beginning because the physical design and the brand identity were developed together. What you see on the robot is the same visual language used across the entire VESTO project.
This matters because VESTO is not just a research platform. It is a public-facing project with its own identity. The colors on the joints, links, and wheels are the same ones that appear in every document, every render, and every slide. When someone sees VESTO, they should recognize it immediately, in any context.
The physical robot, when built, will carry these exact finishes. This transforms the palette from a superficial design choice into a functional system where structural roles, joint axes, and ground contacts are immediately identifiable at a glance.
Mass, centre of mass position, and the full inertia tensor for every link come out of SolidWorks and go directly into the simulation model. Joint limits are measured from motion studies. Contact geometry is simplified from mesh to primitive shapes for simulation speed.
The URDF was exported using SW2URDF, then audited manually. Seven issues were found and corrected before the file was converted to MuJoCo's MJCF format.
The total simulated mass of approximately 10 kg includes the battery sitting at the very bottom of the torso enclosure. That placement is deliberate. It keeps the centre of mass at around 59% of standing height, which is the range where a wheel-legged biped is stable enough to learn balance without being too rigid to move.
Before reinforcement learning starts, a simple PD controller runs the model. If the robot holds a standing pose for 30 seconds under proportional-derivative control, the physics are correct. If it cannot, something is wrong with the mass or joint configuration and needs to be fixed before training begins.
The full robot description file (URDF) is available for anyone interested in loading, studying, or building on the VESTO kinematic model. The link below will be updated when the file is publicly released.
MuJoCo (Multi-Joint dynamics with Contact) is the physics engine underneath everything here, but the part that actually made training tractable is MJX, MuJoCo's JAX backend, paired with Brax's PPO implementation. Instead of stepping one robot through one simulation, MJX steps 2,048 copies of VLR-01 in parallel on the GPU (an RTX 4050, 6 GB). That parallelism is the difference between a 200-million-step PPO run finishing in about 75 minutes and the same run taking days on CPU.
Control runs at 250 Hz. That number isn't arbitrary: early versions ran slower control rates, and balance simply wasn't learnable; raising it to 250 Hz was the first real unlock. Each training step still produces the same three things any RL setup needs: an observation of what the robot currently senses, an action specifying joint torques, and a reward measuring how well it's doing. A second, much smaller Gymnasium environment (8 CPU instances) exists alongside the GPU path purely for fast sanity-checking a policy after training, without needing the GPU pipeline spun up.
The plan once called for five skill levels (balance, then wheel drive, then full leg articulation, then walking, and then velocity commands) trained as a literal curriculum, each level's policy warm-starting the next. In practice, training organized itself around a different and blunter ladder, one with a pass/fail gate at each rung instead of a skill list:
Clean legs (under 6° posture deviation), 30-second standstill balance, ±0.3 m/s driving forward and backward, and no continuous spin. Verified headless in CPU MuJoCo, not just by the MJX training reward: those two disagree more often than not (see below). Reproduced across two independent GPU runs with the same recipe.
Survive ≥0.4 m/s shoves from any direction, ±5° slopes, bumps, and ±20% mass/friction variation. Matching the hand-tuned LQR controller's flat-ground balance isn't worth deploying on its own: RL only earns a place on the robot if it beats hand-tuning at what hand-tuning can't do. Several domain-randomization attempts on top of the Stage A recipe have so far traded robustness for the very balance that Stage A had already achieved; that tension is the open problem right now.
The Stage A policy is already exported to a portable NumPy MLP (no JAX dependency at inference, verified to match the original policy's output to within 3.4×10⁻⁶), and a DDS controller skeleton exists to run it on the same interface as the hand-tuned controller. Wiring it up and confronting a real control loop is the next concrete milestone once Stage B clears.
Getting from nothing to a clean, reproducible Stage A policy took eighteen tracked attempts, and the early ones taught a misleading lesson. One version balanced robustly for the full 30 seconds under pushes and sensor noise but with contorted, energy-wasting legs and a slow, uncommanded spin. Tightening the posture reward to fix the legs produced clean legs and a policy that fell over in a second or two. The obvious read was "the reward weights are wrong." They weren't.
The real cause turned up once one version isolated it: every brittle policy was holding heading fixed with a yaw angle penalty. The robot falls by rolling, and recovering from a roll needs to countersteer: yaw into it, the way a cyclist corrects a skid. Headholding made that illegal. Dropping the yaw-angle penalty and controlling heading through yaw rate instead took standstill balance from 2.4 seconds to a clean 30 seconds in one change. It still rolled over driving forward at 18.9 seconds, and the heading drifted roughly 100° over a 30-second hold. Freeing recovery cost directional control. Progress, not an answer.
Then came the harder problem. Re-running the exact same configuration (same code, same seed, same environment) produced wildly different outcomes: one run balanced 30 seconds, the byte-identical rerun fell at 1.8 seconds. The task wasn't the unreliable part. PPO's optimizer was.
The fix was one line: gradient-norm clipping, max_grad_norm = 1.0. Across eleven training runs spanning seven reward and observation iterations, every run without clipping either collapsed mid-training or plateaued well below its peak; every run with it stayed stable.
The clipped policy reproduced cleanly across two independent GPU runs and is the version that finally clears standstill, forward, and backward driving at a full 30 seconds each, with the cleanest legs (2.8°–3.3°) of any version tried. Exported and tested headless under an identical 5° pitch perturbation, the unclipped policy falls at 4.97 seconds; the clipped one holds the full 15-second test with under 1° of residual pitch error. This result, an isolated, same-seed ablation plus a closed-loop deployment test, is the subject of a dedicated paper draft (see Research output, below); it's the closest thing in the whole project to a publication-ready result.
VESTO is a research platform, but it does not behave like one. It runs a small library of implemented gestures: short, deliberately choreographed motions it performs on command, layered on top of the live balance loop so it stays upright the whole time. These are movements, not static poses: each one is scripted in the controller and tuned for how it reads, then runs while the inverted-pendulum balance keeps the machine standing underneath it.
They are triggered live, from the keyboard or by voice ("nod", "spin", "dance") through the speech-command layer, so the robot has a bit of character without ever leaving its balance controller and without any pre-baked animation.
The set below is what the robot can actually do today. Polished renders and clips of each are still to be captured; the grid names the real gestures in the meantime.
What makes these harder than they look: the robot is a two-wheel inverted pendulum, so it is never passively stable. A bow shifts the centre of mass forward; a bob drives the legs down and back up; a spin dumps differential torque into the wheels. On a statically stable robot these are throwaway animations. Here, every one of them is a disturbance the balance controller has to absorb in real time while still keeping the machine upright. The gesture and the balancing run on the same body at the same instant.
That is the actual point of the set. It is not decoration bolted onto a research demo; it is the clearest, most human-readable evidence that the controller is robust enough to do something deliberately destabilizing and recover from it on every cycle. Each slot above takes a render or a short clip of the gesture running live in the simulator. The motions exist today; capturing them cleanly is the remaining step.
A 2D LiDAR is simulated by raycasting directly through MuJoCo's own geometry (mujoco.mj_ray) from the robot's tracked pose, publishing 360 rays as a standard ROS2 /scan topic with no separate sensor simulator needed. slam_toolbox consumes that scan plus wheel odometry and builds a live occupancy grid: a 7×7 m test room resolves into a clean 138×138-cell map at 5 cm resolution, with all four pillars and every wall distinguished from open floor. Nav2 takes that map, the robot's pose, and a goal point, and has driven the simulated base end-to-end to a reported "goal succeeded."
The part that doesn't come for free from gluing together standard tools: this base doesn't stand still. A body-mounted LiDAR pitches and rolls with every balance correction, which corrupts a naive 2D scan; almost no SLAM/Nav2 reference setup is built on a dynamically balancing platform. The laser frame here is tilt-decoupled, tracking yaw only, so the scan stays usable while the robot is actively correcting its own balance. A second fix, a balance-aware mapping from Nav2's /cmd_vel to the controller's stick input, keeps Nav2's velocity requests inside what an underactuated balancing base can actually track without falling.
The entire stack runs without Gazebo. A custom MuJoCo-to-ROS2 bridge (wl_ros2_bridge / wl_state_bridge) publishes sensor data and accepts velocity commands as standard ROS2 topics, so the same software that drives the simulated robot can drive a real one with minimal changes. Right now those velocity commands reach the robot through the hand-tuned LQR controller, not the RL policy; wiring the learned policy into this loop is part of Stage C, above.
VESTO is not affiliated with a university lab or a company. Every decision in it, from the leg geometry to the reward function structure, is original. The problems encountered along the way are documented alongside the solutions, including the ones that did not work.
The goal is not just a robot that moves. It is documentation detailed enough that someone else could build on it, extend it, or replicate it. That is what makes open-source robotics research worth doing.
The target is a wheel-legged biped that navigates a real indoor environment without teleoperation, without a pre-mapped space, and without a hardcoded gait. It sees the room with a depth camera, builds a map, plans a path, and walks it using a learned policy. The work on this page is what that requires.