# How FrictionDRBamActuator Works for Domain Randomization in microduck_rl

> Learn how FrictionDRBamActuator enhances domain randomization in microduck_rl by injecting friction_scale for non-accumulating friction control. Achieve robust RL training.

- Repository: [Pollen Robotics/microduck_rl](https://github.com/pollen-robotics/microduck_rl)
- Tags: how-to-guide
- Published: 2026-09-02

---

**The `FrictionDRBamActuator` injects a per-environment `friction_scale` scalar into the BAM actuator's internal friction budget, enabling non-accumulating friction randomization that standard MuJoCo `dof_frictionloss` cannot achieve.**

The microduck_rl repository implements a custom voltage-controlled actuator model for the real-world XL330 servo. Because this **BAM (Bang-Bang with Anti-Windup Model) actuator** computes friction internally rather than relying on MuJoCo's native physics, conventional domain randomization through `dr.dof_frictionloss` has no effect. The `FrictionDRBamActuator` wrapper solves this gap by exposing a tunable, per-environment friction multiplier that task configurations can randomize on every episode reset.

## Why Standard MuJoCo Friction Randomization Fails

The BAM actuator in [`src/mjlab_microduck/actuator/bam_actuator.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/actuator/bam_actuator.py) overrides torque computation with a custom friction model incorporating Coulomb, viscous, Stribeck, and load-dependent terms. Since MuJoCo's `dof_frictionloss` field is never read during `BamActuator._compute_friction_budget()`, the simulator's built-in friction randomization (`dr.dof_frictionloss`) is silently ignored. This architectural detail forces any domain randomization strategy to operate at the actuator level rather than the physics engine level.

## Core Architecture of FrictionDRBamActuator

The implementation resides in [`src/mjlab_microduck/actuator/friction_dr_bam.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/actuator/friction_dr_bam.py) and extends the base `BamActuator` with three key mechanisms:

### Per-Environment Friction Scale Storage

During initialization, the actuator allocates a `friction_scale` tensor matching the environment count:

```python

# friction_dr_bam.py, lines 31-36

def __init__(self, cfg: FrictionDRBamActuatorCfg, *args, **kwargs):
    super().__init__(cfg, *args, **kwargs)
    self.friction_scale = torch.ones_like(self.kp_scale)  # line 34

    self._friction_scale_default = self.friction_scale.clone()

```

This tensor maintains independent friction multipliers for each parallel environment, enabling batched randomization without cross-contamination.

### Friction Budget Multiplication

Every simulation step, the computed friction budget is scaled by the current `friction_scale`:

```python

# friction_dr_bam.py, lines 43-48

def _compute_friction_budget(self, pos, vel, load):
    base = super()._compute_friction_budget(pos, vel, load)
    fs = self.friction_scale
    return base if fs is None else base * fs  # line 47

```

The multiplication occurs after the base friction model computes nominal values, preserving all physical structure (velocity-dependence, load-dependence) while adjusting magnitude.

### Reset-Safe Scale Management

Two methods enable non-accumulating randomization:

```python

# friction_dr_bam.py, lines 50-56

def reset_friction_scale(self, env_ids):
    """Restore nominal friction (scale = 1.0) for specified environments."""
    self.friction_scale[env_ids] = self._friction_scale_default[env_ids]

def set_friction_scale(self, env_ids, values):
    """Apply new per-environment friction scalars."""
    self.friction_scale[env_ids] = values

```

The explicit **reset-before-set pattern** prevents randomization from accumulating across episodes—a failure mode documented in the project's [`AGENTS.md`](https://github.com/pollen-robotics/microduck_rl/blob/main/AGENTS.md) where unbounded drift caused policy divergence.

## The Randomization Event: randomize_bam_friction

Task implementations trigger friction randomization through a registered event in [`src/mjlab_microduck/tasks/mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/mdp.py):

### Event Registration and Model Fields

```python

# mdp.py, lines 3197-3202

@requires_model_fields("dof_frictionloss", "dof_damping")
def randomize_bam_friction(env, env_ids: torch.Tensor, scale_range: tuple, asset_cfg=_DEFAULT_ASSET_CFG):
    """Sample per-env friction scalars for all FrictionDRBamActuator instances."""

```

The `@requires_model_fields` decorator ensures the MJCF includes `dof_frictionloss` and `dof_damping` fields, even though the BAM actuator overrides their values. This maintains compatibility with inspection tools and potential hybrid actuator configurations.

### Non-Accumulating Sampling Logic

```python

# mdp.py, lines 3212-3220 (excerpt with clarification)

for act in asset.actuators:
    if isinstance(act, FrictionDRBamActuator):
        act.reset_friction_scale(env_ids)  # line 39 equivalent: restore 1.0

        lo, hi = scale_range
        samples = torch.rand(len(env_ids), 1, device=env.device) * (hi - lo) + lo
        act.set_friction_scale(env_ids, samples)  # apply fresh randomization

```

The strict ordering—**reset to nominal, then sample new values**—guarantees that friction variation remains bounded within the configured `scale_range` regardless of training duration.

## Task Configuration Integration

Training tasks integrate the randomization event through their environment configuration files. The [`microduck_velocity_env_cfg.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/microduck_velocity_env_cfg.py) demonstrates standard usage:

```python

# microduck_velocity_env_cfg.py, line 477

@configclass
class EventCfg:
    randomize_friction = EventTerm(
        func=microduck_mdp.randomize_bam_friction,
        mode="reset",           # execute once per episode reset

        params={
            "scale_range": (0.8, 1.2),  # ±20% friction variation

            "asset_cfg": SceneEntityCfg("robot"),
        },
    )

```

Multiple task variants use different ranges. The [`microduck_roller_crouch_env_cfg.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/microduck_roller_crouch_env_cfg.py) (line 297) applies the same event with potentially distinct parameters, allowing per-task tuning of friction uncertainty without code changes.

## Sim-to-Real Transfer Benefits

The `FrictionDRBamActuator` randomization strategy targets specific hardware uncertainty sources:

- **Coulomb friction variation** from temperature-dependent grease viscosity in the XL330 gearbox
- **Stribeck effect uncertainty** from seal condition and manufacturing tolerance
- **Load-dependent friction** from belt tension variations in the microduck drivetrain

By scaling the complete internal friction budget rather than perturbing individual parameters, the randomization preserves the BAM model's physical correlations—critical for policies that must infer unmeasured friction states from actuator voltage and velocity observations.

## Usage Example: Adding Friction DR to a New Task

To enable friction domain randomization in a custom microduck_rl task:

```python

# my_task_env_cfg.py

from mjlab_microduck.actuator.friction_dr_bam import FrictionDRBamActuatorCfg
from mjlab_microduck.tasks import mdp as microduck_mdp
from mjlab.managers import EventTerm, SceneEntityCfg
from omni.isaac.lab.utils import configclass

@configclass
class MyRobotCfg:
    # Replace standard BAM with DR-enabled variant

    actuator_cfg = FrictionDRBamActuatorCfg(
        kp=500.0,
        kd=30.0,
        max_torque=1.0,
        # friction_scale initialized automatically

    )

@configclass
class EventCfg:
    randomize_bam_friction = EventTerm(
        func=microduck_mdp.randomize_bam_friction,
        mode="reset",
        params={"scale_range": (0.7, 1.3)},  # 30% variation for aggressive DR

    )

```

The `FrictionDRBamActuatorCfg` is API-compatible with `BamActuatorCfg`, requiring no changes to robot MJCF definitions or control code.

## Summary

- **Problem**: BAM actuator computes friction internally, bypassing MuJoCo `dof_frictionloss` randomization.
- **Solution**: `FrictionDRBamActuator` in [`friction_dr_bam.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/friction_dr_bam.py) injects a per-environment `friction_scale` multiplier into the friction budget computation.
- **Safety**: The `reset_friction_scale` → `set_friction_scale` pattern in `randomize_bam_friction` (mdp.py) prevents accumulation across episodes.
- **Integration**: Tasks register the event via `EventTerm` with `mode="reset"` and tunable `scale_range` parameters.
- **Outcome**: Policies train against diverse, bounded friction magnitudes without physical model corruption, improving robustness to real XL330 hardware variation.

## Frequently Asked Questions

### Why can't I use Isaac Lab's built-in `dr.dof_frictionloss` for the BAM actuator?

The BAM actuator's `_compute_friction_budget` method overrides MuJoCo's native friction computation with a custom model based on XL330 datasheet parameters. Since the physics engine's `dof_frictionloss` value is never read during torque calculation, randomizing it has no effect on simulation behavior. The `FrictionDRBamActuator` restores randomization capability by operating at the actuator abstraction layer where friction is actually computed.

### What happens if I forget to call `reset_friction_scale` before `set_friction_scale`?

Without the reset step, friction scales would compound multiplicatively across episodes (e.g., 1.2 × 0.9 × 1.1...), eventually diverging to near-zero or extreme values. This accumulation bug was explicitly documented in project notes as a cause of policy training failures. The `randomize_bam_friction` event in mdp.py enforces correct usage by always calling `reset_friction_scale` first.

### How do I choose the `scale_range` parameter for my task?

Start with ±20% (0.8–1.2) based on XL330 friction variation measurements across temperature and load conditions documented in the hardware characterization. For tasks requiring aggressive sim-to-real transfer (e.g., roller crouching with ground contact), expand to ±30% or implement curriculum randomization that widens the range during training. Monitor policy entropy—if entropy collapses, reduce the range; if real-world performance lags simulation, increase it.

### Can I combine `FrictionDRBamActuator` with other domain randomization techniques?

Yes. The actuator composes naturally with Isaac Lab's model randomization (link masses, inertias), observation noise injection, and action delays. The `@requires_model_fields` decorator only specifies MJCF field requirements without excluding other randomizers. Configure multiple `EventTerm` entries with distinct `priority` values to control execution order during resets.