"""Native CPU forward selection, guards/ABI and unchanged native line-energy math."""
from pathlib import Path
import struct,subprocess,json,os,hashlib
import argparse
ap=argparse.ArgumentParser();ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);a=ap.parse_args();here=Path(__file__).resolve().parent;out=a.output.resolve();out.mkdir(parents=True,exist_ok=True);tools=here.parent;rom=a.rom.read_bytes();report=[]
def attrs(club):
 ptr=struct.unpack_from('>I',rom,0x30e+club*4)[0];p=ptr+struct.unpack_from('>H',rom,ptr)[0];rr=[]
 for i in range(26):
  p+=struct.unpack_from('>H',rom,p)[0];rr.append(rom[p:p+8]);p+=8
 return rr
# Controlled valid roster-ID/profile fixture; check immutable original56 bytes
# and true native GetLineE arithmetic for the608-byte first-unit policy.
lines=bytes([3,20,19,11,14,17,8,0,3,25,23,7,6,13,9,0,3,26,21,10,4,12,15,0])+bytes([3,20,19,11,14,6,8,0])*4
cases=[]
for side in [0,1]:
 for line in [0,1,2,3,4,5,6,-1]:cases.append(dict(name=f'cpu-{side}-{line}',side=side,line=line,active=line in [0,1,2]))
 for difficulty in [0,2]:cases.append(dict(name=f'difficulty-{side}-{difficulty}',side=side,difficulty=difficulty,active=False))
 for pad in range(4):
  for own in [True,False]:cases.append(dict(name=f'pad-{side}-{pad}-{own}',side=side,pad=pad,human=side+1 if own else 2-side,active=not own))
 for flag in [1,4]:cases.append(dict(name=f'penalty-shot-shootout-{side}-{flag}',side=side,flag=flag,active=False))
 cases += [dict(name=f'bad-roster-{side}',side=side,badptr=True,active=False),dict(name=f'injured-{side}',side=side,injured=True,active=True),dict(name=f'too-few-{side}',side=side,few=True,active=False)]
def expected(s):
 if not s['active']:return lines[:24]
 ff=[];pool={v-1 for ln in range(3) for v in lines[8*ln+3:8*ln+6]}
 for i,a in enumerate(attrs(10)):
  if i not in pool or s.get('injured') and i in [13,5] or s.get('few') and i not in [3,6]:continue
  score=(a[1]&15)+3*(a[2]>>4)+2*(a[2]&15)+2*(a[3]&15)+3*(a[5]&15)+(a[5]>>4)+(a[7]>>4);ff.append((-score,i,a[4]&1))
 ff.sort();x=bytearray(lines[:24]);assert len(ff)>=3
 for j,(_,i,h) in enumerate(ff[:3]):
  if j==0:slot=4
  elif j==1:wing=5 if h==0 else 3;slot=wing
  else:slot=8-wing
  selected=i+1;displaced=x[slot]
  for ln in range(3):
   for pos in [3,4,5]:
    at=8*ln+pos
    if x[at]==displaced:x[at]=selected
    elif x[at]==selected:x[at]=displaced
 return bytes(x)
