"""Native containment stopping target, arrival callback and scope guard contracts.
These are reset/declared-callee fixtures, not matches or strength measurements.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import struct,hashlib,json
from native_probe import NativeProbe

def verify(rom,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 image[0x228040:0x228044]==bytes.fromhex('0c400009'),'arrival brake callback missing'
 specs=[dict(orient=d,v=v,base=y) for d in [-1,1] for v in [-32768,-20000,-12000,-4096,-1,0,1,4096,8000,12000,20000,32767] for y in [-160,-236]]
 def geometry(batch):
  ss=specs[batch*12:batch*12+12];body=[]
  for j,z in enumerate(ss):
   body += [' lea 0xffffb04a,%a3',f' move.w #{z["v"]},0x2a(%a3)',f' move.b #{128 if z["orient"]==1 else 0},0x62(%a3)',f' move.w #{z["base"]},%d1',' move.l #0x12345678,%d0',' move.l #0x24681357,%d4',' jsr 0x228000',f' move.w %d1,0x{0xffff4000+10*j:x}',f' move.l %d0,0x{0xffff4002+10*j:x}',f' move.l %d4,0x{0xffff4006+10*j:x}']
  raw=probe.run(o/f'geometry-{batch}','\n'.join(body),10*len(ss));rows=[]
  for j,z in enumerate(ss):
   v=z['v']*z['orient'];v=((v+32768)%65536)-32768
   extra=max(0,((v*v//65536)*17)//300-(v//256)) if v>0 else 0
   want=z['base']-extra;got,d0,d4=struct.unpack_from('>hII',raw,10*j);assert (got,d0,d4)==(want,0x12345678,0x24681357),('coasting target math/context',z,got,want,d0,d4)
   rows.append(dict(spec=z,outward_velocity=v,extra_coast=extra,target=got))
  return rows
 with ThreadPoolExecutor(max_workers=3) as pool:rows=sum(pool.map(geometry,range(4)),[])
 # Callee spy: every ordinary command still calls native E594, with D0 intact.
 # Native command9 alone returns as an arrival brake. The spy preserves SR/GPR.
 spy=bytes.fromhex('40e75279ffff300046df4e75');patch=[(0xe594,image[0xe594:0xe594+len(spy)],spy)]
 def callbacks(index,mutant=False):
  ss=[(command,irq) for command in range(16) for irq in range(8)][index*16:index*16+16];body=[];expected=[]
  for j,(cmd,irq) in enumerate(ss):
   regs=[0x11223300+i for i in range(8)]+[0xffff1000+i*256 for i in range(7)];regs[0]=cmd;sr=0x2000+(irq<<8)+(cmd&31)
   body += [' clr.w 0xffff3000']+[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 0x228040',f' movem.l %d0-%d7/%a0-%a6,0x{0xffff4000+68*j:x}',f' move.w %sr,0x{0xffff403c+68*j:x}',f' move.l %sp,0x{0xffff403e+68*j:x}',f' move.w 0xffff3000,0x{0xffff4042+68*j:x}'];expected.append((regs,sr,cmd!=9))
  patches=patch+([(0x228044,image[0x228044:0x228046],bytes.fromhex('4e71'))] if mutant else []);raw=probe.run(o/('callback-mutant' if mutant else f'callback-{index}'),'\n'.join(body),68*len(ss),patches)
  for j,(regs,sr,called) in enumerate(expected):
   got=list(struct.unpack_from('>15I',raw,68*j));returned,sp,n=struct.unpack_from('>HIH',raw,68*j+60)
   assert got==regs and sp==0xffff9800 and returned&0xff00==sr&0xff00,('callback GPR/IRQ/SP',ss[j],got,sp,returned,sr)
   assert n==int(called),('native arrival brake overridden or ordinary checking skipped',ss[j],n,called)
  return dict(contexts=ss,complete_GPR_IRQ_SP_preserved=True)
 with ThreadPoolExecutor(max_workers=3) as pool:cb=list(pool.map(callbacks,range(8)))
 try:callbacks(4,True)
 except AssertionError as e:negative=str(e);assert 'native arrival brake overridden' in negative,negative
 else:raise AssertionError('arrival charge override mutant not detected')
 report=dict(rom_sha256=hashlib.sha256(image).hexdigest(),coast_geometry_cases=rows,callback_contexts=128,callback_batches=cb,arrival_override_mutant_detected=negative,native_physics_and_stats_unchanged=True,scope='Only Hard CPU nearest-containing defender receives the callback. Guarded in original ai_gap native context suite. Native checking remains for every command other than arrival9.')
 (o/'results.json').write_text(json.dumps(report,indent=2)+'\n');print('PASS48 outward-coast geometry and128 native-check callback GPR/IRQ/SP contexts; arrival-override mutant rejected',flush=True);return report
