#!/usr/bin/env python3
"""Paired live AI skating under a stationary wide opposing carry, both teams/ends.
Nine setup frames settle captured CPU/input state; all 140 observation frames
run native AI/physics without position resets. Receiver collisions remain natural.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse,json,os,statistics,subprocess
ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);ap.add_argument('--baseline',type=Path,required=True);ap.add_argument('--state',type=Path,required=True);a=ap.parse_args();here=Path(__file__).resolve().parent;out=a.output.resolve();out.mkdir(parents=True,exist_ok=True)

def run(profile,rom,name,spec):
 p=out/(profile+'-'+name);p.mkdir(exist_ok=True);(p/'spec.json').write_text(json.dumps(spec,indent=2)+'\n');cmd=p/'commands.txt';cmd.write_text(f'100 load {a.state.resolve()}\n148 snap\n188 snap\n249 exit\n')
 env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env.update(GPGX_DEV='513',GPGX_HOOK=str(here/'ai_defence_gameplay_hook.py'))
 with (p/'log.txt').open('w') as log:subprocess.run(['nice','-n','19','python3',str(here/'test_core.py'),str(rom.resolve()),str(cmd),str(p)],env=env,check=True,stdout=log,stderr=log,timeout=30)
 rows=json.loads((p/'trajectory.json').read_text());assert len(rows)==140
 return rows
cases=[]
for team in [0,1]:
 for orient in [-1,1]:
  for px in [-110,110]:cases.append(dict(team=team,orient=orient,px=px,difficulty=1,period=0))
for period in [1,2,3]:cases.append(dict(team=0,orient=1,px=110,difficulty=1,period=period))
for difficulty in [0,2]:
 for period in [0,1,2,3]:cases.append(dict(team=0,orient=1,px=110,difficulty=difficulty,period=period))
def pair(spec):
 name='t{team}-o{orient}-x{px}-d{difficulty}-p{period}'.format(**spec);old=run('baseline',a.baseline,name,spec);new=run('fixed',a.rom,name,spec)
 # Compare the same elapsed frames while the original human carrier still owns
 # the puck and the actual native defensive winger job runs in both builds.
 pairs=[(b,n) for b,n in zip(old,new) if 10<=b['frame']<=60 and b['owner']==b['carrier'] and n['owner']==n['carrier'] and b['assignment']==n['assignment']==3]
 assert len(pairs)>=8,('insufficient natural coverage frames',name,len(pairs),old[:3],new[:3])
 before=statistics.mean(b['distance'] for b,n in pairs);after=statistics.mean(n['distance'] for b,n in pairs)
 if spec['difficulty']==1:assert after<before-3,('Hard coverage did not improve',name,before,after)
 else:assert abs(after-before)<5,('Normal/Easy native coverage changed',name,before,after)
 return dict(case=name,spec=spec,paired_frames=len(pairs),baseline_distance=round(before,3),fixed_distance=round(after,3),improvement=round(before-after,3))
with ThreadPoolExecutor(max_workers=3) as pool:results=list(pool.map(pair,cases))
(out/'results.json').write_text(json.dumps(results,indent=2)+'\n');print('PASS',len(results),'paired natural skating scenarios; mean coverage-distance reduction',round(statistics.mean(x['improvement'] for x in results if x['spec']['difficulty']==1),2),'rink units')
