test: make linear pathing suite manifest-driven

This commit is contained in:
BruceChen 2026-04-14 11:12:16 +00:00
parent e4fc557aac
commit 84a452d5c9
3 changed files with 204 additions and 88 deletions

View file

@ -0,0 +1,123 @@
#!/usr/bin/env bash
manifest_cases_for_query() {
local manifest_path="$1"
local family_csv="$2"
python3 - "$manifest_path" "$family_csv" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as handle:
manifest = json.load(handle)
families = {item for item in sys.argv[2].split(",") if item}
for row in manifest:
if row["family"] in families and row["movement_mode"] == "sprint" and row["momentum_ticks"] == 12:
print(row["case_id"])
PY
}
manifest_case_json() {
local manifest_path="$1"
local case_id="$2"
python3 - "$manifest_path" "$case_id" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as handle:
manifest = json.load(handle)
case_id = sys.argv[2]
row = next(row for row in manifest if row["case_id"] == case_id)
print(json.dumps(row))
PY
}
record_live_result() {
local results_path="$1"
local case_json="$2"
local live_result="$3"
local log_path="$4"
python3 - "$results_path" "$case_json" "$live_result" "$log_path" <<'PY'
import json
import sys
row = json.loads(sys.argv[2])
record = {
"case_id": row["case_id"],
"bucket_id": row["bucket_id"],
"world_recipe_id": row["world_recipe_id"],
"expected_result": row["expected_result"],
"live_result": sys.argv[3],
"log_path": sys.argv[4],
}
with open(sys.argv[1], "a", encoding="utf-8") as handle:
handle.write(json.dumps(record) + "\n")
PY
}
run_test() {
local name="$1"
local start_x="$2" start_y="$3" start_z="$4"
local dest_x="$5" dest_y="$6" dest_z="$7"
TEST_NUM=$((TEST_NUM + 1))
echo ""
echo "=== TEST $TEST_NUM: $name ==="
echo " Start: ($start_x, $start_y, $start_z) -> Dest: ($dest_x, $dest_y, $dest_z)"
mcc-cmd "respawn" 2>/dev/null || true
sleep 0.5
mc-rcon "gamemode creative CursorBot" >/dev/null 2>&1
sleep 0.3
mc-rcon "tp CursorBot ${start_x}.5 ${start_y} ${start_z}.5" >/dev/null 2>&1
sleep 2
mc-rcon "gamemode survival CursorBot" >/dev/null 2>&1
sleep 1
: > "$LOG"
sleep 0.5
mcc-cmd "pathfind $dest_x $dest_y $dest_z"
sleep 8
local a_star_result
a_star_result=$(grep -a '\[A\*\]' "$LOG" | head -3 | sed 's/\x1b\[[0-9;]*m//g')
local path_exec
path_exec=$(grep -a '\[PathExec\]' "$LOG" | sed 's/\x1b\[[0-9;]*m//g')
local path_mgr
path_mgr=$(grep -a '\[PathMgr\]' "$LOG" | sed 's/\x1b\[[0-9;]*m//g')
local nav_segs
nav_segs=$(grep -a '\[Navigate\].*seg' "$LOG" | sed 's/\x1b\[[0-9;]*m//g')
local physics_line
physics_line=$(grep -a '\[Physics\]' "$LOG" | tail -1 | sed 's/\x1b\[[0-9;]*m//g')
local result="invalid_live_case"
if echo "$path_mgr" | grep -q "complete"; then
result="pass"
elif echo "$a_star_result" | grep -q "Failed"; then
result="reject"
elif echo "$path_mgr" | grep -q "Replan failed\|Giving up"; then
result="fail"
elif echo "$path_exec" | grep -q "FAILED"; then
result="fail"
fi
LAST_RESULT="$result"
echo " A*: $a_star_result"
echo " Segments: $nav_segs"
echo " Exec: $(echo "$path_exec" | tail -3)"
echo " Manager: $(echo "$path_mgr" | tail -2)"
echo " Physics: $physics_line"
echo " RESULT: $LAST_RESULT"
RESULTS="${RESULTS}TEST $TEST_NUM ($name): $LAST_RESULT\n"
}

View file

