#!/usr/bin/env python3
"""Native approaching-pass keeper targets, span boundaries, depth and ABI."""
from pathlib import Path
import os,json,struct,subprocess
import argparse
ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);ap.add_argument('--minimum-span',type=int,choices=[32,64],default=64);a=ap.parse_args()
site=Path(__file__).resolve().parents[2];o=a.output.resolve();o.mkdir(parents=True,exist_ok=True)
cases=[dict(team=t,direction=d,sign=sgn,active=True) for t in [0,1] for d in [-1,1] for sgn in [-1,1]]
for key,v in [('difficulty',0),('difficulty',2),('flags',8),('flags',32),('boxed',4),('whistle',1),('highlight',16),('view',128),('penalty',4),('special',1),('special',4),('live',True),('cleared',True),('missed',True),('own_pass',True),('keeper_x',41),('keeper_y',231),('keeper_y',265),('receiver_y',159),('receiver_y',253),('target_depth',231),('target_depth',257),('receiver_role',0),('receiver_boxed',4)]:cases.append(dict(active=False,**{key:v}))
cases += [dict(active=True,sender_x=a.minimum_span-40),dict(active=False,sender_x=a.minimum_span-41)]
reports=[]
for mix in [1]:
 for batch in range((len(cases)+11)//12):
  ss=cases[batch*12:batch*12+12];p=o/f'{mix}-{batch}';p.mkdir(exist_ok=True);body=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xFFF800,%sp']
  expected=[]
  for n,s in enumerate(ss):
   t=s.get('team',0);d=s.get('direction',1);sgn=s.get('sign',1);actor=6*t+5;sender=6*t if s.get('own_pass') else 6*(1-t);target=sender+2;ab=0xffb04a+128*actor;tb=0xffb04a+128*target;pb=0xffb04a+128*sender
   body+=[' lea 0xFFB04A,%a0',' move.w #383,%d0',f'clear{n}: clr.l (%a0)+',f' dbra %d0,clear{n}']
   for b,vals in [(ab,[(0,s.get('keeper_x',20)*sgn),(0x14,s.get('keeper_y',246)*d),(0x34,0),(0x52,actor)]),(tb,[(0,-40*sgn),(0x14,s.get('receiver_y',220)*d),(0x34,s.get('receiver_role',4)),(0x52,target)]),(pb,[(0,s.get('sender_x',100)*sgn),(0x14,200*d),(0x34,4),(0x52,sender)])]:
    for off,v in vals:body.append(f' move.w #{v},0x{b+off:X}')
   body += [f' lea 0x{ab:X},%a3',f' move.b #{(64 if t else 0)+s.get("flags",0)},0x62(%a3)',f' move.b #{s.get("boxed",0)},0x63(%a3)',f' move.b #{s.get("receiver_boxed",0)},0x{tb+0x63:X}',f' move.w #{s.get("difficulty",1)},0xFFF07A',f' move.w #{target if s.get("live") else 65535},0xFFB7AA',f' move.w #{65535 if s.get("cleared") else target},0xFFBEE0',f' move.w #{sender},0xFFBEDA',f' move.w #{40*sgn},0xFFB74A',f' move.w #{200*d},0xFFB75E',f' move.w #{(512 if s.get("missed") else -512)*sgn},0xFFB772',f' move.w #{256*d},0xFFB774']
   for addr,v in [(0xc2ea,s.get('whistle',0)|s.get('highlight',0)),(0xc2ec,s.get('view',0)),(0xc2f2,s.get('penalty',0)),(0xc2fa,s.get('special',0))]:body.append(f' move.b #{v},0x{0xff0000+addr:X}')
   for i in range(8):body.append(f' move.l #0x{0x11223300+i:X},%d{i}')
   for i in [0,1,2,4,5,6]:body.append(f' movea.l #0x{0xff1000+256*i:X},%a{i}')
   native_y=s.get('target_depth',244)*d;native_x=8*sgn
   body += [f' move.w #{native_x},%d0',f' move.w #{native_y},%d2',' jsr 0xDA58',f' movem.l %d0-%d7/%a0-%a6,0x{0xff4000+n*64:X}',f' move.l %sp,0x{0xff4000+n*64+60:X}']
   expected.append(((native_x-18*sgn)//2 if mix==1 else (8*sgn-54*sgn)//4) if s['active'] else native_x)
  body+=[' move.l #0x4B505353,0xFF3FF0','end: bra.s end'];(p/'driver.s').write_text('\n'.join(body)+'\n');subprocess.run([str(site/'tools/m68kasm.sh'),str(p/'driver.s'),'2D9000',str(p/'code.bin')],check=True,stdout=subprocess.DEVNULL);data=bytearray(a.rom.read_bytes());code=(p/'code.bin').read_bytes();assert len(code)<0x3000 and data[0x2d9000:0x2d9000+len(code)]==b'\xff'*len(code);data[0x2d9000:0x2d9000+len(code)]=code;struct.pack_into('>I',data,4,0x2d9000);data[0xda5e:0xda6c]=bytes.fromhex('33dfffff3fe033dfffff3fe24e75');(p/'driver.bin').write_bytes(data);(p/'commands.txt').write_text(f'500 dump ff3ff0 4 done\n500 dump ff4000 {64*len(ss)} results\n501 exit\n');env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env['GPGX_DEV']='513'
  with (p/'log.txt').open('w') as log:subprocess.run(['python3',str(site/'tools/experimental/test_core.py'),str(p/'driver.bin'),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=30)
  result={l.split()[1]:bytes.fromhex(' '.join(l.split()[2:])) for l in (p/'commands.out').read_text().splitlines()};assert result['done']==b'KPSS'
  for n,(s,x) in enumerate(zip(ss,expected)):
   regs=struct.unpack_from('>16I',result['results'],n*64);y=s.get('target_depth',244)*s.get('direction',1);exp=[0x11223300+i for i in range(8)];exp[0]=(exp[0]&0xffff0000)|(x&65535);exp[1]=(exp[1]&0xffff0000)|(y&65535);exp[2]=(exp[2]&0xffff0000)|(y&65535);exp += [0xff1000+256*i if i!=3 else 0xffb04a+128*(s.get('team',0)*6+5) for i in range(7)]+[0xfff800];assert list(regs)==exp,(mix,s,regs,exp);reports.append(dict(mix=mix,spec=s,target_x=x,depth_unchanged=True,registers_preserved=True,stack_preserved=True))
(o/'results.json').write_text(json.dumps(reports,indent=2)+'\n');print('PASS',len(reports),'native keeper pass-target, depth, eligibility and exact register/stack cases')
