#!/usr/bin/env python3 """Run five UE 30 m/s movement trials and compare with the L127 4041d4b baseline.""" from __future__ import annotations import importlib.util import json import os import re import statistics from pathlib import Path ROOT = Path(__file__).resolve().parents[4] RESEARCH = ROOT / "Standalone/Research/WorldAssemblerM3" OUT = ROOT / "Saved/Linux/l128/ue" HOST = Path("/home/jahni/VFHost") EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd") MOVEMENT_PATH = RESEARCH / "Linux121/RunL121Movement.py" BASELINE_PATH = ROOT / "Saved/Linux/l127/ue/movement-pairs-summary.json" def load_movement_module(): spec = importlib.util.spec_from_file_location("l128_movement", MOVEMENT_PATH) if spec is None or spec.loader is None: raise RuntimeError(f"cannot load UE movement runner: {MOVEMENT_PATH}") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def main() -> int: if not BASELINE_PATH.is_file(): raise SystemExit(f"missing 4041d4b movement baseline: {BASELINE_PATH}") OUT.mkdir(parents=True, exist_ok=True) module = load_movement_module() module.HOST = HOST module.EDITOR = EDITOR module.OUT = OUT / "movement" module.HOST_SAVED = HOST / "Saved/Linux/l128/host" module.HOST_OUT = HOST / "Plugins/VoxelForge/Saved/Linux/l128/movement" module.RUN_TIMEOUT_S = 1740 module.HOST_OUT.mkdir(parents=True, exist_ok=True) os.environ["XDG_CONFIG_HOME"] = str(OUT / "config") rows = [] for repetition in range(1, 6): row = module.run_unreal("L128", repetition) events = module.parse_events(ROOT / row["trace"]) row["supplemental_far_applies_total"] = sum( int(event.get("supplemental_far_applies", 0)) for event in events if event.get("type") == "apply_frame") row["supplemental_far_applies_moving"] = sum( int(event.get("supplemental_far_applies", 0)) for event in events if event.get("type") == "apply_frame" and float(event.get("speed_cm_s", 0.0)) >= 50.0) if row.get("effective_tile_slots") != 8 or row["supplemental_far_applies_moving"] <= 0: raise SystemExit(f"movement run missed the configured apply lane: {row}") rows.append(row) (OUT / "movement-runs-partial.json").write_text( json.dumps(rows, indent=2, sort_keys=True) + "\n", encoding="utf-8") print("[L128UEMovement] " + json.dumps({ key: row[key] for key in ( "condition", "repetition", "near_ready_moving_mean_pct", "far_holes_moving_mean_pct", "game_thread_tick_p99_ms", "game_thread_tick_max_ms", "supplemental_far_applies_moving", "wall_s", "trace")}, sort_keys=True), flush=True) baseline_doc = json.loads(BASELINE_PATH.read_text(encoding="utf-8")) baseline_rows = [row for row in baseline_doc["runs"] if row["condition"] == "merge"] if len(baseline_rows) != 5: raise SystemExit(f"expected five 4041d4b baseline runs, got {len(baseline_rows)}") metrics = ("near_ready_moving_mean_pct", "far_holes_moving_mean_pct", "game_thread_tick_p99_ms", "game_thread_tick_max_ms") baseline = {metric: statistics.median(row[metric] for row in baseline_rows) for metric in metrics} candidate = {metric: statistics.median(row[metric] for row in rows) for metric in metrics} build_log = (ROOT / "Saved/Linux/l128/build-clang.log").read_text( encoding="utf-8", errors="replace") stamp_match = re.search(r"^Source stamp: (\S+)$", build_log, re.M) if stamp_match is None: raise SystemExit("missing L128 Clang source stamp") summary = { "baseline": {"commit": "4041d4b", "runs": len(baseline_rows), "metrics": baseline}, "candidate": {"source_stamp": stamp_match.group(1), "runs": len(rows), "metrics": candidate}, "candidate_vs_4041d4b_delta": { metric: candidate[metric] - baseline[metric] for metric in metrics}, "bar_pass": candidate["near_ready_moving_mean_pct"] > baseline["near_ready_moving_mean_pct"] and all(candidate[metric] <= baseline[metric] for metric in ( "far_holes_moving_mean_pct", "game_thread_tick_p99_ms", "game_thread_tick_max_ms")), } (OUT / "movement-summary.json").write_text( json.dumps({"runs": rows, "summary": summary}, indent=2, sort_keys=True) + "\n", encoding="utf-8") print("[L128UEMovementSummary] " + json.dumps(summary, sort_keys=True), flush=True) if not summary["bar_pass"]: raise SystemExit("L128 UE movement medians missed the 4041d4b coverage/tick bar") return 0 if __name__ == "__main__": raise SystemExit(main())