"""Actual accepted-pickup observer: complete native GPR/SR/SP/RAM qualification.
Callee replaced explicitly with RTS to isolate the read-only observer itself;
natural CPU flow separately executes the real native pickup and release paths.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import struct,json,hashlib
from native_probe import NativeProbe

def verify(rom,code,here,out):
 h=Path(here);o=Path(out);o.mkdir(parents=True,exist_ok=True);image=Path(rom).read_bytes();probe=NativeProbe(rom,h)
 assert code[0x400:0x406]==bytes.fromhex('40e748e7fffe')
 contexts=[dict(irq=i,ccr=c,valid=v) for v in [True,False] for i in range(8) for c in range(32)]
 def batch(index,mutant=None):
  ss=contexts[index*8:index*8+8];body=[];expect=[]
  for j,z in enumerate(ss):
   owner=2 if z['valid'] else 3;regs=[0x11223300+i for i in range(8)]+[0xffff1000+256*i for i in range(7)];regs[0]=2;regs[10]=0xffffb14a;sr=0x2000+(z['irq']<<8)+z['ccr'];at=0xffff4000+82*j
   body += [' move.w #4,0xffffb17e',' move.w #2,0xffffb19c',f' move.w #{owner},0xffffb7aa',' clr.w 0xffffbeda',f' move.w #{0xff00|owner},0xffffbee0',' move.w #290,0xffffc468',' clr.l 0xffffef86',' clr.l 0xffffeb50',' clr.l 0xffffeb54',' clr.l 0xffffeb58']
   body += [f' move.l #{v},%d{i}' for i,v in enumerate(regs[:8])]+[f' movea.l #{v},%a{i}' for i,v in enumerate(regs[8:])]+[f' move.w #{sr},%sr',' jsr 0x2d9400',f' movem.l %d0-%d7/%a0-%a6,0x{at:x}',f' move.w %sr,0x{at+60:x}',f' move.l %sp,0x{at+62:x}',f' move.l 0xffffeb50,0x{at+66:x}',f' move.l 0xffffeb54,0x{at+70:x}',f' move.l 0xffffeb58,0x{at+74:x}',f' move.l 0xffffef86,0x{at+78:x}'];expect.append((regs,sr,owner))
  tail=82*len(ss);body += [' lea 0xffffb000,%a0',f' lea 0x{0xffff4000+tail:x},%a1',' move.w #0x4850/4-1,%d0','copyram: move.l (%a0)+,(%a1)+',' dbra %d0,copyram']
  payload=code
  if mutant=='owner':
   assert payload[0x416:0x41a]==bytes.fromhex('66000078');payload=payload[:0x416]+bytes.fromhex('4e714e71')+payload[0x41a:]
  if mutant=='D7':
   assert payload[-8:-6]==bytes.fromhex('46df');payload=payload[:-8]+bytes.fromhex('4287')+payload[-8:]
  base=[(0x2efc00,image[0x2efc00:0x2efc02],bytes.fromhex('4e75'))]
  observed=probe.run(o/(f'{mutant}-{index}' if mutant else f'current-{index}'),'\n'.join(body),tail+0x4850,base+[(0x2d9000,b'\xff'*len(payload),payload)])
  # Same callsite/stack to an RTS-only reference at the observation entry.
  reference=probe.run(o/(f'reference-{mutant}-{index}' if mutant else f'reference-{index}'),'\n'.join(body),tail+0x4850,[(0x2d9400,b'\xff\xff',bytes.fromhex('4e75'))])
  for j,(z,want) in enumerate(zip(ss,expect)):
   regs,sr,owner=want;at=82*j;got=list(struct.unpack_from('>15I',observed,at));returned,sp=struct.unpack_from('>HI',observed,at+60)
   assert (got,returned,sp)==(regs,sr,0xffff9800),('catch observer GPR/SR/SP corruption',z,got,returned,sp)
   assert observed[at:at+66]==reference[at:at+66],('catch observer native boundary ABI mismatch',z)
   actual=struct.unpack_from('>8H',observed,at+66);expected=(2,0,0xff02,290,1,1,1,1) if z['valid'] else (0,)*8
   assert actual==expected,('catch record identity/guard/serial',z,actual,expected)
  old,new=bytearray(reference[tail:]),bytearray(observed[tail:])
  for lo,hi in [(0xeb50,0xeb5c),(0xef86,0xef8a)]:old[lo-0xb000:hi-0xb000]=new[lo-0xb000:hi-0xb000]
  assert old==new,('catch observer changed game fields/actual RNG outside its packet',index)
  return ss
 with ThreadPoolExecutor(max_workers=3) as pool:rows=sum(pool.map(batch,range(64)),[])
 negatives=[]
 for index,mutant,needle in [(0,'D7','GPR/SR/SP'),(32,'owner','catch record identity')]:
  try:batch(index,mutant)
  except AssertionError as e:message=str(e);assert needle in message,message;negatives.append(message)
  else:raise AssertionError(('catch observer mutant accepted',mutant))
 report=dict(rom_sha256=hashlib.sha256(image).hexdigest(),observer_sha256=hashlib.sha256(code).hexdigest(),contexts=rows,complete_native_GPR_SR_all_CCR_IRQ_SP_RAM_RNG_exact=True,active_negative_controls=negatives)
 (o/'results.json').write_text(json.dumps(report,indent=2)+'\n');print('PASS512 accepted-catch observer full GPR/SR/IRQ/SP/RAM/RNG contexts and2 active mutants',flush=True);return report
