How Joint Friction Is Randomized in Microduck RL Training
Microduck RL randomizes joint friction through a dedicated BAM actuator friction-scale hook invoked on every environment reset, using curriculum-driven ranges that evolve throughout training.
The pollen-robotics/microduck_rl repository implements domain randomization for a 14-DOF quadruped robot where standard MuJoCo friction parameters are bypassed in favor of a custom actuator model. This article examines the exact mechanism, source files, and configuration patterns that enable per-episode joint friction variation.
Why Standard MuJoCo Friction Randomization Doesn't Apply
Microduck uses BAM (Bam actuator model)—voltage-controlled XL330 servo units modeled in src/mjlab_microduck/actuator/friction_dr_bam.py. Under this model, MuJoCo's native dof_frictionloss field is zeroed because the actuator supplies its own velocity-independent friction internally.
This design choice makes the conventional dr.dof_frictionloss domain randomizer ineffective. Instead, friction variability must be injected through the actuator's proprietary friction_scale parameter.
The Core Randomization Function: randomize_bam_friction
All joint friction randomization flows through a single function defined in src/mjlab_microduck/tasks/mdp.py:
def randomize_bam_friction(
env: ManagerBasedRlEnv,
asset_cfg: SceneEntityCfg = _DEFAULT_ASSET_CFG
):
"""Randomize the BAM actuator friction scale per-environment."""
asset = env.scene[asset_cfg.name]
# Sample scale factors from curriculum-provided range
scale = torch.empty(env.num_envs, device=env.device).uniform_(
env.curriculum["joint_friction"].low,
env.curriculum["joint_friction"].high
)
# Apply to all servo actuators (14 joints, excluding passive wheels)
for actuator in asset.spec.actuators:
if actuator.name.startswith("servo_"):
actuator.friction_scale[:] = scale
This function operates in reset mode, meaning it executes fresh randomization every time environments are reset—typically once per episode.
Event Registration Pattern
Every Microduck task configuration registers the friction randomization event following a consistent pattern. In src/mjlab_microduck/tasks/microduck_velocity_env_cfg.py:
cfg.events["randomize_joint_friction"] = EventTermCfg(
func=microduck_mdp.randomize_bam_friction,
mode="reset", # Apply on every environment reset
)
The same registration appears across task families, including microduck_roller_standup_env_cfg.py and other specialized environments. This ensures consistent friction randomization behavior regardless of the specific control objective.
Curriculum-Driven Randomization Ranges
Microduck employs progressive curriculum to control how friction ranges evolve during training. The curriculum is defined per-environment:
cfg.curriculum["joint_friction"] = CurriculumTermCfg(
func=microduck_mdp.joint_friction_curriculum,
params=dict(
event_name="randomize_joint_friction",
ranges_stages=[
{"stage": 0, "ranges": (0.6, 1.0)}, # Wide initial variation
{"stage": 1, "ranges": (0.8, 1.0)}, # Tighter as policy improves
# Subsequent stages further narrow the distribution
],
),
)
The curriculum mechanism:
- Starts broad—exposing the policy to extreme friction variations early
- Progressively tightens—focusing on realistic, deployment-relevant ranges
- Prevents catastrophic forgetting—maintaining sufficient variability for robustness
Architectural Flow: From Reset to Physics
The end-to-end friction randomization pipeline operates as follows:
- Environment reset triggered—episode termination or timeout
- Event manager queries registered events—finds
randomize_joint_frictionwithmode="reset" randomize_bam_frictioninvoked—samples from current curriculum stagefriction_scalewritten to actuators—all 14 servo joints updated in-place- MuJoCo step executes—scaled friction contributes to velocity-independent damping term
This non-accumulative design (preserved by mjlab 1.3.0's dr.* operations reading compile-time defaults each step) ensures consistent statistical distributions across training without drift.
Sim-to-Real Motivation
Joint friction dominates sim-to-real transfer gaps for small legged robots. Physical bearings exhibit:
- Manufacturing tolerance variations (±20% typical)
- Temperature-dependent viscosity changes
- Wear-induced degradation over operational lifetime
By randomizing friction_scale across 0.6–1.0× nominal during early training, Microduck policies learn invariant control strategies that generalize to the physical robot's unmodeled damping characteristics without requiring system identification.
Summary
- BAM actuators bypass MuJoCo
dof_frictionloss, requiring custom friction injection randomize_bam_frictioninsrc/mjlab_microduck/tasks/mdp.pyimplements the core randomization logic- Event registration in task configs (e.g.,
microduck_velocity_env_cfg.py) triggers randomization on every reset - Curriculum terms progressively tighten randomization ranges from wide initial bounds to deployment-realistic values
- 14 servo joints receive correlated scale factors, excluding passive wheel actuators
Frequently Asked Questions
What is a BAM actuator and why does it handle friction differently?
BAM (Bam actuator model) is Microduck's voltage-controlled XL330 servo implementation that internalizes friction physics. Unlike standard MuJoCo joints where dof_frictionloss applies external damping, BAM actuators compute friction internally based on motor characteristics. This requires randomization through the actuator's friction_scale parameter rather than native MuJoCo domain randomization.
How does the curriculum control friction randomization over time?
The curriculum defines staged ranges passed to joint_friction_curriculum. Early training stages specify wider intervals (e.g., 0.6–1.0× nominal friction) to force robust policy learning. Later stages contract to tighter bands (e.g., 0.9–1.0×) focusing policy capacity on precise control near expected physical parameters. Stage transitions occur based on training progress metrics.
Can different joints receive independent friction values?
The current implementation applies correlated randomization—all 14 servo joints receive identical scale factors per environment. This reflects the physical reality that friction variations derive from shared environmental factors (temperature, lubrication) rather than independent per-joint manufacturing defects. The code structure in randomize_bam_friction permits per-joint modification by indexing actuator.friction_scale individually.
Where is the friction scale physically applied in simulation?
The friction_scale value multiplies the velocity-independent friction term within FrictionDRBamActuator.compute_torque(), defined in src/mjlab_microduck/actuator/friction_dr_bam.py. This scaled friction contributes additively to actuator output torque during each MuJoCo physics step, affecting joint dynamics without modifying the underlying dof_frictionloss parameter.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →