The AY-Robots policies comparison table showing the five trainable policies with parameter counts, GPU tier, inference latency and minimum episodes
sim-to-realsimulationdomain randomizationSO-100reinforcement learning

Sim-to-Real for a Low-Cost Arm: What Simulation Actually Gives You

AY-Robots ResearchAugust 23, 202617 min read

Which simulators contain an SO-100, what domain randomization really costs in compute, and how large the sim-to-real gap measures in published paired evaluations.

An SO-100 costs about 110 to 150 EUR in parts, and every hour you spend driving it by hand is an hour spent on nothing else. A simulated arm costs nothing per attempt, never strips a gear, and runs a thousand copies of itself on one GPU. The question is what you get back when you carry a simulated policy over to a real arm.

The short version

  • Simulation buys cheap trials, not a free policy. The best published SO-101 result falls from 96.1 percent in simulation to 91.3 percent on hardware, and the two hardest tasks fall 15 and 21 points.
  • The arm does exist in simulation: an MJCF in MuJoCo Menagerie, a ready SO100GraspCube-v1 task in ManiSkill3, an SO-101 course from NVIDIA. None ships your arm, with your backlash.
  • Domain randomization is a tax paid in compute: OpenAI needed roughly 3 years of simulated experience without it and roughly 100 years with it.
  • Simulation is strongest for reinforcement learning and repeatable evaluation. The five policies here are imitation learners, and a physics engine does not produce demonstrations.
  • Validation loss is a poor stand-in for real performance: r = 0.308 in the SIMPLER study, against r = 0.924 for a matched simulated evaluation.

The honest answer, from published numbers rather than launch posts: a great deal for some task shapes, close to nothing for others. Below, which simulators contain an SO-100, what randomization costs, how large the gap measures, and where it will not help an imitation learning project.

How big is the reality gap, in numbers

Several groups have published paired evaluations: the same trained policy scored in simulation and then on the physical robot under the same task definition. Those pairs are the only honest measurement of the gap.

SystemTaskSimulationHardware
Squint on SO-101, Feb 20268 tasks, average96.1 percent91.3 percent (73 of 80)
Squint on SO-101Reach Cube100 percent10 of 10
Squint on SO-101Stack Cube95.0 percent8 of 10
Squint on SO-101Stack Can81.2 percent6 of 10
PPO baseline, same setup8 tasks, average60.2 percent62.5 percent (50 of 80)
OpenAI Dactyl, Shadow HandBlock rotations, visionmedian 33median 11.5
OpenAI Dactyl, Shadow HandBlock rotations, statemedian 50median 13

Reaching transfers essentially perfectly, stacking does not. The gap grows with contact precision, and fastest where a low-cost arm is weakest: holding a pose while the gripper closes. The Dactyl rows give the scale of the problem on far better hardware.

The average hides the shape of the gap

The average gap above is under 5 points, which sounds solved. Split by task and the reach tasks contribute nothing while Stack Can contributes 21. If your task ends with a tolerance smaller than the arm's own repeatability, budget for the tail. From the real side this is the policy only works in one setup failure.

The four gaps, and which one bites a hobby arm

NVIDIA's SO-101 course splits the gap into sensing, actuation, physics and modeling. On an SO-100 the four are not equally dangerous. Three you can attack with randomization. The fourth is hardware.

GapWhat it meansHow it shows up on an SO-100
SensingCamera models, lighting and exposure are not physically faithfulYour webcam has rolling shutter and auto exposure. The renderer has neither, so the image distribution shifts when a cloud passes
ActuationMotor models omit friction, backlash, thermal drift and delaySTS3215 servos run their own position loop at 7.4 V. The simulator drives an idealised actuator, so commanded and executed paths diverge
PhysicsContact, friction and deformables are approximationsA printed jaw on a smooth cube is friction guesswork. Cable, cloth and anything soft is outside what these engines model
ModelingThe URDF is not the machine on your deskPrinter tolerances, assembly slop, hand-tightened screws. The published description is one arm; yours has its own play

NVIDIA is blunt about the actuation term: these are hobby servos, they introduce backlash, and it accumulates along the kinematic chain. The course's answer is SAGE, a Tongji University, Peking University and NVIDIA project that replays the same commanded motion in both domains and compares position, velocity and torque per joint. Bridging what it measures needs a separate learned actuator model, GapONet, and the course lists compensation inside the deployment loop as future work. Measurement comes first because the motor model is what randomization cannot fix.

