Enable consensus tracker by default + round-world Strömbom fix

Two changes that together raise diff/round gym success ~52%→88% (BC)
and ~68%→88% (RL) without retraining; diff/field stays at 100%.

* TrackerConfig.consensus_k default 1 → 3 (radius 0.5 m, max_age 15
  frames). The same candidate-promotion mechanism that closed the
  Webots LiDAR gap also filters gym tracker phantoms — they show up
  on the round field where sheep run further between detection
  cycles than GATE_M, so each new position spawns a fresh track
  while the stale one persists in memory. SheepTracker() called with
  no tracker_cfg keeps the legacy pass-through behaviour for
  backwards compatibility.
* Strömbom + universal teachers now detect when the natural
  "behind the flock" drive target leaves the curved boundary and
  fall back to pushing the flock radially inward toward the centre.
  Breaks the wall-circling pattern that previously trapped both the
  analytical baselines and the trained policies.

A/B numbers (n_sheep ∈ {1,2,3,5,10}, 5 seeds each, max_steps=15000):

  diff/field  bc:  baseline 100%  consensus 100%
  diff/field  rl:  baseline 100%  consensus 100%
  diff/round  bc:  baseline  52%  consensus  88%
  diff/round  rl:  baseline  68%  consensus  88%

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Johnny Fernandes
2026-05-16 21:09:25 +00:00
parent 03b2df5656
commit 1c197e0ff7
4 changed files with 62 additions and 10 deletions
+23
View File
@@ -29,6 +29,7 @@ For differential drive ``omega`` is always 0.0 and can be ignored.
import math
from herding.world.geometry import (
FIELD_ROUND_R, FIELD_SHAPE,
PEN_ENTRY, GATE_X, GATE_Y, in_pen,
)
@@ -171,6 +172,28 @@ def compute_action(dog_xy, dog_heading, sheep_positions,
mode = "drive"
face_target = pen_target
# On the round field the natural "behind the flock" point can fall
# outside the curved wall when the flock CoM is itself close to the
# wall. The dog tries to reach an unreachable target, ends up
# tangent to the wall, and the flock circles indefinitely.
# Fix: when the natural target leaves the field, fall back to
# pushing the flock radially inward toward the centre — break the
# wall-circle pattern, then resume normal pen-direction drive once
# the flock is back in the interior.
if FIELD_SHAPE == "field_round" and mode == "drive":
if math.hypot(tx, ty) > FIELD_ROUND_R - 1.0:
r_com = math.hypot(com_x, com_y)
if r_com > 1e-3:
ux2, uy2 = com_x / r_com, com_y / r_com
tx = com_x + DELTA_DRIVE * ux2
ty = com_y + DELTA_DRIVE * uy2
# Clamp to inside-field radius so the dog target is reachable.
r_t = math.hypot(tx, ty)
if r_t > FIELD_ROUND_R - 1.0:
scale = (FIELD_ROUND_R - 1.0) / r_t
tx *= scale
ty *= scale
ax, ay = _unit(tx - dog_xy[0], ty - dog_xy[1])
# ---- Omega (mecanum only) ----