microduck_rl/docs/superpowers/specs/2026-07-23-swizzle-env-design.md
Upstream Snapshot 47372443ff Import upstream snapshot d424a0c899f6b33cbd3daeb279913134349c0b63
Upstream: https://github.com/pollen-robotics/microduck_rl
Upstream-Commit: d424a0c899f6b33cbd3daeb279913134349c0b63
Upstream-Branch: develop
2026-08-28 15:41:56 +08:00

104 lines
5.1 KiB
Markdown

# Swizzle roller environment — design
**Date:** 2026-07-23
**Branch:** `new_pre_alpha_rollers`
## Goal
A **separate** roller task that produces a **clean classic swizzle**: both blades
stay on the ground, the legs spread out and pull back in **symmetrically**
(hourglass pattern), propelling the duck forward. This is a simpler, more stable
alternative to the alternating stride (`Mjlab-Velocity-Flat-MicroDuck-Rollers`),
motivated by the stride not transferring well to the real robot. The stride env is
left untouched.
Sim2real is a target: same robot, observations, command semantics, domain
randomization and ONNX export as the stride env, so it **deploys identically**
(`microduck_runtime ... --roller`, same flags).
## Approach (chosen: A — remove anti-swizzle + reward symmetry)
The base roller velocity recipe *naturally* converges to a swizzle (this is the
attractor we fought against for the stride). So the simplest way to a clean swizzle
is to **remove the anti-swizzle machinery** and **reward the swizzle's defining
features** (symmetry, feet grounded). No phase scripting.
Rejected: B (explicit hourglass foot-pattern shaping) and C (phase-driven scripted
trajectory) — more complex, only needed if A's swizzle looks messy (rhythm/amplitude).
## Structure
- New file `src/mjlab_microduck/tasks/microduck_velocity_swizzle_env_cfg.py` with
`make_microduck_velocity_swizzle_env_cfg(play=False)` and `MicroduckSwizzleRlCfg`.
Built from `make_velocity_env_cfg()` + the roller robot, mirroring
`microduck_velocity_rollers_env_cfg.py`'s structure (obs, DR, command, curricula).
- Register `Mjlab-Velocity-Swizzle-MicroDuck` in `tasks/__init__.py`.
- Reuse everything sim2real from the stride env: robot cfg, 61D obs layout, command
(cmd_x push/coast/brake, straight-line: `ang_vel_z=(0,0)`, `heading_hold`), all DR
events + curricula (com, wheel_friction), `action_over_limit`, ONNX export path.
## Reward recipe
**Kept** (task + stability + sim2real):
`wheel_speed` (forward propulsion, the task), `braking`, `upright`, `com_height_target`,
`pose`, `forward_lean`, `heading_hold`, `action_over_limit`, `feet_flat`,
`self_collisions`, regularizers (`action_rate_l2` + curriculum, `neck_action_rate_l2`,
`neck_joint_pos_l2`, `joint_torques_l2`).
**Removed** (stride / anti-swizzle machinery):
`single_support`, `glide`, `skating_air_time`, `gait_symmetry`, `hip_roll_neutral`
(the last would fight the swizzle's lateral out-motion).
**Added** (pro-swizzle):
- `leg_symmetry` — reward left/right legs mirroring. The robot uses mirrored L/R
sign conventions, so a symmetric config satisfies `q_left + q_right ≈ 0` per pair.
Return `-mean_pairs |q_left + q_right|` (L1, constant gradient — same form as the
existing `bilateral_symmetry_penalty`) over the leg joint pairs (hip_yaw, hip_roll,
hip_pitch, knee, ankle); used with a positive weight so asymmetry is penalised and
the symmetric swizzle is favoured. This is the swizzle's defining feature.
(Implementation: the existing `bilateral_symmetry_penalty` takes explicit L/R
index lists; add a thin wrapper that resolves the L/R leg-joint pairs by name at
runtime so it can be configured without hard-coded indices.)
- `grounded` — reward both blades in contact (n_contact == 2) while pushing, so the
feet stay down (classic swizzle, no lifting). Small weight. New mdp function
(mirror of `single_support_reward` but rewarding double support). Gate on
`cmd_x >= 0` like the others.
Leave `hip_roll` pose std loose (as in the stride env) so the legs can spread.
## New mdp functions (in `tasks/mdp.py`)
1. `leg_symmetry_reward(env, asset_cfg)` — resolve L/R leg joint pairs by name,
return `-mean_pairs |q_left + q_right|` (used with a positive weight).
2. `grounded_reward(env, sensor_name, command_name)` — reward exactly-two-blades in
contact, scaled by `clamp(cmd_x, 0)`.
## Command / sim2real (identical to stride)
`cmd_x` push/coast/brake, `lin_vel_y=0`, `ang_vel_z=(0,0)` (straight-line), full DR
(com, head_com, mass/inertia, joint friction, armature, wheel friction, velocity
pushes, IMU misalignment, encoder bias, obs delays), 61D obs, `vel_scale=0.3`.
Deploys with the same runtime flags as the stride roller policy.
## PPO config
Reuse `MicroduckRollersRlCfg`'s hyperparameters (same actor/critic 512-256-128 ELU,
PPO settings, `entropy_coef=0.03`), new `experiment_name`/`run_name` = `velocity_swizzle`.
## Testing / verification
- Smoke test: `uv run train Mjlab-Velocity-Swizzle-MicroDuck --env.scene.num-envs 16
--agent.max-iterations 2` runs without error; `leg_symmetry` and `grounded` appear
in the reward log.
- Watch on a real run: `leg_symmetry` high (symmetric), `grounded` high (both feet
down), `wheel_speed` rising (moves forward). Video: symmetric hourglass swizzle,
both blades on the ground.
## Tuning knobs (post-first-run)
- If not symmetric enough → raise `leg_symmetry` weight.
- If it lifts feet → raise `grounded` weight.
- If it barely moves → the symmetry/grounded weights are too high vs `wheel_speed`;
lower them.
- If the swizzle looks messy (rhythm/amplitude) → escalate to Approach B.