L2 · PAI-110

The PID Loop

Write a proportional heading controller in Python control(obs) that eases the rover onto a goal pad within a tight 0.12 m tolerance, with no overshoot or weave.

01
Challenge

Try this first — before any explanation.

Steer the rover to the green pad on real MuJoCo physics. The starter is the block's reflex: 'is the goal left or right? turn that way, full tilt.' That is bang-bang — it always commands a hard turn, so the nose slews past the goal heading, snaps back, and the rover weaves down the field. On a wide pad it might luck in; on this tight 0.12 m pad it skates right past. The weave is the whole motivation for proportional control.

The Bench

Write control(obs) to ease the rover onto the green pad — real MuJoCo physics, tight 0.12 m tolerance. The starter is bang-bang and weaves; make it proportional.

0:\n turn = 1.0 # goal is LEFT -> hard left (bang-bang)\n else:\n turn = -1.0 # goal is RIGHT -> hard right (bang-bang)\n return (forward - turn, forward + turn)\n","task":{"goal":[1.2,1.2],"time":14,"tol":0.12},"title":"The PID Loop"}">
MUJOCO · REAL PHYSICS · IN-BROWSER

The PID Loop

Write control(obs) to ease the rover onto the green pad — real MuJoCo physics, tight 0.12 m tolerance. The starter is bang-bang and weaves; make it proportional.

02
Model

The idea, built visually.

Your control(obs) only ever asks which way — left or right — and then turns as hard as it can. A turning body can't stop on a dime, so it sails past the heading it wanted, then slams back the other way, forever.

The fix is to also ask how much. Make the turn proportional to the error: turn = Kp * goal_bearing. Far off heading, big bearing, hard turn; nearly aligned, tiny bearing, gentle turn that glides in instead of snapping. That single multiply is the P in PID — react in proportion to how wrong you are, not just to the sign of it.

One more ease: as goal_dist shrinks, bleed off forward speed, so you arrive slow enough to actually settle inside the small pad instead of shooting through it.

▣ Stage animation: The CHALLENGE's weaving trail labeled 'bang-bang: always full turn'; the fixed +/- turn command morphs into turn = Kp * goal_bearing with an arrow that shrinks as the bearing closes; the trail straightens and the wheel mix (forward - turn, forward + turn) is annotated; forward speed fades as a goal_dist bar empties, and the rover kisses the 0.12 m pad and stops.

03
Guided practice

Build it up, step by step.

Step A (read it): the starter computes only the SIGN of obs['goal_bearing'] and turns full tilt — that is bang-bang, the source of the weave. Step B (make it proportional): replace the hard turn with turn = Kp * obs['goal_bearing'] (a known-good neighborhood for this pad is Kp around 1.2), then mix into wheels as (forward - turn, forward + turn). Step C (ease the throttle): set forward proportional to obs['goal_dist'], capped (e.g. forward = min(0.7, 0.9 * obs['goal_dist'])), so you slow into the tight pad instead of overshooting. Remember + goal_bearing means the goal is to your LEFT, so a positive turn must steer left.

04
Feedback

How the Bench grades your run.

PASS WHEN On real MuJoCo physics, control(obs) eases the rover onto the goal pad and reaches within 0.12 m: the turn is proportional to goal_bearing (no weave) and forward speed bleeds off as goal_dist shrinks (no overshoot).

  • FAIL: closest approach never neared the pad — your steering sign is backwards. Positive goal_bearing means the goal is to your LEFT, so a positive turn must steer left; mix it as (forward - turn, forward + turn).
  • FAIL: you weave across the pad and skate past — that is bang-bang. Replace the fixed +/-1 turn with turn = Kp * obs['goal_bearing'] (try Kp around 1.2) so the turn eases as the bearing closes.
  • FAIL: closest approach is over 0.12 m — you arrive too fast and overshoot the tight pad. Ease the throttle: make forward proportional to obs['goal_dist'] (e.g. min(0.7, 0.9 * goal_dist)) so you slow into the pad.
05
Retrieve & space

Bring back what you've already mastered.

  • From 2.1: your read_state feeds heading and goal_dist into this loop — if its estimate jitters, which term of a proportional turn gets noisy first? (The turn itself, since turn scales directly with the bearing error.)
  • From 1.2: mark the line in the bang-bang starter that is purely reactive (sign-only) and rewrite it proportionally as turn = Kp * goal_bearing.
  • Given a run that weaves across the pad with no steady offset, name the single change and direction. (It is bang-bang / Kp effectively infinite — make the turn proportional, lower the gain.)
06
Mastery gate

What you must demonstrate to advance.

Drive the rover onto the goal pad within 0.12 m on real MuJoCo physics using a proportional control(obs): turn proportional to goal_bearing (no weave) and forward eased by goal_dist (no overshoot) — the P core of the DECIDE/ACT loop (L2).

07
Project

How this feeds your build.

This proportional control(obs) is the DECIDE/ACT core of the capstone autonomy stack; the 2.3 FSM calls it per mode as a black box, Module 4 re-implements the same law in C on a timer interrupt, and Module 5 profiles it toward the metal.