Microduck Workshop
GitHub ↗

SELF-PACED WORKSHOP

Adapt walking to slopes

Adapt walking to slopes. Explore the concepts, run the lab, and inspect its results.

Chapter 4 · Estimated learning time: 30 minutes

Start with a testable question

Do not assume the old actor will fail. Hold the policy, command, duration, friction and initial-state distribution fixed while varying slope. Test traversal and balance, not just a single attractive frame.

Scroll horizontally to view the full diagram

Terrain generalizationBaseline:Same actor: flat/up/down;Isaac Lab training:Sample parallel terrains;Held-out evaluation:Arrival, falls, speed errorTerrain generalizationBaselineSame actor: flat/up/downIsaac Lab trainingSample parallel terrainsHeld-out evaluationArrival, falls, speederror
Evaluate before adapting. Retain flat-ground ability while testing improvements on slopes.

Build a flat–ramp–landing scene

The surface has a 0.35 m flat approach, a 1 m horizontal ramp, and a landing. Positive angles ascend; negative angles descend. A continuous collision mesh avoids unintended steps. Visual materials do not determine contact dynamics.

Isaac Sim flat approach, ramp and target line
The darker surface marks the ramp; gold guides show direction and green marks the target. These visual markings do not change contact physics.
Flat: 0.35 mRamp run: 1 mLandingh(x) = tan(α) · clip(x − 0.35, 0, 1)

Adjust the slope to compare ascent and descent. Horizontal and vertical scales match; the landing height changes with the angle.
坡面高度
\[h(x)=\tan(\alpha)\,\operatorname{clip}(x-0.35,0,1)\]
Python · 核心代码节选Full source ↗
slope = math.tan(math.radians(cfg.slope_deg))
xs = [-48.0, 2.35, 3.35, 52.0]
zs = [0.0, 0.0, slope, slope]
vertices = [(x, y, z) for x, z in zip(xs, zs) for y in [-48.0, 52.0]]
origin = np.array([2.0, 2.0, 0.0])

The mesh origin is (2, 2, 0). Subtracting it places the robot at relative x = 0 and the ramp between x = 0.35 m and 1.35 m. Continuous approach and landing support extends across 100 × 100 m so failures do not fall off a small terrain tile. Isaac Lab’s TerrainGenerator and TerrainImporter share this geometry across visualization, physics tests and parallel training.

Test the official walking policy first

Run the official walking policy on a 5° uphill ramp for 20 seconds. Run the command below from the repository root with a new output directory.

Terminal
python3 scripts/workshop.py terrain \
  --model models/walk.onnx \
  --slope 5 --num-envs 1 --seconds 20 --seed 42001 \
  --output outputs/slope-up-05

Without --headless this opens Isaac Sim. Use --num-envs 5 --headless for batched evaluation, and test 0, ±5, ±10 and ±15 degrees in separate output directories. The first 2 s are standing; then the command is 0.25 m/s. Actual speed may differ.

Success requires reaching x ≥ 1.45 m within 20 s with lateral error below 0.3 m and staying upright throughout. A fall remains a failure even if the body later slides across the finish. Reports separate reaching, falls and velocity RMSE.

SlopePassFall
0°5/50/5
+5°0/50/5
-5°0/55/5
+10°0/50/5
-10°0/55/5
+15°0/50/5
-15°0/55/5

Each angle uses five lightly perturbed starts. Flat trials pass; uphill trials stall without falling; downhill trials fall. This small baseline identifies an adaptation need, not general reliability.

Why flat-ground experience may not transfer

A slope changes foot clearance, gravity along the surface and usable friction. Insufficient clearance or propulsion can stall ascent; downhill landing and balance changes can cause falls. Survival alone misses stalled traversal.

Continue learning across terrain types

Keep the official ONNX unchanged. Initialize a new actor from its weights and normalization, with a fresh critic and optimizer. Mix 25% flat terrain with ascending and descending terrain. Verify initial actor parity before learning.

Terminal
python3 scripts/workshop.py train-terrain \
  --model models/walk.onnx \
  --num-envs 256 --iterations 1000 --seed 43001 \
  --output outputs/terrain-training-01

Training slopes are tan(α), not degrees: 0.03–0.22 is about 1.7°–12.4°. Training uses pyramidal and inverted slopes; evaluation uses straight ramps and separate seeds.

Python · 核心代码节选Full source ↗
sub_terrains = {
    "flat": MeshPlaneTerrainCfg(proportion=0.25),
    "slopes": HfPyramidSlopedTerrainCfg(
        proportion=0.375, slope_range=(0.03, 0.22), platform_width=0.7
    ),
    "descending": HfInvertedPyramidSlopedTerrainCfg(
        proportion=0.375, slope_range=(0.03, 0.22), platform_width=0.7
    ),
}

The fixed terrain weights retain their original training lineage. Current practice runs use the same full-contact robot as the demonstrations, so their contact configuration differs from the historical training and defines a new experiment. Training is physics-only; inspect exported policies in the shared RTX scene.

Balance and direction both matter

