#!/usr/bin/env python3
"""Complete C2F2 execution equals unmodified native physics for the same target.

The reference executes original C566, then supplies the exact chosen EXISTING
target to its original BEDC write. Its remaining shot/accuracy/error/RNG/power
instructions are unchanged. This is an isolated Native contract, no win credit.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse, hashlib, json, os, struct, subprocess
from smart_aim_test_support import setup

ap=argparse.ArgumentParser(description=__doc__)
ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path)
ap.add_argument('--first-only',action='store_true')
a=ap.parse_args();h=Path(__file__).resolve().parent;o=a.output.resolve();o.mkdir(parents=True,exist_ok=True)
cases=[dict(team=t,direction=d,difficulty=level,human=human,one_timer=True,animation=0x7fc,busy=True) for t in (0,1) for d in (-1,1) for level in (0,1,2) for human in (False,True)]
cases += [dict(team=t,direction=d,difficulty=1,one_timer=True,animation=0x7fc,crit=True) for t in (0,1) for d in (-1,1)]
if a.first_only:cases=cases[:1]
def run(s,index,reference):
    p=o/(('native-reference-' if reference else 'smart-')+str(index));p.mkdir(parents=True,exist_ok=True)
    lines,_=setup(s)
    body=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xfffff900,%sp']+lines
    # Inspect policy before the shot. Both paths receive identical state and
    # then execute their full actual C2F2; this inspection adds no random draws.
    body += [' jsr 0xc566',' jsr 0x2df008',' move.w %d1,0xffff4880',f' move.w #{0 if s.get("crit") else 1},0xfffff0ec',' clr.w 0xfffff060',' clr.w 0xfffff0b0',' clr.w 0xfffff618',' clr.w 0xfffff002',' clr.w 0xfffff700',' move.w #31,0xffffbede',' move.l #0x12345678,0xffffd066']
    for reg in range(8):body.append(f' move.l #0x{0x11223300+reg:x},%d{reg}')
    body += [' move.w #0x2710,%sr',' jsr 0xc2f2',' move.w %sr,0xffff4800',' movem.l %d0-%d7/%a0-%a6,0xffff4000',' move.l %sp,0xffff403c',' move.l #0x53485059,0xffff3ff0','end: bra.s end','forced_target:',' moveq #0,%d0',' move.w 0xffff4880,%d0',' move.w %d0,0xffffbedc',' btst #2,0xffffc2f2',' rts']
    (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)
    data=bytearray(a.rom.read_bytes());code=(p/'code.bin').read_bytes();assert data[0x2d9000:0x2d9000+len(code)]==b'\xff'*len(code);data[0x2d9000:0x2d9000+len(code)]=code;struct.pack_into('>I',data,4,0x2d9000)
    if reference:
        assert data[0xc624:0xc62e]==bytes.fromhex('31c0bedc08380002c2f2')
        marker=bytes.fromhex('70003039ffff4880');at=code.index(marker)
        data[0xc30c:0xc314]=bytes.fromhex('610002583f3c0005')
        data[0xc624:0xc62e]=bytes.fromhex('4eb9')+struct.pack('>I',0x2d9000+at)+bytes.fromhex('4e714e71')
    (p/'driver.bin').write_bytes(data);(p/'commands.txt').write_text('200 dump ff3ff0 4 done\n200 dump ff4000 64 regs\n200 dump ff4800 2 sr\n200 dump ff4880 2 selected\n200 dump ffb04a 1536 actors\n200 dump ffb74a 104 puck\n200 dump ffbed8 88 shot\n200 dump ffd066 4 rng\n200 dump fff000 128 effects\n201 exit\n')
    env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env['GPGX_AUTO_CREDITS']='0'
    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)
    out={line.split()[1]:bytes.fromhex(' '.join(line.split()[2:])) for line in (p/'commands.out').read_text().splitlines()}
    assert out['done']==b'SHPY',('native C2F2 did not return',s)
    return out
def pair(item):
    index,s=item;native=run(s,index,True);smart=run(s,index,False)
    assert native==smart,(s,{k:[native[k].hex()[:160],smart[k].hex()[:160]] for k in native if native[k]!=smart[k]})
    assert int.from_bytes(smart['regs'][-4:],'big')==0xfffff900,'complete native return stack'
    vx,vy=struct.unpack_from('>hh',smart['puck'],0x28)
    assert vx or vy,('actual native shot has no velocity',s)
    return dict(case=s,selected_native_target=int.from_bytes(smart['selected'],'big'),complete_native_actors_puck_power_height_error_rng_effects_registers_CCR_stack_identical=True,puck_velocity=[vx,vy])
with ThreadPoolExecutor(max_workers=3) as pool:rows=list(pool.map(pair,enumerate(cases)))
(o/'results.json').write_text(json.dumps(dict(rom_sha256=hashlib.sha256(a.rom.read_bytes()).hexdigest(),cases=rows),indent=2)+'\n')
print('PASS',len(rows),'complete native C2F2 same-target physical equivalence cases, Normal/Easy/humans and Crit On/Off')
