Skip to content

Command Terms

Command terms produce the goal a policy is conditioned on (a base velocity, a target pose, a gait phase, a motion clip) and resample it on their own schedule. Other terms read a command by its config key through env.command_manager. The commands guide covers resampling and debug visualization; the term catalog lists every parameter.

from eden import commands

The base class CommandTerm is on the managers page.

Velocity

SE(2) base-velocity commands: sampled, held constant, or driven by an operator's input device.

SE(2) velocity command terms for locomotion: sampled, held, and operator-driven.

Attributes

Classes

UniformVelocityCommand

UniformVelocityCommand(env: EnvBase, options: CommandTermOptions)

Bases: CommandTerm

SE(2) velocity command sampled from a uniform distribution.

The command comprises a linear velocity in x and y direction and an angular velocity around the z-axis. It is given in the robot's base frame.

If the heading_command attribute is set to True, the angular velocity is computed from the heading error similar to doing a proportional control on the heading error. The target heading is sampled uniformly from the provided range. Otherwise, the angular velocity is sampled uniformly from the provided range.

Source code in eden/managers/terms/commands/velocity.py
def __init__(self, env: EnvBase, options: CommandTermOptions):
    super().__init__(env, options)

    # check configuration
    if self.heading_command and self.heading_range is None:
        raise ValueError(
            "The velocity command has heading commands active (heading_command=True) but `heading_range` is None."
        )

    self.robot: Entity | None = None
    # -- command: x vel, y vel, yaw vel (the heading target is kept separately)
    self.vel_command_b = torch.zeros(self.num_envs, 3, device=self.device)
    self.heading_target = torch.zeros(self.num_envs, device=self.device)
    self.is_heading_env = torch.zeros(self.num_envs, dtype=torch.bool, device=self.device)
    self.is_standing_env = torch.zeros_like(self.is_heading_env)

Attributes

entity_name class-attribute instance-attribute
entity_name: str = ''
heading_command class-attribute instance-attribute
heading_command: bool = False
heading_control_stiffness class-attribute instance-attribute
heading_control_stiffness: float = 1.0
rel_standing_envs class-attribute instance-attribute
rel_standing_envs: float = 0.0
rel_heading_envs class-attribute instance-attribute
rel_heading_envs: float = 1.0
lin_vel_x_range class-attribute instance-attribute
lin_vel_x_range: tuple[float, float] = (0.0, 0.0)
lin_vel_y_range class-attribute instance-attribute
lin_vel_y_range: tuple[float, float] = (0.0, 0.0)
ang_vel_z_range class-attribute instance-attribute
ang_vel_z_range: tuple[float, float] = (0.0, 0.0)
heading_range class-attribute instance-attribute
heading_range: tuple[float, float] | None = None
robot instance-attribute
robot: Entity | None = None
vel_command_b instance-attribute
vel_command_b = torch.zeros(self.num_envs, 3, device=self.device)
heading_target instance-attribute
heading_target = torch.zeros(self.num_envs, device=self.device)
is_heading_env instance-attribute
is_heading_env = torch.zeros(self.num_envs, dtype=torch.bool, device=self.device)
is_standing_env instance-attribute
is_standing_env = torch.zeros_like(self.is_heading_env)
command property
command: Tensor

The desired base velocity command in the base frame. Shape is (num_envs, 3).

Methods:

build
build() -> None
Source code in eden/managers/terms/commands/velocity.py
def build(self) -> None:
    self.robot = self.resolve_entity(self.entity_name)

ConstantVelocityCommand

ConstantVelocityCommand(env: EnvBase, options: CommandTermOptions)

Bases: CommandTerm

SE(2) velocity command held at a fixed value instead of resampled.

Same command as :class:UniformVelocityCommand -- (vx, vy, wz) in the robot's base frame -- with the random generator taken out: the value is whatever :attr:command_value says, or whatever :meth:set_command was last given, and it never changes on its own.

This is the term a deployment substitutes for the trained one. A policy deployed from its training config keeps resampling the command it was trained against, which on a robot reads as the machine wandering off on its own; holding a value the operator chose is what a deploy actually wants. It is equally the right term for an evaluation that has to hold one command for a whole episode.

The value is clamped into lin_vel_x_range / lin_vel_y_range / ang_vel_z_range, which a deploy fills from the term it replaces, so the held command cannot leave the distribution the policy was trained on. A degenerate range — move_forward's lin_vel_x_range=(0.5, 0.5) — pins its axis: the policy only ever saw one velocity there, so that axis is not controllable and the clamp holds the single trained value whatever it is asked for. The clamp is logged.

Source code in eden/managers/terms/commands/velocity.py
def __init__(self, env: EnvBase, options: CommandTermOptions):
    super().__init__(env, options)
    self.robot: Entity | None = None
    self.vel_command_b = torch.zeros(self.num_envs, 3, device=self.device)
    self._limits_key: tuple[tuple[float, float], ...] | None = None
    self._low: torch.Tensor | None = None
    self._high: torch.Tensor | None = None

Attributes

entity_name class-attribute instance-attribute
entity_name: str = ''
command_value class-attribute instance-attribute
command_value: tuple[float, float, float] = (0.0, 0.0, 0.0)
lin_vel_x_range class-attribute instance-attribute
lin_vel_x_range: tuple[float, float] = (-1.0, 1.0)
lin_vel_y_range class-attribute instance-attribute
lin_vel_y_range: tuple[float, float] = (-1.0, 1.0)
ang_vel_z_range class-attribute instance-attribute
ang_vel_z_range: tuple[float, float] = (-1.0, 1.0)
resampling_time_range class-attribute instance-attribute
resampling_time_range: tuple[float, float] = (1000000000.0, 1000000000.0)
robot instance-attribute
robot: Entity | None = None
vel_command_b instance-attribute
vel_command_b = torch.zeros(self.num_envs, 3, device=self.device)
command property
command: Tensor

The desired base velocity command in the base frame. Shape is (num_envs, 3).

Methods:

build
build() -> None
Source code in eden/managers/terms/commands/velocity.py
def build(self) -> None:
    # Resolve the named entity and *keep* it, exactly as UniformVelocityCommand does:
    # consumers reach through the command term for it (``eden/managers/terms/metrics/
    # error.py`` reads ``command.robot.get_vel()``), so a substitute that only resolved
    # and discarded would not be interchangeable with the term it replaces. Resolving
    # here also means a bad name fails at setup rather than on the robot. An unnamed
    # entity is legitimate: a command term standing alone, driving no particular entity,
    # is what the task-less deploy path builds.
    if self.entity_name:
        self.robot = self.resolve_entity(self.entity_name)
    self._seed_command()