Bad calibration looks exactly like a reality gap

Check the arm before you blame physics. The lerobot-sim2real tutorial spends its first step here: set the zero position accurately and move every joint to its true mechanical extreme. A sloppy zero offsets every simulated joint angle by a constant and fails identically to a transfer failure. See calibration and arm twitches then sags. Keep the rail at 7.4 V: 12 V destroys STS3215 servos.

Which simulator actually contains an SO-100

This is where people lose a weekend, because the answer differs between the SO-100 and the SO-101, and between an arm description and a usable task. Checked on 23 August 2026.

WhereWhat you getVersion checkedThe catch
MuJoCo Menagerie, trs_so_arm100Simplified MJCF of the 5-DoF Standard Open Arm-100Model v1.3, MuJoCo 3.1.6+, Apache-2.0Simplified: hand-made gripper collision meshes, extra box geoms, position actuators added on conversion
SO-ARM100 repo, Simulation/SO100: one URDF. SO101: URDF plus MJCF, two calibration variantsmain branch, 23 Aug 2026Base collision meshes removed for misbehaving in sim. The LeRobot gripper convention is in neither file
ManiSkill3SO100GraspCube-v1, a digital-twin task with randomization and greenscreening built inManiSkill3, RSS 2025Needs an NVIDIA GPU with working Vulkan, the fast part and the fragile part
Isaac Lab plus LeIsaacSO-101 teleoperation in Isaac Sim, HDF5 recording, conversion to LeRobot formatv3.0.0-beta2.patch1, 2 July 2026A beta line. The Newton backend is in active development and not every environment has a Newton preset yet
gym-hil, LeRobot's own simMuJoCo pick-cube environments, PandaPickCubeGamepad-v0current mainA Franka Panda, not an SO-100. Fine for the tooling, useless as a twin
The gripper number means different things in the two places

In LeRobot the gripper is a linear joint where 0 is closed and 100 is open. The SO-101 simulation README states that this mapping is not yet reflected in the URDF and MuJoCo files. Replay a trajectory into the simulator and the gripper can do the opposite of what you expect: gripper does not close.

The AY-Robots glossary entry for the LeRobot dataset format
Whatever simulator you pick, its output has to become a LeRobot dataset before a trainer will touch it. That format is the interface, not the physics engine.

Domain randomization, with the real default values

Domain randomization comes from Tobin et al. in 2017: rather than make one simulator accurate, make many varied enough that reality looks like another sample. That paper trained a real-world object detector accurate to 1.5 cm from simulated images with non-realistic random textures. Peng et al. extended it to dynamics the same year.

The abstract version is not useful. Here is what the ManiSkill3 SO-100 grasping task randomizes, from the environment source.

python
# mani_skill/envs/tasks/digital_twins/so100_arm/grasp_cube.py
@dataclass
class SO100GraspCubeDomainRandomizationConfig:
    # task agnostic
    initial_qpos_noise_scale: float = 0.02
    robot_color: Optional[Union[str, Sequence[float]]] = None   # set "random" to vary
    randomize_lighting: bool = True
    max_camera_offset: Sequence[float] = (0.025, 0.025, 0.025)  # metres, per axis
    camera_target_noise: float = 1e-3
    camera_view_rot_noise: float = 5e-3
    camera_fov_noise: float = np.deg2rad(2)                     # 2 degrees

    # task specific, applied while the scene loads
    cube_half_size_range: Sequence[float] = (0.022 / 2, 0.028 / 2)
    cube_friction_mean: float = 0.3
    cube_friction_std: float = 0.05
    cube_friction_bounds: Sequence[float] = (0.1, 0.5)
    randomize_cube_color: bool = True

@register_env("SO100GraspCube-v1", max_episode_steps=64)
class SO100GraspCubeEnv(BaseDigitalTwinEnv):
    def _default_sim_config(self):
        return SimConfig(sim_freq=100, control_freq=20)
Domain randomization defaults shipped with the ManiSkill3 SO-100 cube grasping task.

Three numbers there are worth stealing. The camera may move 2.5 cm on any axis and its field of view may wander 2 degrees, which is the tolerance you are granted for aligning a real camera by hand. The cube is not one cube: half size from 1.1 to 1.4 cm, friction centred on 0.3 and clipped to 0.1 to 0.5. Physics runs at 100 Hz, control at 20 Hz.