def run(s):
 p=out/s['name'];p.mkdir(exist_ok=True);side=s['side'];team=0xffffc6ce+side*0x364;ptr=struct.unpack_from('>I',rom,0x336)[0];base=team+0x16a;cache=0xfffff500+32*side;line=s.get('line',0);body=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xFFF800,%sp',' lea 0xFFFFB000,%a0',' move.w #0x4800/4-1,%d0','zero: clr.l (%a0)+',' dbra %d0,zero',f' move.w #{s.get("difficulty",1)},0xFFF07A',f' move.l #{0 if s.get("badptr") else ptr},0x{team+0x1e:X}',f' move.w #{line},0x{team+0x16:X}',f' move.w #6,0x{team+0x24:X}',f' move.w #10,0x{0xffc330+2*side:X}',f' move.b #{s.get("flag",0)},0xFFC2FA']
 for i in range(26):
  unavailable=-3 if s.get('injured') and i==13 else 60 if s.get('injured') and i==5 else -4 if s.get('few') and i not in [3,6] else -1
  body += [f' move.w #{unavailable},0x{team+0x66+2*i:X}',f' move.w #{700+97*i},0x{team+0x32+2*i:X}']
 for i in range(0,len(lines),4):body.append(f' move.l #0x{lines[i:i+4].hex()},0x{base+i:X}')
 if 'human' in s:body.append(f' move.w #{s["human"]},0x{0xffc328+2*s["pad"]:X}')
 for r in range(8):body.append(f' move.l #0x{0x10203040+0x1010101*r:X},%d{r}')
 for r in range(7):body.append(f' movea.l #0x{team if r==2 else 0xffe100+32*r:X},%a{r}')
 body += [' jsr 0x1584C',' movem.l %d0-%d7/%a0-%a6,0xFF4000',' move.w %sr,0xFF403C']
 energy_lines=[0,1,2] if line in [0,1,2] else [3,4,5]
 for n,ln in enumerate(energy_lines):body += [f' moveq #{ln},%d0',' jsr 0x12EB4',f' move.l %d0,0x{0xff4040+n*4:X}']
 body += [' move.l #0x4C434348,0xFF3FFC','done: bra.s done'];(p/'driver.s').write_text('\n'.join(body)+'\n');subprocess.run([str(tools/'m68kasm.sh'),str(p/'driver.s'),'2D9000',str(p/'driver.bin')],check=True,stdout=subprocess.DEVNULL);code=(p/'driver.bin').read_bytes();assert len(code)<0x3000;b=bytearray(rom);assert b[0x2d9000:0x2d9000+len(code)]==b'\xff'*len(code);b[0x2d9000:0x2d9000+len(code)]=code;b[0x15854:0x15856]=bytes.fromhex('4e75');struct.pack_into('>I',b,4,0x2d9000);(p/'rom.bin').write_bytes(b);(p/'commands.txt').write_text(f'500 dump ff3ffc 4 done\n500 dump ff4000 80 regs\n500 dump {base:x} 56 original\n500 dump {cache:x} 24 cache\n500 dump {team+0x32:x} 104 energy_availability\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(['nice','-n','19','python3',str(here/'test_core.py'),str(p/'rom.bin'),str(p/'commands.txt'),str(p)],env=env,check=True,stdout=log,stderr=log,timeout=60)
 rr={l.split()[1]:bytes.fromhex(' '.join(l.split()[2:])) for l in (p/'commands.out').read_text().splitlines()};assert rr['done']==b'LCCH',(s,'native driver incomplete');regs=struct.unpack_from('>15I',rr['regs']);assert regs[0]==0x10200000+(line&65535),(s,'D0',hex(regs[0]))
 for r in range(1,8):assert regs[r]==0x10203040+0x1010101*r,(s,'D'+str(r))
 for r in [0,2,3,4,5,6]:assert regs[8+r]==(team if r==2 else 0xffe100+32*r),(s,'A'+str(r))
 assert rr['original']==lines,(s,'persistent lineups changed');x=expected(s);wantptr=cache if s['active'] else base;assert regs[9]==wantptr,(s,'A1',hex(regs[9]),hex(wantptr))
 if s['active']:assert rr['cache']==x,(s,'cache',rr['cache'].hex(),x.hex())
 # Native GetLineE must use the SAME selected people, with unchanged native
 # average/remainder arithmetic and unchanged individual energy values.
 for n,ln in enumerate(energy_lines):
  energy_map=x if ln<3 else lines
  ids=energy_map[8*ln+1:8*ln+6];total=sum(700+97*(i-1) for i in ids);want=(total%5<<16)|(total//5);got=struct.unpack_from('>I',rr['regs'],64+n*4)[0];assert got==want,(s,'native energy',ln,hex(got),hex(want),ids.hex())
 energies=struct.unpack_from('>26H',rr['energy_availability']);assert list(energies)==[700+97*i for i in range(26)],(s,'energy written')
 return dict(case=s['name'],passed=True,selected_lines=x.hex(),native_energy=True)
for s in cases:
 report.append(run(s));print('PASS',s['name'],flush=True)
(out/'results.json').write_text(json.dumps(dict(status='passed',rom_sha256=hashlib.sha256(rom).hexdigest(),cases=report),indent=2)+'\n')