set_command
set_command(command, *, warn_on_clamp: bool = True) -> Tensor

Hold (vx, vy, wz) from now on, clamped into the configured ranges.

Returns the clamped command, and warns when clamping actually bit — a value outside the trained ranges is worth seeing rather than silently trimming.

PARAMETER DESCRIPTION
command

(vx, vy, wz) in the robot's base frame.

warn_on_clamp

Whether to log a clamp. Off for a value nothing will hold long enough to matter — :class:OperatorVelocityCommand seeds the buffer with command_value only so the first observation is in range, and the device overwrites it on the first compute.

TYPE: bool DEFAULT: True

Source code in eden/managers/terms/commands/velocity.py
def set_command(self, command, *, warn_on_clamp: bool = True) -> torch.Tensor:
    """Hold ``(vx, vy, wz)`` from now on, clamped into the configured ranges.

    Returns the clamped command, and warns when clamping actually bit — a value
    outside the trained ranges is worth seeing rather than silently trimming.

    Parameters
    ----------
    command
        ``(vx, vy, wz)`` in the robot's base frame.
    warn_on_clamp
        Whether to log a clamp. Off for a value nothing will hold long enough to
        matter — :class:`OperatorVelocityCommand` seeds the buffer with
        ``command_value`` only so the first observation is in range, and the device
        overwrites it on the first ``compute``.
    """
    value = torch.as_tensor(command, dtype=torch.float32, device=self.device).flatten()
    if value.numel() != 3:
        raise ValueError(f"A velocity command is (vx, vy, wz); got {value.numel()} value(s): {command}.")
    low, high = self._limits()
    clamped = torch.clamp(value, min=low, max=high)
    if warn_on_clamp and not torch.equal(clamped, value):
        # Reported from ``_limits_key``, not the fields, so the range named here is
        # always the one that was actually applied.
        vx_range, vy_range, wz_range = self._limits_key
        en.logger.warning(
            f"[commands] Velocity command {value.tolist()} is outside the trained ranges "
            f"(vx {vx_range}, vy {vy_range}, wz {wz_range}); holding {clamped.tolist()} instead."
        )
    self.vel_command_b[:] = clamped
    return clamped

OperatorVelocityCommand

OperatorVelocityCommand(env: EnvBase, options: CommandTermOptions)

Bases: ConstantVelocityCommand

SE(2) velocity command driven live by a human, from a keyboard or a gamepad.

A :class:ConstantVelocityCommand whose held value is re-read from an input device every control step. Deploy a locomotion policy with this term substituted for its trained :class:UniformVelocityCommand and the robot goes where the operator says instead of where the training distribution says.

The device emits a normalized deflection; it is scaled into the same lin_vel_*_range / ang_vel_z_range and clamped there, so full deflection is the fastest command the policy was trained on and the operator cannot ask for more. See :mod:eden.utils.devices.se2 for the bindings.

For deployment and interactive evaluation, not for training: it reads a physical device, so a training run would sit at whatever the sticks happen to say.

Source code in eden/managers/terms/commands/velocity.py
def __init__(self, env: EnvBase, options: CommandTermOptions):
    super().__init__(env, options)
    self._device = None
    self._scale = None

Attributes

device_name class-attribute instance-attribute
device_name: str = 'keyboard'
device_options class-attribute instance-attribute
device_options: dict = {}

Methods:

build
build() -> None
Source code in eden/managers/terms/commands/velocity.py
def build(self) -> None:
    from eden.utils.devices.se2 import make_se2_device, scale_se2_deflection

    super().build()
    # The device is opened in build(), not __init__: build() is the last step of term
    # construction, so one that cannot be opened (no display for pynput, no gamepad
    # plugged in) leaves the term unusable rather than half-alive.
    self._device = make_se2_device(self.device_name, self.device_options)
    # Bound once rather than imported per control step.
    self._scale = scale_se2_deflection
    en.logger.info(
        f"[commands] Operator velocity command via {self.device_name}: "
        f"vx {self.lin_vel_x_range}, vy {self.lin_vel_y_range}, wz {self.ang_vel_z_range}."
    )
close
close() -> None

Stop the input device. Idempotent.

Source code in eden/managers/terms/commands/velocity.py
def close(self) -> None:
    """Stop the input device. Idempotent."""
    stop = getattr(self._device, "stop", None)
    if stop is not None:
        stop()

Functions:

Target poses

Oracle pose command terms (uniform T3 / SE3 targets).

Attributes

Classes

UniformT3Command

UniformT3Command(env: EnvBase, options: CommandTermOptions)

Bases: CommandTerm

Command that samples T(3) translation command.

PARAMETER DESCRIPTION
x_range

Range of the x-coordinate of the target position.

y_range

Range of the y-coordinate of the target position.

z_range

Range of the z-coordinate of the target position.

debug_vis

When True (and not training), draw a debug sphere at the goal each step.

marker_frame_entity

