# Why Joint Indices Should Not Be Hardcoded in Microduck RL MDP Functions

> Avoid hardcoding joint indices in Microduck RL MDP functions to prevent silent failures and ensure proper action space alignment with your robot's physical configuration.

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

---

**Hardcoding joint indices in Microduck RL MDP functions causes silent failures when passive joints are present, breaking the alignment between the 14-D action space and the physical robot configuration.**

The Microduck RL framework from Pollen Robotics manages a robot model containing 14 actuated servos alongside variable un-actuated "passive_*" joints such as backlash hinges, wheels, and jaw linkages. When these passive joints exist in the model, the joint array becomes wider and interleaved with servo joints, making fixed numeric indices point to the wrong physical degrees of freedom. Using hardcoded indices in reward, observation, or event functions therefore risks incorrect reward signals and malformed observations that destabilize sim-to-real transfer.

## The Risk of Hardcoded Indices in Microduck RL

Using literal integers to address joints assumes a static model structure that the Microduck RL architecture explicitly does not guarantee.

### How Passive Joints Break Fixed Indices

The robot model includes 14 actuated servos plus a variable number of passive joints. When passive joints are present, the joint array expands and interleaves these additional degrees of freedom with the servo joints. If an MDP function addresses joints by fixed numeric indices, those indices will reference the wrong physical joint whenever passive joints are added or reordered. This misalignment corrupts the relationship between the policy's action space and the actual hardware configuration.

### The Sim-to-Real Failure Mode

Hardcoding indices creates a dangerous discrepancy where policies succeed in simulation but fail on physical hardware. When indices point to passive wheels or backlash hinges instead of intended servos, the policy receives incorrect state observations and generates actions for the wrong joints. This mismatch manifests as unpredictable robot behavior during deployment, violating the 14-D action space contract that the physical robot expects.

## The Dynamic Solution: Servo Joint Helpers

The framework provides specialized helper functions to compute joint indices dynamically, ensuring the code works for both plain and backlash-augmented models.

### _servo_joint_ids and Filtering Logic

The helper `_servo_joint_ids(env, asset)` filters the joint array using the regex pattern `^(?!passive_).*`, guaranteeing only the 14 servo joints are considered regardless of the underlying model's joint ordering. This filtering happens in [`src/mjlab_microduck/tasks/mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/mdp.py), where the function implementation provides the authoritative safety mechanism against index misalignment.

### Caching and Performance

The helper caches computed indices per-entity, making the lookup cheap even when called frequently during training. The returned indices provide a consistent view of servo-only joint positions, velocities, and default positions without requiring manual index management.

## Implementation in Core MDP Functions

All joint-index-based calculations in [`src/mjlab_microduck/tasks/mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/mdp.py) use these helpers instead of literal values. For example, `target_overrides` and `qpos` column math rely on dynamically retrieved indices to maintain compatibility across different model configurations.

The following examples demonstrate correct usage versus the dangerous pattern of hardcoding:

```python

# Correct: use the dynamic helper to get servo-only joint indices

servo_ids = _servo_joint_ids(env, robot_entity)

# Example: compute a joint-position penalty only on servo joints

pos_error = robot.data.joint_pos[:, servo_ids] - desired_positions
penalty = torch.norm(pos_error, dim=1)

```

```python

# Incorrect: hard-coded indices break when passive joints exist

# This would incorrectly address the 5th joint, which might be a passive wheel

pos_error = robot.data.joint_pos[:, 4] - desired_positions[4]

```

Additional helpers provide safe access to velocities and default positions:

```python

# Using the helper for velocity calculations

servo_vel = _servo_joint_vel(env, robot_entity)
smoothness = torch.mean(torch.square(servo_vel), dim=1)

# Accessing default joint positions safely

default_pos = _servo_default_joint_pos(env, robot_entity)

```

Environment configurations in [`src/mjlab_microduck/tasks/microduck_velocity_rollers_env_cfg.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/microduck_velocity_rollers_env_cfg.py) and [`src/mjlab_microduck/tasks/microduck_ground_pick_env_cfg.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/microduck_ground_pick_env_cfg.py) illustrate these principles in practice, emphasizing that hardcoded symmetry assumptions fail when passive joints alter the model structure.

## Summary

- **Passive joints interleave** with servo joints in the model, making fixed indices point to wrong physical locations.
- **Hardcoding causes sim-to-real failures** by misaligning the 14-D action space with actual hardware configuration.
- **Dynamic helpers** like `_servo_joint_ids` filter passive joints using regex `^(?!passive_).*` and cache results per-entity.
- **Core MDP utilities** in [`src/mjlab_microduck/tasks/mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/mdp.py) enforce this pattern for all joint-index calculations.
- **Consistent API** provides `_servo_joint_vel` and `_servo_default_joint_pos` for safe state access across model variations.

## Frequently Asked Questions

### What happens if I hardcode joint indices in Microduck RL?

Hardcoding joint indices causes the policy to read from or write to the wrong joints when passive joints (wheels, backlash hinges, jaw linkages) are present in the model. This produces incorrect reward signals and observations, leading to policies that work in simulation but fail catastrophally on the physical robot due to action space misalignment.

### How does _servo_joint_ids handle passive joints?

The function applies a regex filter `^(?!passive_).*` to exclude any joint whose name starts with "passive_", returning only indices for the 14 actuated servos. It caches these indices per-entity to maintain performance while ensuring the returned array matches the expected observation and action dimensions regardless of model complexity.

### Where are the servo joint helpers defined?

The helpers `_servo_joint_ids`, `_servo_joint_vel`, and `_servo_default_joint_pos` are defined in [`src/mjlab_microduck/tasks/mdp.py`](https://github.com/pollen-robotics/microduck_rl/blob/main/src/mjlab_microduck/tasks/mdp.py). This file contains the core MDP utilities and authoritative documentation explaining why joint indices must not be hardcoded in reward, observation, or event functions.

### Can I use these helpers for velocity and default position queries?

Yes. The framework provides `_servo_joint_vel(env, asset)` for retrieving velocity indices and `_servo_default_joint_pos(env, asset)` for default position configurations. These functions use the same filtering and caching mechanism as `_servo_joint_ids`, ensuring consistent behavior across all joint state queries.