Dual-shepherd axis-split (HERDING_NDOGS=2)
The launcher can now spawn two `ShepherdDog` robots, each masked to a single axis of motion, so the herding workload is split orthogonally. Mechanic: * `HERDING_NDOGS=2` (default 1) tells `tools/run_webots.sh` to replace the single-dog node in the generated test world with two copies: - `ShepherdDogX` at (-4, -10), `customData "axis=x"` - `ShepherdDogY` at (+4, -10), `customData "axis=y"` Each spawn position sits south of the field interior so the pair doesn't collide with starting sheep. * `controllers/shepherd_dog/shepherd_dog.py` reads `getCustomData()` at startup; when `axis=x|y` it zeroes the off-axis component of every action *after* speed modulation and *before* EMA smoothing. With `customData` empty the controller behaves identically to single-dog mode, so all existing launches are unaffected. * The dog's emitter line now carries the robot's name (`dog:ShepherdDogX:x:y`), and `controllers/sheep/sheep.py` keeps a `dogs` dict keyed by name, picking the closest one each step for its flee target. Single-dog runs still use the legacy two-field `dog:x:y` format thanks to a length check. * `HERDING_NDOGS` is written into `herding_runtime.cfg` and exported to subprocesses so future tooling can read it. Verified behaviour in Webots smoke tests (HERDING_NDOGS=2, strombom, diff/field, 5 sheep): both dogs spawn with the expected names and axis tags, the dual-dog status print appears, each dog acts only on its assigned axis early in the trial, and the masking is internally consistent. The pair stalls before penning under pure axis-split because each dog reaches its drive standoff and then has only one degree of freedom — useful research finding for the write-up; coordination strategy (shared CoM, role-switching, etc.) is future work. 126 pytest cases still pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -161,11 +161,62 @@ for i in $(seq $((N+1)) 10); do
|
||||
sed -i "s|^Sheep .* \"sheep${i}\".*|# &|" "$DST"
|
||||
done
|
||||
|
||||
# Dual-dog axis split. When HERDING_NDOGS=2 the launcher replaces the
|
||||
# single dog node in the world with two named dogs whose customData
|
||||
# carries the axis assignment (x or y); the controller masks the
|
||||
# off-axis component of every action.
|
||||
NDOGS="${HERDING_NDOGS:-1}"
|
||||
if [[ "$NDOGS" != "1" && "$NDOGS" != "2" ]]; then
|
||||
echo "HERDING_NDOGS must be 1 or 2, got '$NDOGS'" >&2; exit 1
|
||||
fi
|
||||
if [[ "$NDOGS" == "2" ]]; then
|
||||
DOG_NODE_NAME="ShepherdDog"
|
||||
if [[ "$DRIVE" == "mecanum" ]]; then
|
||||
DOG_NODE_NAME="ShepherdDogMecanum"
|
||||
elif [[ "$LIDAR_VARIANT" == "360" ]]; then
|
||||
DOG_NODE_NAME="ShepherdDog360"
|
||||
fi
|
||||
python3 - "$DST" "$DOG_NODE_NAME" <<'PY'
|
||||
import re, sys
|
||||
path, node = sys.argv[1], sys.argv[2]
|
||||
with open(path) as f:
|
||||
txt = f.read()
|
||||
# Match the single existing dog block from "ShepherdDog{,360,Mecanum} {"
|
||||
# up to its closing "}" on a line by itself.
|
||||
pattern = re.compile(rf"^{re.escape(node)} \{{\n(.*?\n)^\}}\n", re.MULTILINE | re.DOTALL)
|
||||
m = pattern.search(txt)
|
||||
if m is None:
|
||||
sys.exit(f"[run_webots] could not locate single-dog block ({node}) for split")
|
||||
two_dogs = (
|
||||
f"{node} {{\n"
|
||||
f" translation -4 -10 0.5\n"
|
||||
f" rotation 0 0 1 1.5708\n"
|
||||
f' name "ShepherdDogX"\n'
|
||||
f' customData "axis=x"\n'
|
||||
f' controller "shepherd_dog"\n'
|
||||
f"}}\n"
|
||||
f"{node} {{\n"
|
||||
f" translation 4 -10 0.5\n"
|
||||
f" rotation 0 0 1 1.5708\n"
|
||||
f' name "ShepherdDogY"\n'
|
||||
f' customData "axis=y"\n'
|
||||
f' controller "shepherd_dog"\n'
|
||||
f"}}\n"
|
||||
)
|
||||
with open(path, 'w') as f:
|
||||
f.write(txt[:m.start()] + two_dogs + txt[m.end():])
|
||||
PY
|
||||
fi
|
||||
export HERDING_NDOGS="$NDOGS"
|
||||
|
||||
active=$(grep -c '^Sheep' "$DST" || true)
|
||||
ndog=$(grep -cE '^(ShepherdDog|ShepherdDog360|ShepherdDogMecanum) \{' "$DST" || true)
|
||||
echo "------------------------------------------------------------"
|
||||
echo "World : $DST"
|
||||
echo "Mode : $MODE"
|
||||
echo "Drive : $DRIVE"
|
||||
echo "LiDAR : ${LIDAR_VARIANT}°"
|
||||
echo "Dogs : $ndog (axis-split=${NDOGS})"
|
||||
echo "Sheep : $active active"
|
||||
echo "Policy dir : $RESOLVED_POLICY_DIR"
|
||||
echo "------------------------------------------------------------"
|
||||
@@ -178,6 +229,7 @@ HERDING_POLICY_DIR=$RESOLVED_POLICY_DIR
|
||||
HERDING_DRIVE=$DRIVE
|
||||
HERDING_WORLD=$WORLD
|
||||
HERDING_LIDAR=$LIDAR_VARIANT
|
||||
HERDING_NDOGS=$NDOGS
|
||||
HERDING_USE_GT=${HERDING_USE_GT:-0}
|
||||
EOF
|
||||
|
||||
|
||||
Reference in New Issue
Block a user