"""Versioned Worker-only collision preparation; never uploads generated assets.""" import hashlib import json import subprocess import time from pathlib import Path from local_state import write_json from k1link.simulation.ai_polygon.terrain_contract import terrain_matches def prepare_terrain(root: Path, episode: Path, world: dict, job=None, pulse=None) -> Path: if world.get("storage", {}).get("kind") == "worker": path = root / "assets/prepared-worlds" / world["sha256"] / "terrain.json" manifest = json.loads(path.read_text(encoding="utf-8-sig")) if ( manifest.get("generator") != "paired-source" or manifest.get("collider_sha256") != world.get("collider_sha256") or not terrain_matches(manifest, world) ): raise ValueError("Paired collision asset does not match the admitted world") return path # install_terrain hashes the actual collider before physics. generator = Path(__file__).parent / "navigation/Prepare-Terrain.ps1" generator_sha256 = hashlib.sha256(generator.read_bytes()).hexdigest() def matching(): for path in (root / "assets/terrain-v1").glob(world["sha256"] + "-*/terrain.json"): value = json.loads(path.read_text(encoding="utf-8-sig")) if terrain_matches(value, world, generator_sha256): return path return None found = matching() if found: return found world_path = episode / "terrain-world.json" write_json(world_path, world) with (episode / "terrain-preparation.log").open("wb") as log: process = subprocess.Popen( [ "powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", str(generator), "-Root", str(root), "-WorldFile", str(world_path), ], stdout=log, stderr=subprocess.STDOUT, ) if job is not None: job.assign(process) deadline = time.monotonic() + 180 try: while process.poll() is None: if time.monotonic() > deadline: raise TimeoutError("Terrain preparation timed out") if pulse: pulse() time.sleep(1) if process.returncode != 0: raise RuntimeError("Terrain preparation failed; inspect the Worker episode log") finally: if process.poll() is None: from worker import terminate_episode terminate_episode(process) found = matching() if found is None: raise RuntimeError("Terrain preparation did not produce a matching collider") return found