NVIDIA's course randomizes wider because it generates demonstrations rather than running RL: exposure -4.0 to 3.0, colour temperature 2500 to 9500 K, HDRI backgrounds, camera offsets of 2 cm in x and y and 1 cm in z, camera rotation of 0.05 radians per axis, plus object mass, friction and restitution, joint damping and limits, and actuator delays.

Randomization is not free, and the bill is compute

OpenAI measured this on in-hand cube rotation: about 3 years of simulated experience with no randomization, about 100 years fully randomized, roughly 1.5 against 50 hours of wall clock. Turning every knob on is not a safe default. Randomize the axis you cannot control on the bench, measure, then add the next. The ManiSkill randomization tutorial lists which properties are fixed once the scene loads.

The manual path: reinforcement learning in ManiSkill, deployed zero-shot

The shortest route ending with an SO-100 picking up a cube from a policy trained without a single real demonstration. It comes from the lerobot-sim2real tutorial by Xander Hinrichsen and Stone Tao, built on ManiSkill3. You need an NVIDIA GPU with at least 8 GB, working Vulkan, one camera and an assembled arm.

  1. 1
    Install the simulator, then pin LeRobot

    ManiSkill and SAPIEN depend on NVIDIA drivers and Vulkan, and the demo command tells you whether yours work. The tutorial pins LeRobot to commit a989c795587d122299275c65a38ffdd0a804b8dc because import paths moved upstream.

    bash
    conda create -n ms3-lerobot "python==3.11"
    git clone https://github.com/StoneT2000/lerobot-sim2real.git
    cd lerobot-sim2real
    pip install -e .
    pip install torch
    python -m mani_skill.examples.demo_random_action
  2. 2
    Place the camera in simulation first, not on the desk

    The robot spawns at the origin with the table top at z = 0, so camera position in env_config.json is metres from the arm base. Record the reset distribution to see where cubes actually spawn.

    bash
    python lerobot_sim2real/scripts/record_reset_distribution.py \
      --env-id="SO100GraspCube-v1" \
      --env-kwargs-json-path=env_config.json
  3. 3
    Nudge the real camera until the overlay lines up

    This overlays the simulated render on the live feed and tunes field of view with the arrow keys. The tutorial calls it the hardest step and reports that worse alignment transfers worse. Randomization covers a margin of error, not a mistake.

    bash
    python lerobot_sim2real/scripts/camera_alignment.py \
      --env-id="SO100GraspCube-v1" \
      --env-kwargs-json-path=env_config.json
  4. 4
    Photograph the empty table for greenscreening

    Unmount the arm, shoot the background, point greenscreen_overlay_path at the result. The simulated foreground is composited onto your real background, removing most of the visual gap.

    bash
    python lerobot_sim2real/scripts/capture_background_image.py \
      --env-id="SO100GraspCube-v1" \
      --env-kwargs-json-path=env_config.json \
      --out=greenscreen.png
  5. 5
    Train PPO on rendered images

    The tuned script uses 8 to 10 GB of GPU memory at 1024 parallel environments. The flag says 100 million timesteps, but the authors report 25 to 40 million is enough, about an hour on a 4090, and that training longer sometimes produces worse policies. For your own task, see the custom task tutorial.

    bash
    seed=3
    python lerobot_sim2real/scripts/train_ppo_rgb.py \
      --env-id="SO100GraspCube-v1" --env-kwargs-json-path=env_config.json \
      --ppo.seed=${seed} \
      --ppo.num_envs=1024 --ppo.num-steps=16 \
      --ppo.update_epochs=8 --ppo.num_minibatches=32 \
      --ppo.total_timesteps=100_000_000 --ppo.gamma=0.9 \
      --ppo.num_eval_envs=16 --ppo.num-eval-steps=64 \
      --ppo.no-partial-reset \
      --ppo.exp-name="ppo-SO100GraspCube-v1-rgb-${seed}"
  6. 6
    Run the checkpoint on the arm

    Use a cube around 2.5 cm, the middle of the trained size range, inside the spawn region from step 2. The tutorial recommends control frequency 15 on SO-100 hardware.

    bash
    python lerobot_sim2real/scripts/eval_ppo_rgb.py \
      --env_id="SO100GraspCube-v1" \
      --env-kwargs-json-path=env_config.json \
      --checkpoint=path/to/ckpt.pt \
      --no-continuous-eval --control-freq=15
Step through the first rollout by hand

