"""Guarded headless native-routine fixture for focused feature contracts.
Reset PC, declared callee replacements and scratch data are explicit probes;
these runs are not matches, controller playtests or strength measurements.
"""
from pathlib import Path
import os,struct,subprocess
class NativeProbe:
 def __init__(self,rom,here):self.rom=Path(rom);self.h=Path(here)
 def run(self,out,body,size,patches=(),data=b'',sram_data=None,frames=1500,data_address=0x230000):
  p=Path(out);p.mkdir(parents=True,exist_ok=True);source=' .text\n .globl _start\n_start:\n move.w #0x2700,%sr\n movea.l #0xffff9800,%sp\n'+body+'\n move.l #0x50524f42,0xffff3ff0\nend: bra.s end\n';(p/'driver.s').write_text(source);subprocess.run([str(self.h.parent/'m68kasm.sh'),str(p/'driver.s'),'2d5000',str(p/'code.bin')],check=True,stdout=subprocess.DEVNULL)
  b=bytearray(self.rom.read_bytes());code=(p/'code.bin').read_bytes();assert len(code)<0x4000 and b[0x2d5000:0x2d5000+len(code)]==b'\xff'*len(code);b[0x2d5000:0x2d5000+len(code)]=code
  if data:
   assert b[data_address:data_address+len(data)]==b'\xff'*len(data);b[data_address:data_address+len(data)]=data
  for at,old,new in patches:
   assert len(old)==len(new) and b[at:at+len(old)]==old,('declared probe replacement bytes',hex(at));b[at:at+len(new)]=new
  struct.pack_into('>I',b,4,0x2d5000);(p/'rom.bin').write_bytes(b);(p/'commands.txt').write_text(f'{frames} dump ff3ff0 4 done\n{frames} dump ff4000 {size} result\n{frames+1} exit\n');env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env['GPGX_DEV']='513'
  if sram_data is not None:
   assert len(sram_data)==65536;(p/'probe.srm').write_bytes(sram_data);env['GPGX_SRM']=str(p/'probe.srm')
  with(p/'log').open('w') as log:subprocess.run(['nice','-n','19','python3',str(self.h/'test_core.py'),str(p/'rom.bin'),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=45)
  rows={x[1]:bytes.fromhex(' '.join(x[2:])) for line in(p/'commands.out').read_text().splitlines() if(x:=line.split())};assert rows['done']==b'PROB',('native return',p);return rows['result']