第一步:定义有物理含义的误差
\[e_{\omega}=\omega_z-\omega_z^*,\qquad u=\frac{e_{\omega}}{\sigma}\]
第二步:把误差转换为平滑、有界的奖励
\[r_{\omega}=\exp(-u^2)=\exp\!\left[-\left(\frac{\omega_z-\omega_z^*}{0.15}\right)^2\right]\]
Python · 核心代码节选Full source ↗
def yaw_rate_tracking(env, std=0.15):
    # Match the commanded turn rate without penalizing normal roll/pitch motion.
    velocity = env.scene["robot"].data.root_link_ang_vel_b.torch
    target = env.command_manager.get_command("base_velocity")[:, 2]

    # Squared, normalized error gives a symmetric reward in (0, 1].
    return torch.exp(-((velocity[:, 2] - target) / std).square())


cfg.rewards.yaw_precision = RewardTermCfg(
    func=yaw_rate_tracking, weight=4.0, params={"std": 0.15}
)
Terminal
python3 scripts/workshop.py train-terrain \
  --model models/walk.onnx \
  --resume outputs/terrain-training-01/model_999.pt \
  --refine-heading --num-envs 256 --iterations 1500 --seed 43002 \
  --output outputs/terrain-heading-01

python3 scripts/workshop.py train-terrain \
  --model models/walk.onnx \
  --resume outputs/terrain-heading-01/model_2498.pt \
  --refine-heading --yaw-precision \
  --num-envs 256 --iterations 2000 --seed 43003 \
  --output outputs/terrain-direction-01

Compare both actors with the same benchmark

Ascent10°: official actor above, terrain actor below. The original stalls while the adapted actor reaches the target. Matched starts and synchronized simulation time; this illustration does not replace the full benchmark.
Downhill at 10°: the official policy is above and the terrain policy below. The official robot loses balance but remains supported by the continuous ground. The terrain policy stays upright but fails the arrival condition because of lateral drift. Both use the same starting setup and play in synchronized simulation time; the wide view retains motion beyond the ramp. This separate recording seed is excluded from the table below. Staying upright is not the same as completing the task.
Terminal
python3 scripts/workshop.py terrain \
  --model models/terrain-walk.onnx \
  --slope 5 --num-envs 5 --seconds 20 --seed 49001 --headless \
  --output outputs/terrain-evaluation-up-05

Use the fixed0.79 MB terrain actor by default. Explicitly select outputs/terrain-direction-01/exported/policy.onnx to test your own export. Chapters1 and7 retain the original walking actor.

Use matched evaluation seeds and angles for both actors. Report flat, uphill and downhill success, falls, tracking error and training time. Stalling is not success, and slope improvements do not justify losing flat-ground ability.

SlopeOfficial passTerrain passTerrain fallsTerrain speed RMSE
0°5/55/50/50.094 m/s
+5°0/55/50/50.105 m/s
-5°0/55/50/50.089 m/s
+10°0/55/50/50.122 m/s
-10°0/52/50/50.090 m/s
+15°0/50/55/50.210 m/s
-15°0/50/55/50.179 m/s

Each angle uses five environments with fixed seed 49001, excluded from model training and selection, and official full-body contacts for both actors. Reusing this seed after correcting the support surface does not create additional independent samples. Success criteria are unchanged. The table averages each environment’s speed RMSE over moving-command samples before its first fall. The lateral gate applies at arrival, not continuously. This small sample does not establish general slope reliability. See the per-environment results.

The terrain actor passes all five trials on flat ground, ±5° and +10°. At −10° it stays upright in all five but misses the arrival region three times; it falls in every ±15° trial. Stability, direction and completion are distinct metrics. Five trials per angle cannot isolate a parameter effect. Inspect failures before changing training; the shared interface does not qualify this actor for football approach and kick handoff.

Check your understanding

Why evaluate on terrain geometry different from training?

Show explanation

To test transfer to new geometry and initial states. Both actors share evaluation conditions; tuning on those results makes them development data.

Try the same ramp on passive wheels

Wheels change support and resistance. Climbing requires propulsion through leg motion, while releasing input downhill may still permit acceleration. We retain the same ramp, continuous support surface, 20-second limit, arrival target and no-fall requirement, and report roller results separately. Roller push and walking velocity commands have different meanings, so their velocity-tracking errors are not compared.

The first 7 seconds of a +5° trial show the robot remaining on its wheels but drifting out of the test corridor. The complete 20-second record has no fall but fails the arrival criterion. This recording uses a separate seed, 55012, excluded from the table. The clip illustrates drift, not successful climbing.

The command below runs five roller environments on a +5° ramp. Inspect passed and fell. For recording, select one environment and add --record; use a new output directory each time.

Terminal · Bash
python3 scripts/workshop.py terrain \
  --locomotion rollers \
  --model models/official-browser/rollers.onnx \
  --slope 5 --seconds 20 --num-envs 5 \
  --seed 55011 \
  --output outputs/rollers-slope-01

Then try −5°, +10° and −10°. Record arrival and whole-episode survival separately: crossing the finish downhill and falling later is still a failure. This experiment does not substitute a slope-trained roller policy; failure is a valid transfer result.

SlopeComplete passNo fall throughout
0°5/55/5
5°0/55/5
-5°0/50/5
10°0/53/5
-10°0/50/5

The fixed official roller actor passed 5/5 on level ground. All +5° trials stayed upright but drifted far sideways and missed the required arrival region. It cannot directly replace the slope walking policy. Seed 55011 was reused while correcting tyre contacts: this is a fixed-configuration check, not an additional independent generalization set, and its seed differs from the walking table. See the roller slope results.

What else can this platform teach?

You changed the world with Isaac Sim, adapted a policy in Isaac Lab and tested its scope. Next, explore community jumping and balancing tasks.

References