Keep --no-continuous-eval on for the first deployment; it makes the arm wait for a keypress before each action. Reinforcement learning finds behaviours that score well and look nothing like a demonstration, which on a printed gripper can mean driving the end effector into the table. Keep a hand on ctrl+c, which stops the script and disables torque. Test more than one checkpoint: several with identical simulated scores behave differently on hardware.

Two routes to a policy that works on your desk

Build a digital twin of your workspace, train with reinforcement learning against a reward function, deploy zero-shot. It is the only practical way to get millions of interactions on an arm that costs 110 to 150 EUR in parts.

  • One NVIDIA GPU with 8 GB or more, correct drivers, working Vulkan. This is the most common failure, not the RL.
  • An afternoon of camera alignment, to be redone after you move the desk.
  • About an hour of PPO per task on a 4090, plus the runs you throw away.
  • A reward function. For SO100GraspCube-v1 it is roughly five lines, which is why it does not generalise.
  • Acceptance that the result is one task, with no language conditioning and no transfer to the next object.
Reproduce the better version

The lerobot-sim2real author recommends reproducing squint instead: same base, faster training, better transfer, eight SO-101 tasks rather than one. Published result 96.1 percent in simulation and 91.3 percent over 80 real trials, after 15 minutes on a single RTX 3090.

What simulation will not do for this project

The gap between what simulation is good at and what an SO-100 project needs is wider than the sim-to-real gap itself. Reinforcement learning wants a reward function and millions of resets. Fine-tuning a VLA wants demonstrations of a task you can name in a sentence. Only the first is what a physics engine is built for.

Simulation on a low-cost arm project
What it buys you
  • Trials and failures are free. The route above burns 25 to 40 million environment steps for one cube grasp, which no physical arm survives.
  • You can vary what reality will not: cube size, friction, lighting, background, camera field of view, robot colour.
  • Reset is instant. In real data collection the human resetting the scene is the bottleneck.
  • Evaluation repeats exactly. On hardware you cannot re-run yesterday's trial.
  • It is the only realistic way to run reinforcement learning on this class of hardware.
What it costs you
  • The simulated arm is not your arm. Menagerie's model is simplified, the SO-101 description dropped its base collision meshes, and the gripper convention is in neither file.
  • Randomization multiplies compute, roughly 33x in the one case where both numbers were published.
  • It needs an NVIDIA GPU with working Vulkan, often a second machine or a rented one.
  • Camera alignment is manual, and with joint calibration it is the first thing to suspect when transfer fails.
  • None of the five trainable policies learn from a reward function. A simulator changes what you teleoperate, it does not remove it.
The AY-Robots policies comparison table with parameters, GPU tier, latency and minimum episodes for the five trainable policies
All five trainable policies are imitation learners. Minimum episodes runs from 30 for SmolVLA to 50 for the rest, and those episodes have to come from somewhere.

Feeding simulated data to a real policy

The bridge between the two worlds is the dataset, not the policy. If your simulator writes episodes in LeRobot format, a trainer cannot tell them from recorded ones. LeIsaac does this for Isaac Lab: teleoperate in Isaac Sim, record HDF5, convert, fine-tune. NVIDIA's course then co-trains on a mixture.

The recommended mixture is deliberately lopsided: about 5 real episodes against 70 to 100 simulated ones. The real handful is not there for volume, it anchors the sensing and actuation terms the renderer got wrong. Their sim evaluation of a vial pick-and-place policy is quoted at 50 to 70 percent.

PolicyDataset format requiredMinimum episodesGPU tier
GR00T N1.7LeRobot v2.0 or v2.150A100 80 GB or H100 80 GB
GR00T N1.5LeRobot v2.0 or v2.150A100 80 GB or H100 80 GB
Pi0.5LeRobot v3.050A100 80 GB or H100 80 GB
SmolVLALeRobot v3.030RTX 4090 or any 24 GB card
ACTLeRobot v3.050RTX 4090 or any 24 GB card
Version mismatch is the trap that eats an afternoon

A LeRobot v3.0 dataset crashes the GR00T loader and has to be converted down to v2.1. Simulation tooling emits whatever version its LeRobot pin produced, so a dataset that trains ACT can reject on GR00T N1.7 for reasons unrelated to your task. Start at dataset rejected as v3, not at your physics. Details in the dataset docs.

No arm on the desk yet?

Drive a real SO-100 in the browser before you spend a weekend building a digital twin of one. No signup, queue-based, and the same class of arm the public datasets were recorded on.

Drive a real arm