@ -1,107 +1,78 @@
#!/usr/bin/env bash
# Automated parkour jump test for MCC pathfinding
# Usage: source tools/mcc-env.sh && bash tools/test-parkour.sh
#
# Prerequisites:
# - MCC connected with FileInput mode
# - CursorBot is OP
# - Server at 1.21.11-Vanilla
set -euo pipefail
source "$(dirname "$0")/mcc-env.sh"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
source "$REPO_ROOT/tools/mcc-env.sh"
source "$REPO_ROOT/tools/pathing_live_common.sh"
MANIFEST="$REPO_ROOT/tools/pathing_data/canonical-live-cases.json"
RESULTS_FILE="${RESULTS_FILE:-/tmp/mcc-debug/pathing-live-results.jsonl}"
LOG="/tmp/mcc-debug/mcc-debug.log"
RESULTS=""
TEST_NUM=0
LAST_RESULT="invalid_live_case"
run_test() {
local name="$1"
local start_x="$2" start_y="$3" start_z="$4"
local dest_x="$5" dest_y="$6" dest_z="$7"
TEST_NUM=$((TEST_NUM + 1))
echo ""
echo "=== TEST $TEST_NUM: $name ==="
echo " Start: ($start_x, $start_y, $start_z) -> Dest: ($dest_x, $dest_y, $dest_z)"
# Respawn if dead, set creative, tp, then survival
mcc-cmd "respawn" 2>/dev/null
sleep 0.5
mc-rcon "gamemode creative CursorBot" >/dev/null 2>&1
sleep 0.3
mc-rcon "tp CursorBot ${start_x}.5 ${start_y} ${start_z}.5" >/dev/null 2>&1
sleep 2
mc-rcon "gamemode survival CursorBot" >/dev/null 2>&1
sleep 1
# Clear log
: > "$LOG"
sleep 0.5
# Execute pathfind
mcc-cmd "pathfind $dest_x $dest_y $dest_z"
sleep 8
# Analyze result
local a_star_result
a_star_result=$(grep -a '\[A\*\]' "$LOG" | head -3 | sed 's/\x1b\[[0-9;]*m//g')
local path_exec
path_exec=$(grep -a '\[PathExec\]' "$LOG" | sed 's/\x1b\[[0-9;]*m//g')
local path_mgr
path_mgr=$(grep -a '\[PathMgr\]' "$LOG" | sed 's/\x1b\[[0-9;]*m//g')
local nav_segs
nav_segs=$(grep -a '\[Navigate\].*seg' "$LOG" | sed 's/\x1b\[[0-9;]*m//g')
# Get final position
local physics_line
physics_line=$(grep -a '\[Physics\]' "$LOG" | tail -1 | sed 's/\x1b\[[0-9;]*m//g')
# Check success/failure
local result="UNKNOWN"
if echo "$path_mgr" | grep -q "complete"; then
result="PASS"
elif echo "$path_mgr" | grep -q "Replan failed\|Giving up"; then
result="FAIL"
elif echo "$path_exec" | grep -q "FAILED"; then
result="FAIL"
elif echo "$a_star_result" | grep -q "Failed"; then
result="NO_PATH"
fi
echo " A*: $a_star_result"
echo " Segments: $nav_segs"
echo " Exec: $(echo "$path_exec" | tail -3)"
echo " Manager: $(echo "$path_mgr" | tail -2)"
echo " Physics: $physics_line"
echo " RESULT: $result"
RESULTS="${RESULTS}TEST $TEST_NUM ($name): $result\n"
if [[ "${1:-}" == "--list-cases" ]]; then
manifest_cases_for_query "$MANIFEST" "linear"
exit 0
fi
mkdir -p "$(dirname "$RESULTS_FILE")"
: > "$RESULTS_FILE"
run_manifest_case() {
local case_id="$1"
local case_json
case_json="$(manifest_case_json "$MANIFEST" "$case_id")"
read -r world_recipe start_x start_y start_z goal_x goal_y goal_z < <(
python3 - "$case_json" <<'PY'
import json
import sys
row = json.loads(sys.argv[1])
print(
row["world_recipe_id"],
row["start"]["x"],
row["start"]["y"],
row["start"]["z"],
row["goal"]["x"],
row["goal"]["y"],
row["goal"]["z"],
)
PY
)
local landing_block_y=$(( ${goal_y%.*} - 1 ))
case "$world_recipe" in
linear-flat|linear-ascend|linear-descend)
mc-rcon "fill 95 80 95 115 90 105 air" >/dev/null
mc-rcon "fill 95 79 95 115 79 105 air" >/dev/null
mc-rcon "setblock 100 79 100 stone" >/dev/null
mc-rcon "setblock ${goal_x%.*} ${landing_block_y} ${goal_z%.*} stone" >/dev/null
;;
*)
echo "Unsupported world recipe for test-parkour.sh: $world_recipe" >&2
return 1
;;
esac
run_test "$case_id" "${start_x%.*}" "${start_y%.*}" "${start_z%.*}" "${goal_x%.*}" "${goal_y%.*}" "${goal_z%.*}"
record_live_result "$RESULTS_FILE" "$case_json" "$LAST_RESULT" "$LOG"
}
echo "========================================"
echo " MCC Parkour Jump Test Suite"
echo "========================================"
# Flat gap tests (same Y level)
run_test "Gap 1 flat" 100 100 100 102 100 100
run_test "Gap 2 flat" 100 100 102 103 100 102
run_test "Gap 3 flat" 100 100 104 104 100 104
run_test "Gap 4 flat" 100 100 106 105 100 106
# Ascend tests (+1Y)
run_test "Gap 1 up +1" 100 100 108 102 101 108
run_test "Gap 2 up +1" 100 100 110 103 101 110
# Descend tests (-1Y)
run_test "Gap 1 down -1" 100 100 112 102 99 112
run_test "Gap 2 down -1" 100 100 114 103 99 114
# Descend tests (-2Y)
run_test "Gap 1 down -2" 100 100 94 102 98 94
run_test "Gap 2 down -2" 100 100 92 103 98 92
while IFS= read -r case_id; do
run_manifest_case "$case_id"
done < <(manifest_cases_for_query "$MANIFEST" "linear")
echo ""
echo "========================================"

View file

@ -0,0 +1,22 @@
import subprocess
import unittest
class PathingLiveScriptTests(unittest.TestCase):
def test_test_parkour_lists_linear_canonical_cases(self) -> None:
result = subprocess.run(
["bash", "tools/test-parkour.sh", "--list-cases"],
check=False,
capture_output=True,
text=True,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("linear-flat-sprint-mm12-gap5-dy0p0", result.stdout)
self.assertIn("linear-ascend-sprint-mm12-gap2-dy1p0", result.stdout)
self.assertNotIn("linear-flat-walk-mm12-gap5-dy0p0", result.stdout)
self.assertNotIn("linear-flat-sprint-mm0-gap3-dy0p0", result.stdout)
if __name__ == "__main__":
unittest.main()