Mecanum Webots via Supervisor kinematic injection

Replace the failing ODE-rolled mecanum chassis dynamics with a
Supervisor.setVelocity call that uses the gym mecanum forward
kinematics formula directly. Wheel motors still spin (visual);
chassis motion comes from the gym model so training and deployment
match by construction.

Results (seed=42, n=10 sheep): BC + RL mecanum pen 10/10 in both
field and field_round. n=5 mecanum cells still 0/5 due to tracker
phantoms anchored to wall corners under the 360° LiDAR — documented
in docs/status.md as the remaining gap.

Cleanup: drop deploy-time hacks (HERDING_HEADING_*, HERDING_OMEGA_CLAMP,
HERDING_TRACKER_*) that were workarounds for the old ODE chaos;
revert the proto inertiaMatrix, roller dampingConstant, and reduced
motor torque since they no longer carry load; refresh comments
around the mecanum config presets.
This commit is contained in:
Johnny Fernandes
2026-05-18 22:46:37 +00:00
parent 1df84ae4b5
commit 27c0f65722
25 changed files with 2635 additions and 76 deletions
+30 -19
View File
@@ -90,24 +90,36 @@ fi
cp "$SRC" "$DST"
# LiDAR FOV variant: HERDING_LIDAR=140 (default) or 360 (ablation).
# 360° is only supported for differential drive; the mecanum proto
# always uses the 140° sensor matching ShepherdDog.proto.
LIDAR_VARIANT="${HERDING_LIDAR:-140}"
# LiDAR FOV variant. For mecanum the default is 360° because the
# physical-roller proto's passive yaw drift makes the 140° front
# cone unreliable (heading errors push detections out of the
# tracker's world-frame matching gates). Diff defaults to 140°
# matching the canonical ShepherdDog.proto.
if [[ -z "${HERDING_LIDAR:-}" ]]; then
if [[ "$DRIVE" == "mecanum" ]]; then
LIDAR_VARIANT="360"
else
LIDAR_VARIANT="140"
fi
else
LIDAR_VARIANT="$HERDING_LIDAR"
fi
if [[ "$LIDAR_VARIANT" != "140" && "$LIDAR_VARIANT" != "360" ]]; then
echo "HERDING_LIDAR must be 140 or 360, got '$LIDAR_VARIANT'" >&2; exit 1
fi
if [[ "$LIDAR_VARIANT" == "360" && "$DRIVE" == "mecanum" ]]; then
echo "[run_webots] HERDING_LIDAR=360 not available for mecanum drive — falling back to 140." >&2
LIDAR_VARIANT="140"
fi
export HERDING_LIDAR="$LIDAR_VARIANT"
# Swap robot proto based on drive mode + LiDAR variant.
# Base worlds reference ShepherdDog (diff-drive 140°). For mecanum we
# swap in ShepherdDogMecanum; for the 360° ablation we swap in
# ShepherdDog360.
if [[ "$DRIVE" == "mecanum" ]]; then
# Base worlds reference ShepherdDog (diff-drive 140°). The four
# combinations the launcher supports:
# diff + 140° → ShepherdDog.proto (default)
# diff + 360° → ShepherdDog360.proto (FOV ablation for diff)
# mecanum+ 140° → ShepherdDogMecanum.proto
# mecanum+ 360° → ShepherdDogMecanum360.proto (the trained mecanum target)
if [[ "$DRIVE" == "mecanum" && "$LIDAR_VARIANT" == "360" ]]; then
sed -i 's|"../protos/ShepherdDog.proto"|"../protos/ShepherdDogMecanum360.proto"|' "$DST"
sed -i 's|^ShepherdDog {|ShepherdDogMecanum360 {|' "$DST"
elif [[ "$DRIVE" == "mecanum" ]]; then
sed -i 's|"../protos/ShepherdDog.proto"|"../protos/ShepherdDogMecanum.proto"|' "$DST"
sed -i 's|^ShepherdDog {|ShepherdDogMecanum {|' "$DST"
elif [[ "$LIDAR_VARIANT" == "360" ]]; then
@@ -115,13 +127,12 @@ elif [[ "$LIDAR_VARIANT" == "360" ]]; then
sed -i 's|^ShepherdDog {|ShepherdDog360 {|' "$DST"
fi
if [[ "$DRIVE" == "mecanum" ]]; then
# Inject mecanum roller contact properties. The proto's rollers are
# split into two contact materials so that we can keep the friction
# axes oriented along each roller's free-spin direction — but with
# physical roller hinges (no longer plain cylinder wheels) the
# ground contact is via the capsules and standard friction works.
# Slightly bumped coulombFriction keeps the rollers gripping during
# mecanum strafing.
# Wheel-ground friction. The chassis is driven kinematically by
# the Supervisor (see drive_mecanum in controllers/shepherd_dog),
# so these properties only affect wheel-spin visuals, not the
# robot's motion. coulombFriction 2.0 plus a small
# forceDependentSlip keeps the rollers from locking up against
# the ground.
python3 -c "
with open('$DST', 'r') as f:
txt = f.read()