# Status — 2026-05-18 Current snapshot of what works in Webots, and what design choices got us here. ## Results matrix (Webots, seed=42) Differential drive — `bash tools/run_webots.sh N MODE differential WORLD`: | controller | field n=5 | field n=10 | field_round n=5 | field_round n=10 | |----------------|:---------:|:----------:|:---------------:|:----------------:| | BC | 5/5 | 10/10 | 5/5 | 10/10 | | RL | 5/5 | 10/10 | 5/5 | 10/10 | | Strömbom | 5/5 | 10/10 | 5/5 | 10/10 | | Sequential | 5/5 | 10/10 | 5/5 | 10/10 | Mecanum drive — `bash tools/run_webots.sh N MODE mecanum WORLD HERDING_LIDAR=360`: | controller | field n=5 | field n=10 | field_round n=5 | field_round n=10 | |------------|:---------:|:----------:|:---------------:|:----------------:| | BC | 0/5 | 10/10 | 0/5 | 10/10 | | RL | 0/5 | 10/10 | 0/5 | 10/10 | Extra-merit: - **360° LiDAR ablation** — `HERDING_LIDAR=360` works in all four diff cells. - **Dual-dog axis-split** — `HERDING_NDOGS=2 HERDING_AXIS_LEAK=0.3` pens 5/5 on diff. ## Architecture decisions and why ### Differential drive — full ODE simulation Standard Webots physics with two wheel motors and a caster. No special handling needed; the chassis is dynamically stable, and the trained policies transfer directly to Webots. ### Mecanum drive — kinematic Supervisor injection The mecanum proto uses physical 8-roller wheels for visual fidelity, but the chassis is moved by `Supervisor.setVelocity()` using the gym mecanum forward-kinematics formula (see `controllers/shepherd_dog/shepherd_dog.py::drive_mecanum`). We explored two other paths before settling here: 1. **Plain cylinder wheels + anisotropic ContactProperties.** Tried `frictionRotation ±0.7854` on the wheel contact frame. Strafe motion came out the wrong direction and diagonals zeroed out. Discarded. 2. **Full ODE simulation on 32 physical roller hinges.** The free-spinning rollers coupled chaotically through the body, producing ±150° yaw drift over 200 control steps. Even with `inertiaMatrix` overrides, `dampingConstant` on every roller, and a 6× cut to motor torque, dynamic policy commands kept producing tumbling. Discarded. 3. **Kinematic Supervisor injection (current).** ODE physics on the wheels is kept for visuals only; the chassis velocity is set directly each step from the gym forward-kinematics formula. Gym training and Webots deployment produce identical body motion. Yaw drift is zero by construction. This is not a hack — it matches how most academic mecanum sims work (e.g., Gazebo's mecanum plugins use kinematic models by default; ODE's contact solver does not handle the rolling-without-slipping constraint cleanly for 32 free hinges). ### Why n=5 mecanum fails (and n=10 passes) The 360° LiDAR scans the full perimeter every step. Wall corners, gate posts, and pen rails occasionally produce sheep-shaped blobs that pass the `wall_reject` and `static_reject` filters. The tracker promotes a candidate to "active" after `consensus_k=3` consistent hits within 20 steps — phantoms anchored to fixed world features satisfy this trivially. With n=10 real sheep, the tracker's active slots fill with real sheep and phantoms can't compete. With n=5 there are ~5 free slots that wall phantoms occupy; the policy then chases ghosts. Tightening the consensus filter (`consensus_k=5`) and `wall_reject=0.9` were tried; both kept ~70% of frames at 10 active tracks. The proper fix is **parallax-aware tracking** — record each track's world position across multiple dog vantage points; real sheep move, static phantoms don't. Out of scope for the 2026-06-04 deadline. ## File map (what changed in this push) ``` herding/config.py mecanum presets keep matched strafe scaling (strafe_eff=0.26, bleed=-0.40) for kinematic injection controllers/shepherd_dog/shepherd_dog.py Supervisor() + drive_mecanum kinematic injection via _self_node.setVelocity protos/ShepherdDogMecanum.proto supervisor TRUE; physics tuning protos/ShepherdDogMecanum360.proto reverted (ODE no longer load-bearing) tools/gen_mecanum_wheels.py wheels regen-script (clean) tools/run_webots.sh contact-properties comment cleaned training/{bc/collect,rl/train}.py comment cleanup; preset selection unchanged ``` ## Options for the remaining cleanup 1. **Keep matched preset (0.26, -0.40)**. Policies trained against these values; controller applies them at deploy; no retrain. *Current state*. 2. **Switch preset to textbook (1.0, 0.0) and retrain mecanum BC+RL** (~6h). Cleaner story (textbook mecanum throughout); same kinematic-injection mechanism. Either is defensible. (1) ships faster; (2) is more "pure".