Does a simulated score predict a real one?

This is the underrated use of simulation, and it pays off even if you never train in it. The SIMPLER study built simulated replicas of two real robot setups and ran paired evaluations of six checkpoints in both. The question was not whether success rates match in absolute terms, but whether they rank policies in the same order, which is all model selection needs.

How you rank checkpointsMMRV, lower is betterPearson r, higher is better
Validation MSE on held-out episodes0.3750.308
SIMPLER, variant aggregation0.1430.778
SIMPLER, visual matching0.0560.924

Averaged over three task families on the Google Robot setup. Sit with the top row: picking a checkpoint by validation loss, the habit carried over from supervised learning, correlates with real performance at r = 0.308. A matched simulated evaluation reaches 0.924. That is the quantitative version of the loss curve looking fine while the arm does nothing, which has its own failure page.

Two caveats the authors state themselves: the environments cover rigid objects only, and the greenscreening assumes a fixed camera and does not reproduce object shadows. Both apply to a desk with one webcam and a printed gripper.

The AY-Robots arena, a sortable table of 85 vision-language-action models with 332 benchmark results
Most published VLA numbers are simulation benchmarks. The arena links each of the 332 results to its source so you can check what was measured.

That matters when you read a leaderboard. Most results on the arena come from simulated benchmarks. They signal relative capability, not what a checkpoint does on your desk under your lighting.

A decision rule that holds up

  • Contact-light reaching or a coarse pick from a known spawn area, and you want reinforcement learning: simulate. This is what the tooling was built for.
  • Precise contact, insertion, deformables, cable routing, or anything where the gripper must feel something: record real episodes.
  • Comparing two checkpoints before spending arm time: a matched simulated evaluation ranks them better than validation loss.
  • A VLA that follows a language instruction: record real demonstrations, then consider adding simulated ones. Start at train your first policy.
  • One free evening, no NVIDIA GPU, an assembled arm: record 50 episodes. That is the minimum for four of the five policies and beats fighting Vulkan.
  • Fast reactive task: none of this saves you from the control loop, 20 ms per action step for ACT and 485 ms for Pi0.5. See inference latency.

Simulation moves work, it does not delete it. On the RL route you trade demonstration hours for camera alignment, GPU time and reward shaping. If the task is geometric and repetitive, that trade is good. If it is the sort of thing you would show a person once, imitation learning from real episodes gets there faster, and how to collect high-quality VLA training data will do more than any randomization. On the simulator side, the Isaac Lab write-up covers the framework.

Can I train a policy entirely in simulation and skip recording on the real SO-100?

For a narrow geometric task with a reward function you can write down, yes. The ManiSkill3 route reaches a working cube grasp on a real SO-100 with no real demonstrations, and the SO-101 task set reports 91.3 percent over 80 real trials from pure simulation training. For a language-conditioned VLA task, no: those models learn from demonstrations, and a physics engine does not produce demonstrations without a scripted expert or a human teleoperating inside it.

Does AY-Robots run a simulator?

No. The platform covers teleoperation, dataset recording, cloud fine-tuning and serving the policy back to the arm. Run the physics engine yourself. The bridge is the format: anything that writes LeRobot v2.1 or v3.0 episodes can be imported from a Hugging Face repo id or your own machine and trained like any recorded dataset.

Which simulator should I start with for an SO-100?

ManiSkill3, because SO100GraspCube-v1 already exists and the path is documented end to end with working defaults. Use MuJoCo Menagerie's trs_so_arm100 for a lightweight MJCF when you want kinematics or a visual check without a GPU. Move to Isaac Lab plus LeIsaac for photorealistic rendering and teleoperated collection inside the simulator, accepting that the current line is a beta.

How much domain randomization is enough?

Start with the axis you cannot control on the bench: camera pose, lighting and object friction, which is what the ManiSkill3 defaults randomize at 2.5 cm of camera offset per axis, 2 degrees of field of view and cube friction around 0.3. Add axes only when a specific failure points at one, because every extra axis costs training time.

My policy hits 99 percent in simulation. What does that predict on hardware?

Less than you would like, and how much less depends on the task. In the SO-101 task set the reach tasks transferred at 100 percent while Stack Can fell from 81.2 percent in simulation to 6 of 10 on the arm. Treat a simulated success rate as necessary, not sufficient, and test several checkpoints with the same simulated score.

Ready for high-quality robotics data?

AY-Robots connects your robots to skilled operators worldwide.

Get Started