Per-sheep pen-time metrics, seed support, make webots → menu

* `controllers/shepherd_dog/shepherd_dog.py`
  - Tracks the first step at which each sheep crosses the gate; on
    auto-finish (all sheep penned) prints a `[results]` summary
    block: mode/drive/world/lidar/dogs/seed line, total simulated
    time, per-sheep penning order with absolute step + seconds since
    sim start, and the gate spread between the first and last
    penning.
  - Reads `HERDING_SEED` (env / runtime cfg) and seeds the
    controller's RNG when set. Empty = time-based default = old
    non-deterministic behaviour.
* `controllers/sheep/sheep.py` reads `HERDING_SEED` the same way
  (loading `herding_runtime.cfg` itself so it works even when
  Webots strips env vars) and seeds Python's RNG XOR'd with the
  sheep's name hash, so a fixed seed gives a reproducible flock
  trajectory without all sheep starting from identical wander state.
* `tools/run_webots.sh` writes `HERDING_SEED` into the runtime cfg
  (empty when unset so existing scripts keep their stochastic
  behaviour).
* `tools/webots_menu.sh` gains a Seed prompt (random / fixed
  integer); the launch summary box shows the choice next to the
  perception row.
* `Makefile`
  - `make webots`  now fires the interactive picker (replacing the
    old positional invocation).
  - `make webots_quick MODE=… DRIVE=… WORLD=… N=…` is the old
    positional path, kept for batch / scripted use.

Smoke-tested: menu renders Mode → Drive → World → LiDAR → Dogs
→ Sheep → Perception → Seed → Headless prompts and shows the
selected Seed value in the launch summary. 126 pytest cases still
pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Johnny Fernandes
2026-05-17 10:33:34 +00:00
parent bdaff6a3e1
commit e86fee5ae8
5 changed files with 106 additions and 6 deletions
+31
View File
@@ -37,6 +37,37 @@ timestep = int(robot.getBasicTimeStep())
name = robot.getName()
self_node = robot.getSelf()
# Seed Python's RNG (shared with the dog controller) so a fixed
# HERDING_SEED produces reproducible runs. Each sheep mixes its name
# into the seed so the flock isn't all identical.
def _read_runtime_cfg():
cfg_path = os.path.join(_PROJECT_ROOT, "herding_runtime.cfg")
out = {}
if os.path.exists(cfg_path):
try:
with open(cfg_path) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, _, v = line.partition("=")
out[k.strip().upper()] = v.strip()
except OSError:
pass
return out
_rt = _read_runtime_cfg()
_seed_raw = (os.environ.get("HERDING_SEED")
or _rt.get("HERDING_SEED")
or "").strip()
if _seed_raw:
try:
# XOR with hash(name) so different sheep have different seeds
# but the flock as a whole is deterministic for a given seed.
random.seed(int(_seed_raw) ^ (hash(name) & 0x7FFFFFFF))
except ValueError:
pass
left_motor = robot.getDevice("left wheel motor")
right_motor = robot.getDevice("right wheel motor")
left_motor.setPosition(float("inf"))