from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import json,subprocess,os,hashlib,statistics
import argparse
ap=argparse.ArgumentParser(description='Paired native lateral carries: both clubs, ends and sides; no forcing after setup.')
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;state=a.state.resolve();o=a.output.resolve();o.mkdir(exist_ok=True,parents=True)
profiles={'baseline':a.baseline.resolve(),'candidate':a.rom.resolve()}
cases=[dict(team=t,orient=ori,sign=sgn,depth=d,shot_at=shot,charge=24,skate_frames=140,diagonal_frames=0,end_frame=348) for t in (0,1) for ori in (-1,1) for sgn in (-1,1) for d in (210,225,235) for shot in (35,50)]
def run(item):
 profile,s=item;n='t{team}-o{orient}-s{sign}-d{depth}-shot{shot_at}'.format(**s);p=o/(profile+'-'+n);p.mkdir(exist_ok=True);(p/'spec.json').write_text(json.dumps(s));(p/'commands.txt').write_text(f'100 load {state}\n170 snap\n195 snap\n349 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/'goalie_lateral_gameplay_hook.py'))
 with (p/'log.txt').open('w') as log:subprocess.run(['nice','-n','19','python3',str(here/'test_core.py'),str(profiles[profile]),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=40)
 rows=json.loads((p/'trajectory.json').read_text());assert len(rows)==240
 goals=[max(r['score'][side] for r in rows)-rows[0]['score'][side] for side in (0,1)]
 pre=[r for r in rows if r['owner']==r['carrier'] and r['anim'] in (0x250,0x2a2)]
 firstshot=next((r for r in rows if r['shotplayer']==r['carrier'] and r['owner']<0),None)
 return dict(profile=profile,name=n,spec=s,rom_sha256=hashlib.sha256(profiles[profile].read_bytes()).hexdigest(),score=goals,total_goals=sum(goals),locked_carry_frames=len(pre),firstshot=firstshot,keeper_catches=sum(1 for i,r in enumerate(rows) if r['owner']==s['team']*6+5 and (i==0 or rows[i-1]['owner']!=r['owner'])),clock_advanced=rows[-1]['clock']<rows[0]['clock'])
with ThreadPoolExecutor(max_workers=3) as pool:results=list(pool.map(run,[(p,s) for s in cases for p in profiles]))
(o/'results.json').write_text(json.dumps(results,indent=2)+'\n')
summary={p:dict(cases=sum(r['profile']==p for r in results),goals=sum(r['total_goals'] for r in results if r['profile']==p),catches=sum(r['keeper_catches'] for r in results if r['profile']==p),locked_carry_frames=sum(r['locked_carry_frames'] for r in results if r['profile']==p)) for p in profiles}
(o/'summary.json').write_text(json.dumps(summary,indent=2)+'\n');print(json.dumps(summary),flush=True)

before=summary['baseline'];after=summary['candidate']
assert before['cases']==after['cases']==48 and all(r['clock_advanced'] for r in results)
assert before['goals']>=8 and after['goals']<=before['goals']-3,('lateral scoring did not improve',summary)
assert after['catches']>=before['catches'],('keeper catches regressed',summary)
assert all(r['score'][r['spec']['team']]==0 for r in results),'wrong native goal attribution in fixture'
key='t0-o1-s1-d225-shot50'
old=next(r for r in results if r['profile']=='baseline' and r['name']==key)
new=next(r for r in results if r['profile']=='candidate' and r['name']==key)
assert old['total_goals']==1 and new['total_goals']==0,('reported lateral-carry geometry did not improve',old,new)
print('PASS 48 paired native carry/windup/release scenarios; original selected geometry reproduced and corrected')
