"""Actual Native input-entry/assignment: immediate B, nearest real puck, safe guards."""
from pathlib import Path
import argparse,json,struct,subprocess,os,hashlib
ap=argparse.ArgumentParser();ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);ap.add_argument('--observe-only',action='store_true');a=ap.parse_args();h=Path(__file__).resolve().parent;o=a.output.resolve();o.mkdir(exist_ok=False,parents=True);rom=a.rom.read_bytes();cases=[]
for adapter,pads in [(False,(0,1)),(True,(0,1,2,3))]:
 for pad in pads:
  for team in (0,1):
   for kind in ['loose','opponent','own_ai_goalie','own_human_goalie','pass_animation','held_latch','keeper_flight','fast_puck','other_human','boxed','knocked','own_skater','no_B']:
    cases.append(dict(adapter=adapter,pad=pad,team=team,kind=kind))
reports=[]
for batch in range((len(cases)+7)//8):
 p=o/str(batch);p.mkdir();body=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xfffff900,%sp'];ss=cases[batch*8:batch*8+8]
 for n,s in enumerate(ss):
  own=6*s['team'];old=own+(5 if s['kind']=='own_human_goalie' else 2);base=0xffffb04a;ob=base+128*old;bank=s['pad']%2;control=0xffffc320+2*bank;side=0xffffc328+2*bank;k=s['kind'];target=own+(1 if k not in ('other_human','boxed','knocked') else 0);want=old if k in ('own_skater','no_B','own_ai_goalie','own_human_goalie') else target
  body += [' lea 0xffffb04a,%a0',' move.w #383,%d0',f'clear{n}: clr.l (%a0)+',f' dbra %d0,clear{n}']
  for i in range(12):
   b=base+128*i;body += [f' move.w #{i},0x{b+0x52:x}',f' move.w #{0 if i%6==5 else 4},0x{b+0x34:x}',f' move.w #{100 if i//6==s["team"] else -100},0x{b:x}',f' move.w #100,0x{b+0x14:x}',f' move.b #{64 if i>=6 else 0},0x{b+0x62:x}']
  for i,x in [(own,25),(own+1,5),(old,60)]:body += [f' move.w #{x},0x{base+128*i:x}',f' clr.w 0x{base+128*i+0x14:x}']
  body += [f' ori.b #8,0x{ob+0x62:x}',f' move.w #{int(s["adapter"])},0xffffd046',f' move.w #{0 if s["pad"]>=2 else -1},0xffffc318',' move.l #0xffffffff,0xffffc320',' move.l #0xffffffff,0xffffc324',' clr.l 0xffffc328',' clr.l 0xffffc32c',f' move.w #{old},0x{control:x}',f' move.w #{s["team"]+1},0x{side:x}',' clr.w 0xffffd412',' clr.b 0xffffc2ea',' clr.b 0xffffc2ec',' clr.b 0xffffc2f2',' clr.b 0xffffc2fa',' clr.b 0xffffc2f6',' clr.w 0xfffff738',' clr.l 0xfffff048',' clr.l 0xfffff050',' move.w #0x5357,0xfffff054',' clr.l 0xfffff064',' clr.l 0xfffff0b2',' clr.l 0xfffff0b6',' clr.l 0xfffff0ba',' clr.l 0xfffff0be',' clr.l 0xfffff0c2',' clr.l 0xfffff0c6',' move.w #0x3447,0xfffff0ca',' clr.l 0xfffff0e0',' clr.w 0xffffb74a',' clr.w 0xffffb75e',' clr.w 0xffffb772',' clr.w 0xffffb774',' nop']
  # Format ownership separately to keep generated assembly independent of policy.
  body[-1]=f' move.w #{dict(opponent=6*(1-s["team"]),own_ai_goalie=own+5,own_human_goalie=own+5,own_skater=old).get(k,65535)},0xffffb7aa'
  if s['adapter'] and s['pad']>=2:
   # Real P3/P4 retain their physical side/control words while mirrored in P1/P2.
   body += [f' move.w #{old},0x{0xffffc320+2*s["pad"]:x}',f' move.w #{s["team"]+1},0x{0xffffc328+2*s["pad"]:x}']
  body += [f' move.w #{own+5 if k=="keeper_flight" else 65535},0xffffbeda',f' move.w #{own+1},0xffffbee0']
  if k=='pass_animation':body += [f' ori.b #8,0x{ob+0x63:x}']
  if k=='held_latch':body += [f' move.w #1,0x{0xfffff0c2+2*s["pad"] if s["adapter"] else 0xfffff050+2*bank:x}']
  if k=='fast_puck':body += [' move.w #0x6000,0xffffb772']
  if k in ('other_human','boxed','knocked'):
   addr=base+128*(own+1)+(0x63 if k=='boxed' else 0x62);body += [f' ori.b #{4 if k=="boxed" else 8 if k=="other_human" else 32},0x{addr:x}']
  body += [f' lea 0x{ob:x},%a3',f' moveq #{2*bank},%d4',f' moveq #{0 if k=="no_B" else 16},%d1',f' moveq #{0 if k=="no_B" else 16},%d3',' move.l #0x11223344,%d7',f' jsr 0x{0x2ea600 if s["adapter"] else 0x2fd900:x}',f' move.w 0x{control:x},0x{0xffff4800+2*n:x}',f' move.l %sp,0x{0xffff4900+4*n:x}',f' move.l %d7,0x{0xffff4a00+4*n:x}',f' move.b 0x{ob+0x63:x},0x{0xffff4b00+n:x}']
  s['expected']=want;s['old']=old
 body += [' move.l #0x42535754,0xffff4f00','end: bra.s end'];(p/'driver.s').write_text('\n'.join(body)+'\n');subprocess.run([str(h.parent/'m68kasm.sh'),str(p/'driver.s'),'2d9000',str(p/'code.bin')],check=True,stdout=subprocess.DEVNULL);code=(p/'code.bin').read_bytes();data=bytearray(rom);assert len(code)<0x2000 and data[0x2d9000:0x2d9000+len(code)]==b'\xff'*len(code);data[0x2d9000:0x2d9000+len(code)]=code
 for at in (0xb1c0,0xba88):data[at:at+2]=bytes.fromhex('4e75')
 struct.pack_into('>I',data,4,0x2d9000);(p/'driver.bin').write_bytes(data);(p/'commands.txt').write_text(f'100 dump ff4f00 4 done\n100 dump ff4800 {len(ss)*2} targets\n100 dump ff4900 {len(ss)*4} stacks\n100 dump ff4a00 {len(ss)*4} regs\n100 dump ff4b00 {len(ss)} oldflags\n101 exit\n')
 env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')}
 with (p/'log.txt').open('w') as log:subprocess.run(['python3',str(h/'test_core.py'),str(p/'driver.bin'),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=30)
 rows={q.split()[1]:bytes.fromhex(' '.join(q.split()[2:])) for q in (p/'commands.out').read_text().splitlines()};assert rows['done']==b'BSWT',(batch,'outer input dispatch failed')
 for n,s in enumerate(ss):
  observed=struct.unpack_from('>H',rows['targets'],2*n)[0];sp=struct.unpack_from('>I',rows['stacks'],4*n)[0];reg=struct.unpack_from('>I',rows['regs'],4*n)[0];reports.append(dict(case=s,observed=observed,immediate_expected=observed==s['expected'],stack=sp,register=reg));(o/'partial.json').write_text(json.dumps(reports,indent=2)+'\n')
  assert rows['oldflags'][n]==(8 if s['kind']=='pass_animation' else 0),(s,'old pass animation flags changed',rows['oldflags'][n])
  assert sp==0xfffff900 and reg==0x11223344,(s,'stack/register')
  if not a.observe_only:assert observed==s['expected'],(s,observed)
(o/'results.json').write_text(json.dumps(dict(rom_sha256=hashlib.sha256(rom).hexdigest(),cases=reports,scope='Actual input entry and unmodified Native C0BC/C0DA cursor assignment; no injected selection result'),indent=2)+'\n');print('OBSERVED' if a.observe_only else 'PASS',len(reports),'native immediate B/nearest actual puck/goalie/flight/hold/four-pad cases')
