"""Current-ROM natural Stars fixture plus the immutable historical reproducer.

Original seven old pending timers and >=3 baseline corruptions are retained.
The current candidate uses its own authenticated completed native game, current
rosters and current six-cell HUD rather than importing obsolete VRAM ownership.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime,timezone
import hashlib,json,os,shutil,struct,subprocess
from PIL import Image,ImageChops

def verify(a,h,o):
 h=Path(h);o=Path(o);o.mkdir(parents=True,exist_ok=True);rom=Path(a.rom).resolve();baseline=Path(a.baseline).resolve();sha=lambda p:hashlib.sha256(Path(p).read_bytes()).hexdigest()
 legacy=Path(a.state).resolve();old=legacy.read_bytes();assert old.startswith(b'GENPLUS-GX 1.7.6')and struct.unpack_from('<I',old,144468)[0]==0xa97e,'immutable natural historical pre-Stars capture required'
 current=getattr(a,'current_state',None)
 if current is None:
  producer=o/'current-native-producer';command=['python3',str(h/'testcases/transfer_flow.py'),str(rom),str(producer),'--abi']
  with(o/'producer.log').open('w')as log:subprocess.run(command,stdout=log,stderr=log,check=True,timeout=900)
  current=producer/'actual-four-pad-game/before-stars.state'
 current=Path(current).resolve();seed=current.with_suffix('.srm');entry=next(z for z in json.loads((current.parent/'captures.json').read_text())if z['id']==current.stem)
 assert entry['ROM_sha256']==sha(rom)and entry['state_sha256']==sha(current)and entry['paired_sram_sha256']==sha(seed),'wrong-ROM or unauthenticated actual completed-game state'
 raw=current.read_bytes();word=lambda at:struct.unpack_from('<H',raw,16+at)[0]
 assert not word(0xc2ea)&0x8000,'current Stars already began before the pending-HUD trial'
 expected=[word(x)for x in[0xc6da,0xca3e,0xc466,0xc468]]
 base=bytearray(rom.read_bytes());patches=[]
 for at in[0x2ed000,0x2e001c]:
  assert base[at:at+2]==bytes.fromhex('6000');patches.append(dict(address=hex(at),old='6000',new='4e75'));base[at:at+2]=bytes.fromhex('4e75')
 base[0x18e:0x190]=(sum(int.from_bytes(base[i:i+2],'big')for i in range(0x200,len(base),2))&65535).to_bytes(2,'big')
 reference=o/'current-native-window-reference.bin';reference.write_bytes(base)
 (o/'reference-scope.json').write_text(json.dumps(dict(parent_ROM_sha256=sha(rom),reference_sha256=sha(reference),patches=patches,scope='Suppress only custom per-frame shot/crowd drawing; original Stars window, roster getters, native accounting and entry cleanup remain real.'),indent=2)+'\n')
 hook='''from pathlib import Path
from datetime import datetime,timezone
import json,hashlib,struct
p=Path(outdir);spec=json.loads((p/'spec.json').read_text());images=[]
def rw(a):return(wram[ram(a)]<<8)|wram[ram(a+1)]
def ww(a,v):wram[ram(a)]=v>>8;wram[ram(a+1)]=v&255
def serialize():
 n=core.retro_serialize_size();b=C.create_string_buffer(n);assert core.retro_serialize(b,n);return b.raw
def capture(name):
 raw,width,height,pitch=frame[0]
 if pixfmt[0]==1:im=Image.frombuffer('RGBA',(width,height),raw,'raw','BGRA',pitch,1).convert('RGB')
 else:
  px=bytearray()
  for y in range(height):
   for(v,)in struct.iter_unpack('<H',raw[y*pitch:y*pitch+width*2]):px+=bytes(((v>>11)<<3,((v>>5)&63)<<2,(v&31)<<3))
  im=Image.frombytes('RGB',(width,height),bytes(px))
 file=p/(name+'.png');im.save(file);images.append(dict(image=file.name,image_sha256=hashlib.sha256(file.read_bytes()).hexdigest(),frame=f,ROM_sha256=hashlib.sha256(romdata).hexdigest(),captured=datetime.now(timezone.utc).isoformat()));(p/'images.json').write_text(json.dumps(images,indent=2))
def test_tick(f):
 if f==100:
  assert not rw(0xc2ea)&0x8000,'Stars began before pending fixture'
  ww(0xf640,0x534d);ww(0xf646,spec['pending']);ww(0xf64c,int(spec['pending']>0));ww(0xf64e,0)
 if f in[150,400]:capture('frame'+str(f))
 if f==400:
  raw=serialize();v=raw[74789:74789+65536]
  assert rw(0xc2ea)&0x8000,'original native Stars did not execute'
  cells=[list(struct.unpack_from('<3H',v,0xe000+row*128+33*2))for row in[24,26]]
  (p/'result.json').write_text(json.dumps(dict(stars=True,score_period_clock=[rw(x)for x in[0xc6da,0xca3e,0xc466,0xc468]],show=rw(0xf646),panel=rw(0xf64c),momentum_flags=rw(0xf8de),below_name_cells=cells),indent=2))
'''
 def run(task):
  profile,selected,pending,state=task;p=o/f'{profile}-{pending}';p.mkdir(exist_ok=True);(p/'hook.py').write_text(hook);(p/'spec.json').write_text(json.dumps(dict(pending=pending)));(p/'commands.txt').write_text(f'100 load {state}\n401 exit\n')
  env={k:v for k,v in os.environ.items()if not k.startswith('GPGX_')};env.update(GPGX_DEV='513',GPGX_HOOK=str(p/'hook.py'))
  if profile!='baseline':shutil.copy2(seed,p/'battery.srm');env['GPGX_SRM']=str(p/'battery.srm')
  with(p/'log').open('w')as log:subprocess.run(['python3',str(h/'test_core.py'),str(selected),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=60)
  result=json.loads((p/'result.json').read_text())
  if profile!='baseline':assert(p/'battery.srm').read_bytes()==seed.read_bytes(),'Stars presentation changed completed-game64KB SRAM'
  return profile,pending,result
 timers=[0,1,2,8,30,120,240];tasks=[('baseline',baseline,t,legacy)for t in timers]+[('reference',reference,0,current)]+[('candidate',rom,t,current)for t in timers]
 with ThreadPoolExecutor(max_workers=3)as pool:rows=list(pool.map(run,tasks))
 crop=(43,116,261,181);old_ref=Image.open(o/'baseline-0/frame400.png').convert('RGB').crop(crop);new_ref=Image.open(o/'reference-0/frame400.png').convert('RGB').crop(crop);reports=[];reproduced=0
 for profile,pending,result in rows:
  panel=Image.open(o/f'{profile}-{pending}/frame400.png').convert('RGB').crop(crop);gold=old_ref if profile=='baseline'else new_ref;exact=panel.tobytes()==gold.tobytes()
  if profile=='baseline':
   assert result['score_period_clock']==[3,0,2,0],('historical native score/period/clock changed',pending,result)
   if not exact:reproduced+=1
  else:
   assert exact,('current native complete Stars window changed/damaged',profile,pending,ImageChops.difference(panel,gold).getbbox())
   assert result['score_period_clock']==expected,('current actual completed game was changed',profile,pending,result,expected)
   assert result['show']==result['panel']==result['momentum_flags']==0,('current Stars did not retire pending HUD',profile,pending,result)
   assert result['below_name_cells']==[[0xa403]*3]*2,('current six Momentum cells remain under native names',profile,pending,result)
  reports.append(dict(profile=profile,pending_timer=pending,native_window_exact=exact,**result))
 assert reproduced>=3,('immutable baseline timeout/window corruption not reproduced',reproduced)
 (o/'results.json').write_text(json.dumps(dict(ROM_sha256=sha(rom),baseline_sha256=sha(baseline),historical_state_sha256=sha(legacy),current_native_capture=entry,baseline_corruption_cases=reproduced,seven_current_timers_verified=True,current_full64KB_SRAM_unchanged=True,cases=reports,scope=__doc__),indent=2)+'\n')
 print('PASS all7 current native Stars windows/timers/six-cell HUD retirement/full64KB SRAM, actual score/period/clock; immutable old7 baseline corruptions reproduced',reproduced,flush=True)
