diff --git a/tools/pathing_live_common.sh b/tools/pathing_live_common.sh new file mode 100644 index 00000000..5648dcb2 --- /dev/null +++ b/tools/pathing_live_common.sh @@ -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" +} diff --git a/tools/test-parkour.sh b/tools/test-parkour.sh index e94bb314..7527998d 100644 --- a/tools/test-parkour.sh +++ b/tools/test-parkour.sh @@ -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 "========================================" diff --git a/tools/tests/test_pathing_live_scripts.py b/tools/tests/test_pathing_live_scripts.py new file mode 100644 index 00000000..9538589b --- /dev/null +++ b/tools/tests/test_pathing_live_scripts.py @@ -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()