Entity whose frame the command is expressed in. When set, the marker is placed at entity.pos + R(entity.quat) @ command (the command is in that entity's local frame, e.g. the robot base); empty -> command is world-frame.

marker_entity_name

Name of a scene entity (e.g. a non-colliding SphereOptions) to move to the goal each step. Preferred over draw_debug_spheres because debug draws are viewer-only and do NOT appear in recorded camera videos.

marker_radius

Fallback debug-sphere radius [m] (used only when marker_entity_name is unset).

marker_color

Fallback debug-sphere RGBA.

Source code in eden/managers/terms/commands/oracle.py
def __init__(self, env: EnvBase, options: CommandTermOptions):
    super().__init__(env, options)
    self.oracle_pos_b = torch.zeros(self.num_envs, 3, device=self.device)

Attributes

x_range class-attribute instance-attribute
x_range: tuple[float, float] = (0.3, 0.5)
y_range class-attribute instance-attribute
y_range: tuple[float, float] = (-0.2, 0.2)
z_range class-attribute instance-attribute
z_range: tuple[float, float] = (0.2, 0.4)
marker_frame_entity class-attribute instance-attribute
marker_frame_entity: str = ''
marker_entity_name class-attribute instance-attribute
marker_entity_name: str = ''
marker_radius class-attribute instance-attribute
marker_radius: float = 0.03
marker_color class-attribute instance-attribute
marker_color: tuple[float, float, float, float] = (0.1, 0.9, 0.1, 0.85)
oracle_pos_b instance-attribute
oracle_pos_b = torch.zeros(self.num_envs, 3, device=self.device)
command property
command: Tensor

Methods:

draw_vis
draw_vis() -> None
Source code in eden/managers/terms/commands/oracle.py
def draw_vis(self) -> None:
    # Goal marker (eval/inference only; gated on debug_vis by the manager).
    pos = self.oracle_pos_b
    if self.marker_frame_entity:
        ent = self.resolve_entity(self.marker_frame_entity)
        pos = ent.get_pos() + transform_by_quat(pos, ent.get_quat())
    marker = (
        self._env.entities[self.marker_entity_name]
        if self.marker_entity_name and self.marker_entity_name in self._env.entities
        else None
    )
    if marker is not None:
        # Move a real (non-colliding) entity to the goal — renders in cameras,
        # unlike draw_debug_* which only shows in the interactive viewer.
        marker.set_pos(pos.detach())
    else:
        self._env.clear_debug_objects()
        self._env.draw_debug_spheres(pos.detach().cpu().numpy(), radius=self.marker_radius, color=self.marker_color)

UniformSE3Command

UniformSE3Command(env: EnvBase, options: CommandTermOptions)

Bases: CommandTerm

Command that samples SE(3) translation and rotation command.

PARAMETER DESCRIPTION
x_range

Range of the x-coordinate of the target position.

y_range

Range of the y-coordinate of the target position.

z_range

Range of the z-coordinate of the target position.

roll_range

Range of the roll angle of the target rotation (in radians).

pitch_range

Range of the pitch angle of the target rotation (in radians).

yaw_range

Range of the yaw angle of the target rotation (in radians).

Note

This term provides an equivalent command to UniformPoseCommand in IsaacLab.

Source code in eden/managers/terms/commands/oracle.py
def __init__(self, env: EnvBase, options: CommandTermOptions):
    super().__init__(env, options)
    # -- commands: (x, y, z, qw, qx, qy, qz) in root frame
    self.oracle_pose_b = torch.zeros(self.num_envs, 7, device=self.device)
    self.oracle_pose_b[:, 3] = 1.0  # identity quaternion (wxyz) until the first resample

Attributes

x_range class-attribute instance-attribute
x_range: tuple[float, float] = (0.3, 0.5)
y_range class-attribute instance-attribute
y_range: tuple[float, float] = (-0.2, 0.2)
z_range class-attribute instance-attribute
z_range: tuple[float, float] = (0.2, 0.4)
roll_range class-attribute instance-attribute
roll_range: tuple[float, float] = (-np.pi, np.pi)
pitch_range class-attribute instance-attribute
pitch_range: tuple[float, float] = (-np.pi, np.pi)
yaw_range class-attribute instance-attribute
yaw_range: tuple[float, float] = (-np.pi, np.pi)
oracle_pose_b instance-attribute
oracle_pose_b = torch.zeros(self.num_envs, 7, device=self.device)
command property
command: Tensor

Methods:

Functions:

Gait phase

Gait phase command term for bipedal locomotion.

Attributes

Classes

GaitPhaseCommand

GaitPhaseCommand(env: EnvBase, options: CommandTermOptions)

Bases: CommandTerm

Command generator that maintains a cyclic gait phase for each foot.

Generates a per-foot phase signal that advances at a configurable frequency. The left foot starts at phase 0 and the right foot starts at phase_offset (default pi for an alternating gait).

The command property returns the raw phase angles (num_envs, num_feet). Use the sin_phase / cos_phase observation terms to feed these into the policy.

PARAMETER DESCRIPTION
gait_frequency

Gait cycle frequency in Hz (cycles per second). Default 1.25 Hz.

phase_offset

Phase offset between left and right foot in radians. Default pi (alternating).

num_feet

Number of feet. Default 2.

velocity_command_name

Optional name of a velocity-style command term (e.g. UniformVelocityCommand) whose command tensor has shape (num_envs, ≥3) interpreted as [lin_x, lin_y, ang_z] in the base frame. When set, the gait phase is pinned to stand_phase_value for environments whose commanded velocities are below stand_velocity_threshold — so downstream alternating-gait rewards stop enforcing a step cycle while the policy is commanded to stand. None (default) keeps the phase always cycling.

stand_velocity_threshold

Velocity-magnitude threshold (m/s for linear, rad/s for angular) below which a command is treated as "stand". Default 0.01.

stand_phase_value

Phase value (radians) assigned to all feet for stand-mode envs. Setting both feet to the same phase means the gait reward stops penalizing alternation while standing. Default pi matches the common contact-schedule formulation where cos(phase) > 0 is stance.

Source code in eden/managers/terms/commands/gait.py
def __init__(self, env: EnvBase, options: CommandTermOptions):
    super().__init__(env, options)
    self._phase = torch.zeros(self.num_envs, self.num_feet, device=self.device)
    # Initialize with offsets (left=0, right=phase_offset, ...)
    for i in range(self.num_feet):
        self._phase[:, i] = i * self.phase_offset
    self._stand_shape_validated = False

Attributes

gait_frequency class-attribute instance-attribute
gait_frequency: float = 1.25
phase_offset class-attribute instance-attribute
phase_offset: float = np.pi
num_feet class-attribute instance-attribute
num_feet: int = 2
velocity_command_name class-attribute instance-attribute
velocity_command_name: str | None = None
stand_velocity_threshold class-attribute instance-attribute
stand_velocity_threshold: float = 0.01
stand_phase_value class-attribute instance-attribute
stand_phase_value: float = float(np.pi)
resampling_time_range class-attribute instance-attribute
resampling_time_range: tuple[float, float] = (1000000000.0, 1000000000.0)
command property
command: Tensor

The current gait phase per foot. Shape (num_envs, num_feet).

Methods:

Motion clips

MotionCommand plays .npz motion clips on a plain RLEnvBase, for frozen-policy inference and single-clip tracking. Training-grade motion tracking goes through MimicRLEnvBase and the motion manager instead.

Motion-clip command term (single- and multi-clip) and npz loaders.

A :class:CommandTerm that drives motion-imitation / tracking policies from one or more BeyondMimic-format .npz clips. Single-clip is the M == 1 case of the multi-clip library. Shared by the frozen ONNX policy ports (sonic, twist2, humanoid_gpt, protomotionv3, beyondmimic_moves) and the beyondmimic example.

Which motion path to use

Eden has two motion-reference systems; pick by use case:

  • This command-term path (MotionCommand) — lightweight, for frozen-policy inference / single-clip tracking on a plain RLEnvBase. Loads npz clips verbatim and exposes the current frame (plus arbitrary future frames via :meth:MotionCommand.gather_motion_frames) directly off the command term. No dataset pipeline.
  • The manager path (eden.managers.motion_manager.MotionManager + eden.utils.motion_reference) — training-grade: dataset adapters, continuous-time interpolation, a future-frame window, symmetry augmentation, and motion-id / start-time curricula. Owned by MimicRLEnvBase and consumed by the tracking reward / observation / termination terms. See docs/guides/motion-tracking.md.

Both expose the same world-frame vocabulary — links_*_w (tracked-link poses) and base_*_w (floating-base root) — so reward / metric code reads the same field names regardless of source.

Config vs data vocabulary

The configurable links_name list names the tracked links; their per-frame data is read as links_*_w. Two links_name members are special: the floating-base body (base_link_name, default links_name[0]; exposed as base_*_w, used to reset the robot root) and anchor_link_name (the yaw-warp reference, exposed as anchor_*_w). Both are resolved and validated against links_name in :meth:build. The npz on-disk keys stay body_*_w (file format); only the Python accessors are links_*.

Attributes

Classes

MotionLoader

MotionLoader(motion_file: str, links_idx: Tensor, device: device = device('cpu'), links_name: list[str] | None = None)

Loader for a single motion clip.

A standalone verbatim loader for callers that only need one clip; :class:MotionCommand itself loads through :class:MultiMotionLoader (single-clip is its M=1 case).

PARAMETER DESCRIPTION
motion_file

Path to the motion file.

TYPE: str

links_idx

Genesis idx_local of the bodies to load; legacy fallback used only when the npz has no body_names.

TYPE: Tensor

device

Device to load the data on.

TYPE: device DEFAULT: device('cpu')

links_name

Names of the bodies to load, in the desired output order. When given and the npz carries body_names, columns are resolved by name (order-independent of Genesis); otherwise links_idx is used.

TYPE: list[str] | None DEFAULT: None

Source code in eden/managers/terms/commands/mimic.py
def __init__(
    self,
    motion_file: str,
    links_idx: torch.Tensor,
    device: torch.device = torch.device("cpu"),
    links_name: list[str] | None = None,
) -> None:
    data = np.load(motion_file)
    body_cols = resolve_body_cols(data, links_idx, links_name)

    def _load(key: str) -> torch.Tensor:
        return torch.tensor(data[key], dtype=torch.float32, device=device)

    self.dofs_pos = _load("joint_pos")
    self.dofs_vel = _load("joint_vel")
    self.time_step_total = self.dofs_pos.shape[0]
    # Body axes are sliced to the requested links; quats are wxyz (MuJoCo convention).
    self.links_pos_w = _load("body_pos_w")[:, body_cols]
    self.links_quat_w = _load("body_quat_w")[:, body_cols]
    self.links_lin_vel_w = _load("body_lin_vel_w")[:, body_cols]
    self.links_ang_vel_w = _load("body_ang_vel_w")[:, body_cols]

Attributes

dofs_pos instance-attribute
dofs_pos = _load('joint_pos')
dofs_vel instance-attribute
dofs_vel = _load('joint_vel')
time_step_total instance-attribute
time_step_total = self.dofs_pos.shape[0]
links_pos_w = _load('body_pos_w')[:, body_cols]
links_quat_w = _load('body_quat_w')[:, body_cols]
links_lin_vel_w = _load('body_lin_vel_w')[:, body_cols]
links_ang_vel_w = _load('body_ang_vel_w')[:, body_cols]

Methods:

MultiMotionLoader

MultiMotionLoader(motion_files: list[str], links_idx: Tensor, device: device, links_name: list[str] | None = None)

Batched verbatim loader for a list of BeyondMimic .npz clips.

Mirrors :class:MotionLoader field-for-field, but stacks M clips of possibly different lengths into padded (M, T_max, ...) tensors plus a per-clip frame_total (M,). Fields are loaded VERBATIM (no interpolation, no velocity recompute) so each clip is bit-identical to the single-clip path. With M == 1 (the single-clip case) T_max == T and no padding occurs, so library[0, t] is plain single-clip indexing.

Padding edge-replicates each clip's last valid frame (never zeros): a zero quaternion would NaN yaw_quat, and an over-read then degrades to a "hold last frame" end-of-motion behavior (the per-env wrap keeps reads in range anyway).

Source code in eden/managers/terms/commands/mimic.py
def __init__(
    self,
    motion_files: list[str],
    links_idx: torch.Tensor,
    device: torch.device,
    links_name: list[str] | None = None,
) -> None:
    if not motion_files:
        raise ValueError("MultiMotionLoader: motion_files is empty")
    self.motion_refs = [os.path.splitext(os.path.basename(f))[0] for f in motion_files]
    clips = [np.load(f) for f in motion_files]
    lengths = [int(c["joint_pos"].shape[0]) for c in clips]
    # All fields of a clip must share its frame count; otherwise the per-field
    # padding below would misalign body vs joint frames silently.
    for f, c in zip(motion_files, clips):
        for k in ("joint_vel", "body_pos_w", "body_quat_w", "body_lin_vel_w", "body_ang_vel_w"):
            if k in c and int(c[k].shape[0]) != int(c["joint_pos"].shape[0]):
                raise ValueError(
                    f"{f}: field '{k}' has {c[k].shape[0]} frames but joint_pos has {c['joint_pos'].shape[0]}"
                )
    # Resolve each clip's body columns by name (order-independent of Genesis) when the npz
    # provides ``body_names``, else fall back to the Genesis idx_local in ``links_idx``.
    body_cols = [resolve_body_cols(c, links_idx, links_name) for c in clips]
    m, t_max = len(clips), max(lengths)
    self.frame_total = torch.tensor(lengths, dtype=torch.long, device=device)
    self.time_step_total = t_max  # nominal; per-clip lengths live in frame_total

    def _stack(key: str, link_sliced: bool) -> torch.Tensor:
        out: torch.Tensor | None = None
        for i, clip in enumerate(clips):
            t = torch.tensor(clip[key], dtype=torch.float32, device=device)
            if link_sliced:
                t = t[:, body_cols[i]]
            if out is None:
                out = t.new_zeros((m, t_max, *t.shape[1:]))
            length = t.shape[0]
            out[i, :length] = t
            if length < t_max:  # edge-replicate the last valid frame
                out[i, length:] = t[length - 1]
        return out

    self.dofs_pos = _stack("joint_pos", False)  # (M, T_max, 29)
    self.dofs_vel = _stack("joint_vel", False)  # (M, T_max, 29)
    self.links_pos_w = _stack("body_pos_w", True)  # (M, T_max, n_bodies, 3)
    self.links_quat_w = _stack("body_quat_w", True)  # (M, T_max, n_bodies, 4)
    self.links_lin_vel_w = _stack("body_lin_vel_w", True)
    self.links_ang_vel_w = _stack("body_ang_vel_w", True)

Attributes

motion_refs instance-attribute
motion_refs = [os.path.splitext(os.path.basename(f))[0] for f in motion_files]
frame_total instance-attribute
frame_total = torch.tensor(lengths, dtype=torch.long, device=device)
time_step_total instance-attribute
time_step_total = t_max
dofs_pos instance-attribute
dofs_pos = _stack('joint_pos', False)
dofs_vel instance-attribute
dofs_vel = _stack('joint_vel', False)
links_pos_w = _stack('body_pos_w', True)
links_quat_w = _stack('body_quat_w', True)
links_lin_vel_w = _stack('body_lin_vel_w', True)
links_ang_vel_w = _stack('body_ang_vel_w', True)

Methods:

MotionCommand

MotionCommand(env: EnvBase, options: CommandTermOptions)

Bases: CommandTerm

Motion command for mimicking one or more motion clips.

Backed by a :class:MultiMotionLoader clip library and a per-env (motion_id, time_steps) gather. A single clip is just the M == 1 case: every env stays on clip 0 and the gather reduces to plain single-clip indexing. Passing motion_files=[...] (or a list to :func:motion_command_options) assigns each env its own clip, re-sampled on every reset/wrap so all clips are exercised regardless of num_envs vs num_clips.

Sampling modes: start / uniform work for any clip count; adaptive (the failure-bin start-time curriculum) requires a single clip, since the bins partition one clip's timeline.

Source code in eden/managers/terms/commands/mimic.py
def __init__(self, env: EnvBase, options: CommandTermOptions):
    super().__init__(env, options)

    self._cache_robot_dofs_pos = None
    self._cache_robot_dofs_vel = None
    self._cache_robot_links_pos_w = None
    self._cache_robot_links_quat_w = None
    self._cache_robot_links_lin_vel_w = None
    self._cache_robot_links_ang_vel_w = None

    self.pose_ranges = self._build_range_tensor(self.pose_range)
    self.velocity_ranges = self._build_range_tensor(self.velocity_range)

    self.robot = None
    self.robot_base_dofs_index = None
    self.base_and_dofs_index = None
    self._reset_through_mask = False
    self._reset_dofs_idx = None
    self._reset_qs_idx = None
    self.motion_anchor_link_index = None
    self.base_link_index = None
    self.links_idx = None

    self.motion = None
    self.num_clips = None
    self.time_steps = None
    # Per-env assigned clip (all zeros for single-clip) and that clip's length.
    self.motion_id = None
    self.frame_total = None
    self.links_pos_relative_w = None
    self.links_quat_relative_w = None

    self.bin_count = None
    # Both EMAs are keyed by the bin the episode STARTED in and accumulated
    # at the same resample events, so they stay phase-aligned. Their ratio
    # is a bounded per-episode failure RATE in [0, 1] (see _adaptive_sampling):
    #   bin_failed_count[k] — episodes that started in bin k AND terminated
    #   bin_starts_count[k] — episodes that started in bin k (any outcome)
    self.bin_failed_count = None
    self._current_bin_failed = None
    self.bin_starts_count = None
    self._current_bin_starts = None
    # Per-env start bin of the currently-running episode (the bin we
    # attribute that episode's outcome to when it ends).
    self.start_bin = None
    self.kernel = None

Attributes

entity_name class-attribute instance-attribute
entity_name: str = ''
ghost_entity_name class-attribute instance-attribute
ghost_entity_name: str = ''
motion_file class-attribute instance-attribute
motion_file: str = 'motion.npz'
motion_files class-attribute instance-attribute
motion_files: list[str] = []
anchor_link_name: str = 'torso_link'
base_link_name: str | None = None
base_joint_name class-attribute instance-attribute
base_joint_name: str | None = None
links_name: list[str] = ['pelvis', 'left_hip_roll_link', 'left_knee_link', 'left_ankle_roll_link', 'right_hip_roll_link', 'right_knee_link', 'right_ankle_roll_link', 'torso_link', 'left_shoulder_roll_link', 'left_elbow_link', 'left_wrist_yaw_link', 'right_shoulder_roll_link', 'right_elbow_link', 'right_wrist_yaw_link']
pose_range class-attribute instance-attribute
pose_range: dict[str, tuple[float, float]] = {}
velocity_range class-attribute instance-attribute
velocity_range: dict[str, tuple[float, float]] = {}
dofs_position_range class-attribute instance-attribute
dofs_position_range: tuple[float, float] = (-0.1, 0.1)
adaptive_kernel_size class-attribute instance-attribute
adaptive_kernel_size: int = 1
adaptive_lambda class-attribute instance-attribute
adaptive_lambda: float = 0.8
adaptive_uniform_ratio class-attribute instance-attribute
adaptive_uniform_ratio: float = 0.1
adaptive_alpha class-attribute instance-attribute
adaptive_alpha: float = 0.001
adaptive_failure_rate_max_over_mean class-attribute instance-attribute
adaptive_failure_rate_max_over_mean: float = 10.0
sampling_mode class-attribute instance-attribute
sampling_mode: Literal['adaptive', 'uniform', 'start'] = 'adaptive'
pose_ranges instance-attribute
pose_ranges = self._build_range_tensor(self.pose_range)
velocity_ranges instance-attribute
velocity_ranges = self._build_range_tensor(self.velocity_range)
robot instance-attribute
robot = None
robot_base_dofs_index instance-attribute
robot_base_dofs_index = None
base_and_dofs_index instance-attribute
base_and_dofs_index = None
motion_anchor_link_index = None
base_link_index = None
links_idx = None
motion instance-attribute
motion = None
num_clips instance-attribute
num_clips = None
time_steps instance-attribute
time_steps = None
motion_id instance-attribute
motion_id = None
frame_total instance-attribute
frame_total = None
links_pos_relative_w = None
links_quat_relative_w = None
bin_count instance-attribute
bin_count = None
bin_failed_count instance-attribute
bin_failed_count = None
bin_starts_count instance-attribute
bin_starts_count = None
start_bin instance-attribute
start_bin = None
kernel instance-attribute
kernel = None
command property
command: Tensor
dofs_pos property
dofs_pos: Tensor
dofs_vel property
dofs_vel: Tensor
links_pos_w: Tensor
links_quat_w: Tensor
links_lin_vel_w: Tensor
links_ang_vel_w: Tensor
anchor_pos_w property
anchor_pos_w: Tensor
anchor_quat_w property
anchor_quat_w: Tensor
anchor_lin_vel_w property
anchor_lin_vel_w: Tensor
anchor_ang_vel_w property
anchor_ang_vel_w: Tensor
base_pos_w property
base_pos_w: Tensor
base_quat_w property
base_quat_w: Tensor
base_lin_vel_w property
base_lin_vel_w: Tensor
base_ang_vel_w property
base_ang_vel_w: Tensor
robot_dofs_pos property
robot_dofs_pos: Tensor
robot_dofs_vel property
robot_dofs_vel: Tensor
robot_links_pos_w: Tensor
robot_links_quat_w: Tensor
robot_links_lin_vel_w: Tensor
robot_links_ang_vel_w: Tensor
robot_anchor_pos_w property
robot_anchor_pos_w: Tensor
robot_anchor_quat_w property
robot_anchor_quat_w: Tensor
robot_anchor_lin_vel_w property
robot_anchor_lin_vel_w: Tensor
robot_anchor_ang_vel_w property
robot_anchor_ang_vel_w: Tensor

Methods:

build
build() -> None
Source code in eden/managers/terms/commands/mimic.py
def build(self) -> None:
    self.robot = self.resolve_entity(self.entity_name)

    # NOTE: avoid tensor indexing by passing int
    robot_base_dofs_index = self.robot.find_free_base_dofs_idx_local(self.base_joint_name)
    self.robot_base_dofs_index = torch.as_tensor(
        robot_base_dofs_index, dtype=torch.long, device=self.device
    ).contiguous()
    self.base_and_dofs_index = torch.cat([self.robot_base_dofs_index, self.robot.dofs_idx_local]).contiguous()

    # Resolve a links_name member to its row in ``links_*_w``, failing loudly on a mistyped or reordered
    # name instead of a bare index error or (for the base) a silent wrong-link root reset.
    def links_index(link_name: str, field_name: str) -> int:
        if link_name not in self.links_name:
            raise ValueError(
                f"{term_subject(self)}: {field_name}={link_name!r} must be a member "
                f"of links_name; got links_name={self.links_name}"
            )
        return self.links_name.index(link_name)

    self.motion_anchor_link_index = links_index(self.anchor_link_name, "anchor_link_name")
    self.base_link_index = 0 if self.base_link_name is None else links_index(self.base_link_name, "base_link_name")

    self.links_idx = torch.tensor(
        self.robot.find_named_links_idx_local(self.links_name, preserve_order=True)[1],
        dtype=torch.long,
        device=self.device,
    )

    # Single-clip is the M=1 case of the clip library: ``motion_file`` is the
    # single-clip shorthand for ``motion_files=[motion_file]``.
    motion_files = list(self.motion_files) if self.motion_files else [self.motion_file]
    self.num_clips = len(motion_files)
    if self.num_clips > 1 and self.sampling_mode == "adaptive":
        raise ValueError(
            "adaptive sampling requires a single clip (the failure-bin curriculum partitions "
            f"one clip's timeline); got {self.num_clips} clips. Use sampling_mode 'start' or 'uniform'."
        )

    self.motion = MultiMotionLoader(motion_files, self.links_idx, device=self.device, links_name=self.links_name)
    self.time_steps = torch.zeros(self.num_envs, dtype=torch.long, device=self.device)
    self.motion_id = torch.zeros(self.num_envs, dtype=torch.long, device=self.device)
    self.frame_total = self.motion.frame_total[self.motion_id]  # (N,) per-env clip length
    self.links_pos_relative_w = torch.zeros(self.num_envs, len(self.links_name), 3, device=self.device)
    self.links_quat_relative_w = torch.zeros(self.num_envs, len(self.links_name), 4, device=self.device)
    self.links_quat_relative_w[:, :, 0].fill_(1.0)

    # Adaptive failure-bin curriculum state. The bins partition the (single)
    # clip's timeline; for multi-clip these stay allocated but unused
    # (``sampling_mode == "adaptive"`` is rejected above for M > 1).
    self.bin_count = int(self.motion.time_step_total // (1 / self._env.dt)) + 1
    self.bin_failed_count = torch.zeros(self.bin_count, dtype=torch.float, device=self.device)
    self._current_bin_failed = torch.zeros(self.bin_count, dtype=torch.float, device=self.device)
    self.bin_starts_count = torch.zeros(self.bin_count, dtype=torch.float, device=self.device)
    self._current_bin_starts = torch.zeros(self.bin_count, dtype=torch.float, device=self.device)
    self.start_bin = torch.zeros(self.num_envs, dtype=torch.long, device=self.device)
    self.kernel = torch.tensor(
        [self.adaptive_lambda**i for i in range(self.adaptive_kernel_size)],
        device=self.device,
    )
    self.kernel = self.kernel / self.kernel.sum()

    # How `_resample_command` hands the reset state to Genesis. Where its setters can keep the
    # bool env mask on the device — a `torch.where` into the state and a masked
    # forward-kinematics kernel — they take every env's row and a `range` for each DOF axis
    # (`RigidEntity.setters_take_every_row`). Otherwise, e.g. under a `dofs_name` reordered away from the
    # asset's or with hibernation on, they take the selected rows and the DOF indices: Genesis'
    # indexed path, which pays a `nonzero()` GPU->CPU sync per setter to turn the mask into indices.
    dofs_range = contiguous_range(self.base_and_dofs_index)
    qs_range = contiguous_range(self.robot.qs_idx_local)
    self._reset_through_mask = self.robot.setters_take_every_row and dofs_range is not None and qs_range is not None
    self._reset_dofs_idx = dofs_range if self._reset_through_mask else self.base_and_dofs_index
    self._reset_qs_idx = qs_range if self._reset_through_mask else self.robot.qs_idx_local

    self._init_sampling_stats()
    # Initial per-env clip assignment. Gated on M > 1 so the single-clip path
    # draws no RNG here (motion_id is already all-zeros) and its sampling
    # stream does not depend on the clip library.
    if self.num_clips > 1:
        self._sample_motions(torch.ones(self.num_envs, dtype=torch.bool, device=self.device))
    self._update_robot_state_cache()
compute
compute(dt: float) -> None
Source code in eden/managers/terms/commands/mimic.py
def compute(self, dt: float) -> None:
    self._update_robot_state_cache()
    super().compute(dt)
post_physics_refresh
post_physics_refresh() -> None
Source code in eden/managers/terms/commands/mimic.py
def post_physics_refresh(self) -> None:
    # The robot-state cache is refreshed at TWO distinct points per control step, by
    # design — they snapshot different states:
    #   1. here (post-physics, before reward) so reward/termination grade S_N, the
    #      pre-reset post-physics state;
    #   2. in compute() (which the command manager runs AFTER _reset_idx) so the
    #      cache then reflects the POST-reset state of just-reset envs, which the
    #      command and the end-of-step observation read.
    # Dropping either leaves one of those consumers a control step stale. See RLEnvBase.step.
    self._update_robot_state_cache()
    # Advance-then-grade: the clip clock was advanced (and wrapped) in
    # pre_physics_advance, so recompute the anchor-relative transforms against
    # the advanced reference and the just-refreshed post-physics robot cache.
    # This is what makes reward/termination grade the temporally-aligned frame.
    self._update_relative_transforms()
frame_total_per_env
frame_total_per_env() -> Tensor

Per-env motion length in frames (each env's assigned clip length; a constant vector when there is a single clip).

Source code in eden/managers/terms/commands/mimic.py
def frame_total_per_env(self) -> torch.Tensor:
    """Per-env motion length in frames (each env's assigned clip length;
    a constant vector when there is a single clip).
    """
    return self.frame_total
clip_finished
clip_finished() -> Tensor

Per-env (N,) bool: has this env's clip reached its final frame?

The command path's answer to "is the motion over", and the counterpart of :meth:~eden.managers.motion_manager.MotionManager.get_motion_phase >= 1 on the manager path. It is only meaningful because :meth:advance_reference holds the clock at frame_total - 1 instead of wrapping: in simulation :meth:pre_physics_advance resamples at the boundary, so the clock regresses and this goes False again, which is correct — a wrapped clip is not a finished one.

DeploymentBase.motion_finished reduces this for the deploy loop's end-of-clip handover; nothing in the training loop reads it.

Source code in eden/managers/terms/commands/mimic.py
def clip_finished(self) -> torch.Tensor:
    """Per-env ``(N,)`` bool: has this env's clip reached its final frame?

    The command path's answer to "is the motion over", and the counterpart of
    :meth:`~eden.managers.motion_manager.MotionManager.get_motion_phase` ``>= 1`` on the
    manager path. It is only meaningful because :meth:`advance_reference` *holds* the clock
    at ``frame_total - 1`` instead of wrapping: in simulation
    :meth:`pre_physics_advance` resamples at the boundary, so the clock regresses and this
    goes ``False`` again, which is correct — a wrapped clip is not a finished one.

    ``DeploymentBase.motion_finished`` reduces this for the deploy loop's end-of-clip
    handover; nothing in the training loop reads it.
    """
    return self.time_steps >= self.frame_total - 1
gather_motion_frames
gather_motion_frames(field_name: str, frame_idx: Tensor) -> Tensor

Gather motion field field_name at per-env frame indices.

frame_idx is (N, K) long -> returns (N, K, ...). Each env indexes its own assigned clip (motion_id); with a single clip every env indexes the one shared clip's time axis.

Source code in eden/managers/terms/commands/mimic.py
def gather_motion_frames(self, field_name: str, frame_idx: torch.Tensor) -> torch.Tensor:
    """Gather motion field ``field_name`` at per-env frame indices.

    ``frame_idx`` is ``(N, K)`` long -> returns ``(N, K, ...)``. Each env
    indexes its own assigned clip (``motion_id``); with a single clip every
    env indexes the one shared clip's time axis.
    """
    field = getattr(self.motion, field_name)  # (M, T_max, ...)
    n, k = frame_idx.shape
    mid = self.motion_id[:, None].expand(n, k)
    return field[mid, frame_idx]  # (N, K, ...)
pre_physics_advance
pre_physics_advance() -> None

Advance the clip clock (+ wrap-teleport) BEFORE the physics sub-steps.

Advance-then-grade: the reference frame is advanced before physics so the post-physics robot (graded at reward/termination time) is compared against the temporally-aligned frame — matching :class:MotionManager, whose clock advances at the start of MimicRLEnvBase.step. Advancing in :meth:compute (after reward) would grade against a one-control-step-stale reference. Wrap-teleport runs here, before physics, so the teleported robot is forward-stepped and its forward kinematics stay fresh at reward time (no stale-FK spurious terminations).

Source code in eden/managers/terms/commands/mimic.py
def pre_physics_advance(self) -> None:
    """Advance the clip clock (+ wrap-teleport) BEFORE the physics sub-steps.

    Advance-then-grade: the reference frame is advanced before physics so the
    post-physics robot (graded at reward/termination time) is compared against
    the temporally-aligned frame — matching :class:`MotionManager`, whose clock
    advances at the start of ``MimicRLEnvBase.step``. Advancing in
    :meth:`compute` (after reward) would grade against a
    one-control-step-stale reference. Wrap-teleport runs here, before physics,
    so the teleported robot is forward-stepped and its forward kinematics stay
    fresh at reward time (no stale-FK spurious terminations).
    """
    self.time_steps += 1
    wrapped = self.time_steps >= self.frame_total  # per-env clip length
    # The bool mask `_resample_command` gets from `_resample` too, so the wrap costs no sync of
    # its own. The `.any()` gate is one: an all-False mask would write nothing, but the
    # full-batch reset draws, math and setter calls still cost ~1 ms at 4096 envs, far more
    # than the sync.
    if wrapped.any():
        self._resample_command(wrapped)
advance_reference
advance_reference() -> None

Advance deployment playback, holding the last frame without a wrap teleport.

Source code in eden/managers/terms/commands/mimic.py
def advance_reference(self) -> None:
    """Advance deployment playback, holding the last frame without a wrap teleport."""
    self.time_steps.copy_(torch.minimum(self.time_steps + 1, self.frame_total - 1))
rewind_reference
rewind_reference() -> None

Rewind the clip clock to frame 0 for every env — the deploy path's handover.

Clock only: no :meth:_resample_command, so the robot is neither teleported nor pose-randomized and the clip assignment stands. That is the whole point on hardware, where the sim's reset-state initialization has nothing to act on, and it mirrors what MotionManager.rewind_to_start does for the manager path.

Source code in eden/managers/terms/commands/mimic.py
def rewind_reference(self) -> None:
    """Rewind the clip clock to frame 0 for every env — the deploy path's handover.

    Clock only: no :meth:`_resample_command`, so the robot is neither teleported nor
    pose-randomized and the clip assignment stands. That is the whole point on hardware,
    where the sim's reset-state initialization has nothing to act on, and it mirrors what
    ``MotionManager.rewind_to_start`` does for the manager path.
    """
    if self.time_steps is not None:
        self.time_steps.zero_()
        # Deployment restarts only on reset/START, not on the training timer.
        # Otherwise compute() can still resample and initialize the robot pose.
        self.time_left.fill_(float("inf"))
draw_vis
draw_vis() -> None
Source code in eden/managers/terms/commands/mimic.py
def draw_vis(self) -> None:
    if not self.ghost_entity_name:
        return

    if self._env.training:
        en.logger.warning("Drawing ghost is expensive in training. Please make sure if this is intended.")

    ghost_entity: Entity = self.resolve_entity(self.ghost_entity_name)
    # NOTE: fixed base entity must use set_pos and set_quat to set the pose
    ghost_entity.set_pos(self.base_pos_w)
    ghost_entity.set_quat(self.base_quat_w)
    ghost_entity.set_dofs_pos(self.dofs_pos)

Functions:

resolve_body_cols

resolve_body_cols(clip, links_idx, links_name) -> 'torch.Tensor | list[int]'

Column indices into a clip's body_*_w arrays for the requested links.

Resolution order:

  1. If the npz stores body_names, resolve by name — independent of Genesis's internal link ordering (which changed breadth-first -> depth-first in genesis 1.2.0).
  2. Else, if the canonical G1 breadth-first link list (G1_DOFS29_TRACKED_LINKS_NAME) covers every requested link, resolve against that order. Every body_names-less npz predates genesis 1.2.0 and was therefore written in the old breadth-first order, so this makes legacy G1 clips correct under a depth-first runtime instead of silently mis-sliced by idx_local.
  3. Else (unrecognized body set, e.g. a non-G1 clip) fall back to the Genesis idx_local in links_idx — order-coupled, correct only when generated under the runtime's ordering.
Source code in eden/managers/terms/commands/mimic.py
def resolve_body_cols(clip, links_idx, links_name) -> "torch.Tensor | list[int]":
    """Column indices into a clip's ``body_*_w`` arrays for the requested links.

    Resolution order:

    1. If the npz stores ``body_names``, resolve by name — independent of Genesis's internal link
       ordering (which changed breadth-first -> depth-first in genesis 1.2.0).
    2. Else, if the canonical G1 breadth-first link list (``G1_DOFS29_TRACKED_LINKS_NAME``) covers
       every requested link, resolve against that order. Every ``body_names``-less npz predates
       genesis 1.2.0 and was therefore written in the old breadth-first order, so this makes legacy
       G1 clips correct under a depth-first runtime instead of silently mis-sliced by ``idx_local``.
    3. Else (unrecognized body set, e.g. a non-G1 clip) fall back to the Genesis ``idx_local`` in
       ``links_idx`` — order-coupled, correct only when generated under the runtime's ordering.
    """
    if links_name is not None:
        if "body_names" in clip:
            return columns_by_name(clip["body_names"], links_name, what="motion npz 'body_names'")
        if all(n in G1_DOFS29_TRACKED_LINKS_NAME for n in links_name):
            en.logger.warning(_LEGACY_BFS_WARNING)
            return columns_by_name(G1_DOFS29_TRACKED_LINKS_NAME, links_name, what="G1_DOFS29_TRACKED_LINKS_NAME")
    en.logger.warning(_LEGACY_NPZ_WARNING)
    return links_idx

motion_command_options

motion_command_options(motion_file: str | list[str], *, anchor_link_name: str, links_name: list[str], entity_name: str = 'robot', ghost_entity_name: str = '', debug_vis: bool = False, sampling_mode: str = 'start', **overrides) -> dict[str, 'CommandTermOptions']

Build the {"motion": <term>} command_options for a motion-tracking policy.

Always builds a single :class:MotionCommand; a list of clip paths populates its motion_files (each env rolls its own assigned clip) and a single path populates motion_file (the M=1 case). The accessor interface is identical either way, so a policy reads motion source-agnostically — a new ONNX motion policy reuses this helper by passing only its body/anchor names. overrides replace shared term fields.

Source code in eden/managers/terms/commands/mimic.py
def motion_command_options(
    motion_file: str | list[str],
    *,
    anchor_link_name: str,
    links_name: list[str],
    entity_name: str = "robot",
    ghost_entity_name: str = "",
    debug_vis: bool = False,
    sampling_mode: str = "start",
    **overrides,
) -> dict[str, "CommandTermOptions"]:
    """Build the ``{"motion": <term>}`` command_options for a motion-tracking policy.

    Always builds a single :class:`MotionCommand`; a list of clip paths populates
    its ``motion_files`` (each env rolls its own assigned clip) and a single path
    populates ``motion_file`` (the M=1 case). The accessor interface is identical
    either way, so a policy reads motion source-agnostically — a new ONNX motion
    policy reuses this helper by passing only its body/anchor names. ``overrides``
    replace shared term fields.
    """
    shared = dict(
        resampling_time_range=(1.0e9, 1.0e9),
        debug_vis=debug_vis,
        entity_name=entity_name,
        ghost_entity_name=ghost_entity_name,
        anchor_link_name=anchor_link_name,
        links_name=links_name,
        pose_range={},
        velocity_range={},
        dofs_position_range=(0.0, 0.0),
        sampling_mode=sampling_mode,
    )
    shared.update(overrides)
    if isinstance(motion_file, (list, tuple)):
        return dict(motion=MotionCommand.configure(motion_files=list(motion_file), **shared))
    return dict(motion=MotionCommand.configure(motion_file=motion_file, **shared))

See also