# Helper Functions for Accessing Servo Joint IDs and Positions in Microduck RL

> Discover Microduck RL helper functions like _servo_joint_ids and _servo_joint_pos to efficiently access servo joint information and isolate actuated degrees of freedom from passive joints.

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

---

**Microduck RL provides four canonical helper functions in [`src/mjlab_microduck/tasks/mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/mdp.py)—`_servo_joint_ids()`, `_servo_joint_pos()`, `_servo_joint_vel()`, and `_servo_default_joint_pos()`—that filter and cache servo joint indices to isolate 14 actuated degrees of freedom from passive joints.**

The Microduck RL reinforcement learning framework isolates controllable servo joints from unactuated passive mechanisms using dedicated utility functions. These helpers ensure that policies and reward calculations always reference the correct 14-DOF servo layout, regardless of whether the underlying MJCF model contains additional passive joints like backlash hinges or wheels. All joint data access in the repository flows through these centralized utilities defined in the MDP task module.

## The Four Canonical Servo Joint Helpers

The [`mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/mdp.py) module defines the canonical interface for servo joint access. According to the Microduck RL source code at lines 26–55, these functions guarantee a consistent 14-servo layout even when the MJCF model contains extra passive joints.

### `_servo_joint_ids(env, asset)`

This function returns the **entity-local indices** of all non-passive joints. It uses the regular expression `r"^(?!passive_).*"` to filter out any joint whose name starts with `passive_`, ensuring that only actuated servos are selected.

To optimize performance, the result is cached on the environment object under `env.__dict__['_servo_joint_ids_cache']`. Subsequent calls retrieve cached indices in O(1) time, eliminating the overhead of repeated regex searches via `asset.find_joints`.

### `_servo_joint_pos(env, asset)`

Returns a **tensor of joint positions** for the servo joints only. This function calls `_servo_joint_ids()` to obtain the correct indices, then indexes `asset.data.joint_pos` to extract the position data for the 14 active DOFs.

### `_servo_joint_vel(env, asset)`

Provides a **tensor of joint velocities** for the servo joints only. The implementation mirrors `_servo_joint_pos()` but indexes `asset.data.joint_vel` instead, ensuring velocity calculations exclude unactuated passive mechanisms.

### `_servo_default_joint_pos(env, asset)`

Retrieves the **default (rest) joint positions** for the servo joints by indexing `asset.data.default_joint_pos` using the cached servo joint IDs. This is essential for computing pose deviations and reset states.

## Implementation Details: Caching and Passive Joint Filtering

The architectural design of these helpers addresses two critical requirements in the Microduck RL training pipeline.

### Regex-Based Filtering

The robot model contains extra joints that are **unactuated** (e.g., wheels, backlash hinges, jaw linkages). Their indices interleave with the servo joints, so directly using raw joint arrays would select the wrong DOFs. The negative lookahead regex `r"^(?!passive_).*" explicitly excludes any joint prefixed with `passive_`, preserving the invariant that policies receive a 61-dimensional observation vector with fixed joint ordering.

### Cache invalidation Strategy

Computing the servo joint list requires a regex search through the asset's joint names. Since every environment step may need joint data, the lookup is performed once per asset and stored in `env.__dict__['_servo_joint_ids_cache']`. This design keeps per-step overhead minimal while maintaining correctness across episode resets.

## Practical Usage Examples

Below are typical patterns found throughout the repository for accessing servo joint data.

### Computing Joint Accelerations

```python
def joint_accelerations_l2(env, asset_cfg=SceneEntityCfg("robot")):
    asset = env.scene[asset_cfg.name]
    
    # Servo joint positions and velocities only

    q = _servo_joint_pos(env, asset)  # shape (N, 14)

    v = _servo_joint_vel(env, asset)  # shape (N, 14)

    
    # Compute accelerations, penalties, etc.

```

### Retrieving Default Rest Pose

```python
def get_rest_pose(env):
    asset = env.scene["robot"]
    rest_q = _servo_default_joint_pos(env, asset)  # shape (N, 14)

    return rest_q

```

### Exporting ONNX Metadata

```python
def _get_base_metadata_no_passive(env, run_path):
    robot = env.scene["robot"]
    joint_action = env.action_manager.get_term("joint_pos")
    
    # Joint IDs for the servo joints only

    servo_ids = _servo_joint_ids(env, robot)  # list of 14 ints

    
    # Use servo_ids to slice actuator properties

```

## Integration with Training Pipeline

These helpers centralize joint access across multiple modules, making the code robust to future model changes.

### Reward Function Integration

Many reward terms rely on these utilities to slice joint tensors correctly. Functions such as `standing_composite_score`, `leg_action_rate_l2`, and `neck_action_rate_l2` all call `_servo_joint_ids()` to ensure they compute penalties only on actuated joints, ignoring passive backlash or wheel joints.

### Environment Configuration

The [`backlash.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/backlash.py) module wraps base environment configurations to add backlash variants while relying on the same joint-ID helpers to maintain index consistency. Similarly, [`microduck_velocity_env_cfg.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/microduck_velocity_env_cfg.py) uses these helpers indirectly through reward term configurations.

### Key Files

- **[`src/mjlab_microduck/tasks/mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/mdp.py)**: Central module defining servo-joint helper functions and reward utilities.
- **[`src/mjlab_microduck/tasks/backlash.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/backlash.py)**: Wraps environments with backlash variants using consistent joint indexing.
- **[`src/mjlab_microduck/tasks/microduck_velocity_env_cfg.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/microduck_velocity_env_cfg.py)**: Task configuration demonstrating indirect usage via reward terms.

## Summary

- **Four canonical functions**—`_servo_joint_ids()`, `_servo_joint_pos()`, `_servo_joint_vel()`, and `_servo_default_joint_pos()`—provide the sole interface for accessing the 14 actuated servo joints in Microduck RL.
- **Regex filtering** with `r"^(?!passive_).*"` isolates actuated joints from passive mechanisms like wheels and backlash hinges.
- **O(1) caching** via `env.__dict__['_servo_joint_ids_cache']` eliminates repeated joint lookups during training steps.
- **Repository-wide usage** ensures reward functions, observation generators, and ONNX export tools operate on a stable, model-agnostic joint layout.

## Frequently Asked Questions

### Why does Microduck RL need helper functions for servo joint access?

The underlying MJCF robot model contains passive joints—such as backlash hinges and wheel mechanisms—that interleave with actuated servo joints in the joint array. Direct indexing would select wrong DOFs and break the 14-servo layout invariant. The helper functions filter these out using regex patterns, ensuring policies and rewards always reference the correct actuated joints.

### How does the caching mechanism work for servo joint IDs?

The first call to `_servo_joint_ids()` performs an expensive regex search via `asset.find_joints` and stores the result in `env.__dict__['_servo_joint_ids_cache']`. Subsequent calls retrieve this cached list instantly, providing O(1) access during high-frequency training steps.

### What is the difference between servo joints and passive joints in Microduck RL?

**Servo joints** are the 14 actuated degrees of freedom controlled by the policy. **Passive joints** are unactuated mechanical elements (e.g., backlash hinges, jaw linkages, wheels) included in the physics model for realism but not driven by actuators. The helper functions explicitly exclude any joint with names starting with `passive_`.

### Which reward functions use these servo joint helpers?

Reward terms including `standing_composite_score`, `leg_action_rate_l2`, and `neck_action_rate_l2` all call `_servo_joint_ids()` to slice joint position and velocity tensors. This centralization ensures penalties apply only to controllable servos, not passive mechanisms.