#!/usr/bin/env python3
"""Native Scoring Leaders exit: clean/stale redraw latch, before/during period3.
Only the documented stale no-redraw bit is injected for diagnostic cases; exit,
menu rebuild, controller input, palette and subsequent play are all native.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse,json,os,subprocess
ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);ap.add_argument('--state',type=Path,required=True);ap.add_argument('--expect-black',action='store_true');a=ap.parse_args();out=a.output.resolve();out.mkdir(parents=True,exist_ok=True);here=Path(__file__).resolve().parent
cases=[dict(dirty=dirty,tap=tap,during=during) for dirty in [False,True] for tap in [1,20] for during in [False,True]]
def run(s):
 name='latch{dirty}-tap{tap}-during{during}'.format(**s);p=out/name;p.mkdir(exist_ok=True);start=500 if s['during'] else 200;open_at=start+650;exit_at=open_at+200;observe=exit_at+180;resume=observe+100
 lines=['100 load '+str(a.state.resolve())]
 if s['during']:lines+=['160 press 1 Start 5','390 press 1 Start 5']
 lines +=[f'{start+40*i} press 1 Down 5' for i in range(13 if s['during'] else 11)]+[f'{open_at} press 1 C 5',f'{exit_at} press 1 Start {s["tap"]}',f'{observe+1} snap',f'{resume} press 1 Start 5',f'{resume+220} snap',f'{resume+221} exit'];(p/'commands.txt').write_text('\n'.join(lines)+'\n')
 hook='''from pathlib import Path
import json
p=Path(outdir)
def rw(a):return (wram[ram(a)]<<8)|wram[ram(a+1)]
def lit():
 raw,w,h,pitch=frame[0]
 if pixfmt[0]==1:return sum(any(raw[y*pitch+4*x+k]>24 for k in range(3)) for y in range(h) for x in range(w))
 return sum(bool(raw[y*pitch+2*x] or raw[y*pitch+2*x+1]) for y in range(h) for x in range(w))
def test_tick(f):
 if f==OPEN-10 and DIRTY:wram[ram(0xc2f6)]|=2
 if f==RESUME+200:
  assert not rw(0xc2ec)&0x8000,('Start did not resume play',f,hex(rw(0xc2ec)))
 if f==OBSERVE:
  row=dict(case=SPEC,lit_pixels=lit(),view=rw(0xc2ec),menu=rw(0xcf20),latch=rw(0xc2f6),period=rw(0xc466),clock=rw(0xc468))
  assert row['menu']==(13 if SPEC['during'] else 11) and row['period']==2,row
  if EXPECT_BLACK and DIRTY:assert row['lit_pixels']==0,row
  else:assert row['lit_pixels']>1000,('black after one Start',row)
  assert not row['latch']&0x200,row
  (p/'result.json').write_text(json.dumps(row,indent=2)+'\\n')
'''.replace('RESUME',str(resume)).replace('OPEN',str(open_at)).replace('OBSERVE',str(observe)).replace('DIRTY',str(s['dirty'])).replace('SPEC',repr(s)).replace('EXPECT_BLACK',str(a.expect_black));(p/'hook.py').write_text(hook)
 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'))
 with (p/'log.txt').open('w') as log:subprocess.run(['nice','-n','19','python3',str(here/'test_core.py'),str(a.rom.resolve()),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=30)
 return json.loads((p/'result.json').read_text())
with ThreadPoolExecutor(max_workers=3) as pool:reports=list(pool.map(run,cases))
(out/'results.json').write_text(json.dumps(reports,indent=2)+'\n');print('PASS',len(reports),'native leader-exit cases;','old black-latch behavior reproduced' if a.expect_black else 'one Start returns to visible menu')
