XLeRobot dual-arm mobile robot (0.3, omni-wheel base)
Vector Wang and the XLeRobot contributors. A certified SimCloud mobile-manipulator twin: the report below is the evidence, check by check, from its latest validation.
certifiedv1.0.0
- twin
xlerobot- version
- 1.0.0
- firmware
- 17 x feetech-sts3215 12V 1:345; lerobot xlerobot driver: arms and head P16 I0 D43 (the SO-101 driver: D32), zero at mid-range; wheels in velocity mode under 3000 counts/s
- certified
- 04 Oct 2026
- kind
- mobile-manipulator
- toolchain
simtwin 0.1.1- re-checked
- 14 Nov 2026, error
checks passed
20 / 26
6 with a warning
engines passed
2 / 2
mujoco, pybullet
worst tracking error
2.5°
left_gripper, 0.044 rad RMSE
control latency
136 ms
fitted from xlerobot-team/xlerobot-pick-cup-lr-merge-20260904@c3f58d5aa35e
Checks
Each check the validation ran, in the order it ran them, with what it found. Run by simtwin 0.1.1 on 2026-10-04.
| Check | Status | What it found |
|---|---|---|
twin-schema | pass | valid simtwin.twin/1 for xlerobot@1.0.0, 14 joints |
files-present | pass | 5 named and 137 referenced files present inside the twin |
mjcf-loads | pass | compiles in MuJoCo 3.14.0: 22 bodies, 20 joints, 17 actuators, 47 meshes |
urdf-loads | pass | 27 links, 26 joints, root 'chassis' |
joint-names-agree | pass | 17 moving joints, same names, types and tree order: wheel_left, wheel_back, wheel_right, left_shoulder_pan, left_shoulder_lift, left_elbow_flex, left_wrist_flex, left_wrist_roll, left_gripper, right_shoulder_pan, right_shoulder_lift, right_elbow_flex, right_wrist_flex, right_wrist_roll, right_gripper, head_pan, head_tilt; the MJCF adds the base's planar joints base_x, base_y, base_yaw |
joint-axes-agree | pass | 17 joints: axes within 0.001 deg and anchors within 0.002 mm over 13 configurations |
joint-limits-agree | pass | 14 joints agree within 0 (tolerance 0.001 rad) |
twin-limits-agree | pass | twin.json limits match URDF and MJCF within 3e-06 |
units-consistent | pass | hwUnits, policyIO and 3 cameras are consistent |
no-massless-bodies | pass | every moving body has mass (45 links and rigid groups checked) |
inertia-valid | pass | 49 inertias positive definite and within the triangle inequality |
mass-agree | pass | 18 rigid groups agree; total 12.0005 kg (URDF) vs 12.0000 kg (MJCF) |
actuator-ranges | pass | 14 actuators command exactly their joint's range |
actuator-params-agree | pass | actuator parameters of 14 joints agree between twin.json and the models |
self-collision-home | warn | home is collision-free; collision geometry interpenetrates at rest: left_lower_arm and left_shoulder by 4.8 mm, left_shoulder and left_wrist by 4.0 mm, right_lower_arm and right_shoulder by 4.8 mm, right_shoulder and right_wrist by 4.0 mm (a pose the real arm reaches without contact: the collision shapes are larger than the parts there) |
mesh-valid | pass | 30 meshes, 402720 triangles, all readable with plausible size and outward faces; 8 notes: assets/raskogbody.stl: not closed (0 boundary and 1 non-manifold edges): engines use its convex hull for contact, which is fine, but inertia from this geometry would be wrong; assets/raskogwheel1.stl: not closed (0 boundary and 16 non-manifold edges): engines use its convex hull for contact, which is fine, but inertia from this geometry would be wrong; assets/topbase1.stl: not closed (0 boundary and 10 non-manifold edges): engines use its convex hull for contact, which is fine, but inertia from this geometry would be wrong; assets/topbase2.stl: not closed (0 boundary and 32 non-manifold edges): engines use its convex hull for contact, which is fine, but inertia from this geometry would be wrong |
chains | pass | 2 arms: left (5 joints + gripper left_gripper, tool point left_gripperframe); right (5 joints + gripper right_gripper, tool point right_gripperframe); other actuated joints head_pan, head_tilt; holonomic base: base_x, base_y, base_yaw driven by velocity actuators base_x_vel, base_y_vel, base_yaw_vel, 3 wheels (wheel_left, wheel_back, wheel_right) |
components | pass | left: so-101@1.0.0 at components/so-101 (content digest is the certified bundle's), certified 2026-10-04 by simtwin 0.1.1 (sysid pass, fidelity pass); axes within 0.000 deg and joint and tool points within 0.000 mm of the component's at 13 configurations in both models, 8 bodies' mass, centre of mass, inertia and collision geometry; right: so-101@1.0.0 at components/so-101 (content digest is the certified bundle's), certified 2026-10-04 by simtwin 0.1.1 (sysid pass, fidelity pass); axes within 0.000 deg and joint and tool points within 0.000 mm of the component's at 13 configurations in both models, 8 bodies' mass, centre of mass, inertia and collision geometry. Reused from the components, not refitted here: kp, kv, armature, frictionloss, damping, backlashRad, effortLimit, velocityLimit, timeconst, effortRating of every arm joint. Also reused by declaration, from a joint in another role (the same servo under a different load, so an assumption the fidelity check cannot test unless a recording moves them): head_pan <- shoulder_pan, head_tilt <- shoulder_lift |
base-kinematics | warn | 3 wheels read from the models, holonomic: omni wheels realise every body velocity (rank 3, condition 5.5); the driver (lerobot xlerobot (software/src/robots/xlerobot/xlerobot.py at 749abc8): wheel radius 0.05 m, base radius 0.125 m) on this geometry achieves vx at 101.8 %, vy at 101.8 %, wz at 99.0 % of a commanded twist - within 5 %, a calibration difference between the driver's constants and the CAD; at the wheels' 4.6019 rad/s: vx up to 0.27 m/s, vy up to 0.23 m/s, wz up to 1.82 rad/s |
base-stability | pass | statically stable everywhere it was sampled: 12.00 kg; centre of mass at home (-0.001, 0.000) m, 0.423 m high, 154 mm inside the support polygon (base.support, 4 corners); 400 sampled configurations of every joint: closest 141 mm inside; at home it tips forward (braking) past 4.2 m/s^2, backward (accelerating) past 4.1 m/s^2, to the left (turning right) past 3.6 m/s^2, to the right (turning left) past 3.6 m/s^2 (the base's real acceleration is not identified: base-dynamics) |
base-dynamics | warn | no recording drives this base, so its dynamics are not identified: how fast it accelerates, how its wheels slip on a floor, how its servos follow a speed command. The twin moves the base kinematically - velocity actuators on its planar joints reach a commanded twist in about 20 ms, without slip - and its mass properties are estimates (twin.json provenance). Trust it for where the base goes, not for how it gets there. |
crosscheck | pass | mujoco: replayed; pybullet: within 0.276 deg and 0.38 mm of MuJoCo over 4 s. |
sysid | warn | 14 joints reuse so-101@1.0.0's fitted values (verified value for value by the components check; that twin's own sysid fitted them). Nothing reused was refitted on this twin's recordings: the fidelity check measures it there. head_pan, head_tilt take values from a joint in another role (head_pan <- shoulder_pan, head_tilt <- shoulder_lift): an assumption, flagged here |
fidelity | warn | left arm on xlerobot-team/xlerobot-pick-cup-lr-merge-20260904@c3f58d5aa35e episodes 91-201 (open loop): RMS left_shoulder_pan 0.87, left_shoulder_lift 1.92, left_elbow_flex 1.58, left_wrist_flex 1.33, left_wrist_roll 0.27, left_gripper 2.53 deg; latency 136 ms twin vs 115 ms arm; right arm on xlerobot-team/xlerobot-pick-cup-lr-merge-20260904@c3f58d5aa35e episodes 0-90 (open loop): RMS right_shoulder_pan 0.52, right_shoulder_lift 2.34, right_elbow_flex 1.88, right_wrist_flex 1.71, right_wrist_roll 0.29, right_gripper 2.25 deg; latency 137 ms twin vs 107 ms arm (right_shoulder_lift tracks 2.34 deg RMS (pass at 2)); not measured, no recording moves them: head_pan, head_tilt |
dataset-units | warn | xlerobot-team/xlerobot-pick-cup-lr-merge-20260904@c3f58d5aa35e: units deg, range_0_100 confirmed by the values | xlerobot-team/xlerobot-pick-cup-lr-merge-20260904@c3f58d5aa35e: units deg, range_0_100 confirmed by the values; right_arm_shoulder_pan.pos: the values do not decide the unit (lattice spacing 0.087912 = 360/4095 deg: LeRobot degrees mode, or range_m100_100 with a span of exactly 2275 counts (no value past 100 and no clamp to tell them apart)); using the declared deg; right_arm_shoulder_lift.pos: the values do not decide the unit (lattice spacing 0.087912 = 360/4095 deg: LeRobot degrees mode, or range_m100_100 with a span of exactly 2275 counts (no value past 100 and no clamp to tell them apart)); using the declared deg; right_arm_elbow_flex.pos: the values do not decide the unit (lattice spacing 0.087912 = 360/4095 deg: LeRobot degrees mode, or range_m100_100 with a span of exactly 2275 counts (no value past 100 and no clamp to tell them apart)); using the declared deg; right_arm_wrist_flex.pos: the values do not decide the unit (lattice spacing 0.087912 = 360/4095 deg: LeRobot degrees mode, or range_m100_100 with a span of exactly 2275 counts (no value past 100 and no clamp to tell them apart)); using the declared deg; right_arm_wrist_roll.pos: the values do not decide the unit (lattice spacing 0.087912 = 360/4095 deg: LeRobot degrees mode, or range_m100_100 with a span of exactly 2275 counts (no value past 100 and no clamp to tell them apart)); using the declared deg; right_gripper goes 2.5 deg past the twin's limit in 0.3 % of frames |
catalogue | pass | xlerobot-twin@1.0.0: 8 operators (backlash, coarse, compliance, drift, lag, mass, sluggish, weak-grip) over 12 arm joints, sized from this robot's recordings, 13 hazards; valid under mutational's rules |
Fidelity per joint
Commanded against measured joint positions over 202 episodes of xlerobot-team/xlerobot-pick-cup-lr-merge-20260904@c3f58d5aa35e: tracking error as RMSE, and overshoot of each step. Control latency 136 ms.
| Joint | Tracking RMSE (rad) | (degrees) | Overshoot |
|---|---|---|---|
left_shoulder_pan | 0.015 | 0.87 | 0.5% |
left_shoulder_lift | 0.033 | 1.92 | 0.0% |
left_elbow_flex | 0.028 | 1.58 | 0.0% |
left_wrist_flex | 0.023 | 1.33 | 0.5% |
left_wrist_roll | 0.005 | 0.27 | 0.0% |
left_gripper | 0.044 | 2.53 | 0.0% |
right_shoulder_pan | 0.009 | 0.52 | 0.6% |
right_shoulder_lift | 0.041 | 2.34 | 0.0% |
right_elbow_flex | 0.033 | 1.88 | 0.0% |
right_wrist_flex | 0.030 | 1.71 | 0.2% |
right_wrist_roll | 0.005 | 0.29 | 0.0% |
right_gripper | 0.039 | 2.25 | 0.1% |
Engines
The same trajectories run in each engine the twin exports to, and compared.
| Engine | Version | Status | Measured |
|---|---|---|---|
| mujoco | 3.14.0 | pass | maxTrackingRad 0.892 |
| pybullet | 202010061 | pass | maxJointDivergenceRad 0.00481, rmsJointDivergenceRad 0.000748, maxTipDivergenceM 0.000385 |
Policy compatibility
Whether a policy's inputs and outputs - names, order, units - are the twin's.
No policy was checked against this twin.
Downloads
Version 1.0.0, as it was certified. The bundle holds twin.json and every file it names.
bundle.zip · twin.json and every file it names, in one zipCanonical spec
twin.json · simtwin.twin/1: joints, actuators, cameras, policy I/OURDF
model.urdf · for ROS, PyBullet and IsaacMJCF
model.xml · for MuJoCoUSD
model.usda · for Isaac Sim and OmniverseFault catalogue
catalogue.json · mutational's catalogue for this robotReport
report.json · simtwin.report/1, every check behind the verdict
Versions
Every published version. A newer certified version supersedes the one before it; the older reports stay readable.
Badge
For a README or a datasheet: it shows the twin's status and the engines it passed in, and links to the twin's page in the catalogue, where its report is.
Use it in an evaluation
A task names the twin by id and version; SimCloud fetches this bundle for it. Run a policy for N episodes, under perturbations, and read the success rate with its interval.