#!/usr/bin/env python3 """Measure planner HEAD and code-equivalent assembler/world with the L121/L127 runners.""" from __future__ import annotations import importlib.util import json import os from pathlib import Path import statistics import sys ROOT = Path(__file__).resolve().parents[4] RESEARCH = ROOT / "Standalone/Research/WorldAssemblerM3" EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd") HOSTS = { "assembler-world": ROOT / "Saved/Linux/l127/ue-baseline-project", "planner-head": Path("/home/jahni/VFHost"), } CAMERAS = { 0: (8.0, 16.0, -372.2626953125), 1: (25.8369140625, 3.19921875, -737.0), 2: (19.166015625, -14.2001953125, -753.3740234375), 3: (-20.0625, -5.068359375, -900.353515625), } def load(name: str, path: Path): spec = importlib.util.spec_from_file_location(name, path) if spec is None or spec.loader is None: raise RuntimeError(f"cannot load {path}") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def configure(arrival, movement, label: str): host = HOSTS[label].resolve() out = ROOT / "Saved/Linux/l152/ue" / label plugin_saved = host / "Plugins/VoxelForge/Saved/Linux/l152" out.mkdir(parents=True, exist_ok=True) plugin_saved.mkdir(parents=True, exist_ok=True) os.environ["XDG_CONFIG_HOME"] = str(out / "config") arrival.HOST = host arrival.EDITOR = EDITOR arrival.OUT = out / "start" arrival.HOST_SAVED = out / "host" arrival.HOST_PLUGIN_SAVED = plugin_saved arrival.RUN_TIMEOUT_S = 1740 movement.HOST = host movement.EDITOR = EDITOR movement.OUT = out / "movement" movement.HOST_SAVED = out / "host" movement.HOST_OUT = plugin_saved / "movement" movement.RUN_TIMEOUT_S = 1740 return host, out def start_matrix(arrival, movement, label: str) -> dict: host, out = configure(arrival, movement, label) spawns = { 0: (8.0, 16.0, -372.2626953125), 1: (25.8369140625, 3.19921875, -737.0), 2: (19.166015625, -14.2001953125, -753.3740234375), 3: (-20.0625, -5.068359375, -900.353515625), } starts = [] for index, position in spawns.items(): stratum = "FI" if index == 1 else "SW" arrival.SPAWNS_M[stratum] = position row = arrival.run(stratum, 1, f"start-s{index}", 8, 1, 0, 1, 1, 0, 1, False, False, 1) row["strate_index"] = index row["spawn_m"] = list(position) if row.get("lod02_full_settled_t_s") is None: raise RuntimeError(f"LOD0-2 did not settle for S{index}: {row}") starts.append(row) print("[L152UEStart] " + json.dumps(row, sort_keys=True), flush=True) (out / "start-summary.json").write_text( json.dumps(starts, indent=2, sort_keys=True) + "\n", encoding="utf-8") row = moving_run(movement, label, 1) (out / "movement-first.json").write_text( json.dumps(row, indent=2, sort_keys=True) + "\n", encoding="utf-8") print("[L152UEMovement] " + json.dumps(row, sort_keys=True), flush=True) return {"host": str(host), "starts": starts, "first_movement": row} def moving_run(movement, label: str, repetition: int) -> dict: condition = "c743f2a" if label == "assembler-world" else "planner-head" row = movement.run_unreal(condition, repetition) events = movement.parse_events(ROOT / row["trace"]) 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: raise RuntimeError(f"expected eight effective tile slots: {row}") return row def main() -> int: out = ROOT / "Saved/Linux/l152/ue" out.mkdir(parents=True, exist_ok=True) arrival = load("l150_arrival", RESEARCH / "Linux121/RunL121Arrival.py") movement = load("l150_movement", RESEARCH / "Linux121/RunL121Movement.py") summaries = {} all_runs = {label: [] for label in HOSTS} for label in HOSTS: summaries[label] = start_matrix(arrival, movement, label) all_runs[label].append(summaries[label]["first_movement"]) for repetition in range(2, 6): order = ("planner-head", "assembler-world") if repetition % 2 == 0 \ else ("assembler-world", "planner-head") for label in order: configure(arrival, movement, label) row = moving_run(movement, label, repetition) all_runs[label].append(row) (out / "movement-runs-partial.json").write_text( json.dumps(all_runs, indent=2, sort_keys=True) + "\n", encoding="utf-8") print("[L152UEMovementRepeat] " + json.dumps({ key: row.get(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) keys = ("near_ready_moving_mean_pct", "far_holes_moving_mean_pct", "game_thread_tick_p99_ms", "game_thread_tick_max_ms") medians = { label: {key: statistics.median(row[key] for row in rows) for key in keys} for label, rows in all_runs.items() } delta = { key: round(100.0 * (medians["planner-head"][key] / medians["assembler-world"][key] - 1.0), 2) for key in keys } result = { "baseline_commit": "c743f2a (code-equivalent to assembler/world eb47d72)", "protocol": ( "L121 arrival and L127 movement; starts at four owner strata; five interleaved " "30 m/s runs per build; 12 s move plus 5 s dwell; eight effective tile slots"), "starts": summaries, "movement_runs": all_runs, "movement_medians": medians, "planner_head_vs_assembler_world_delta_pct": delta, } (out / "summary.json").write_text( json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8") print("[L152UESummary] " + json.dumps({ "movement_medians": medians, "planner_head_vs_assembler_world_delta_pct": delta, "starts_s": { label: [round(row["lod02_full_settled_t_s"], 3) for row in summary["starts"]] for label, summary in summaries.items() }, }, sort_keys=True), flush=True) return 0 if __name__ == "__main__": try: raise SystemExit(main()) except Exception as error: print(f"[L152UEFailure] {error}", file=sys.stderr, flush=True) raise SystemExit(1)