#!/usr/bin/env python3
"""Mandatory native downed-Y ABI/guards and genuine solo-season mash reproducer."""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse,hashlib,json,os,shutil,subprocess,struct
from downed_y import ORIGIN,HOOK,OLD,NEW
ap=argparse.ArgumentParser();ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);a=ap.parse_args();h=Path(__file__).resolve().parent;rom=a.rom.resolve();o=a.output.resolve();o.mkdir(parents=True,exist_ok=True);sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest();b=rom.read_bytes();assert b[HOOK:HOOK+6]==NEW
cmd=['python3',str(h/'testcases/downed_y_abi.py'),str(rom),str(o/'abi')]
with(o/'abi.log').open('w')as f:subprocess.run(cmd,stdout=f,stderr=f,check=True,timeout=600)
code=o/'module.bin';subprocess.run([str(h.parent/'m68kasm.sh'),str(h/'downed_y.s'),'25cc00',str(code)],check=True,stdout=subprocess.DEVNULL);blob=code.read_bytes();assert b[ORIGIN:ORIGIN+0x200]==blob+b'\xff'*(0x200-len(blob))
legacy=bytearray(b);legacy[HOOK:HOOK+6]=OLD;legacy[ORIGIN:ORIGIN+0x200]=b'\xff'*0x200;struct.pack_into('>H',legacy,0x18e,sum(struct.unpack_from('>H',legacy,i)[0]for i in range(0x200,len(legacy),2))&65535);ref=o/'matched-legacy-Y.bin';ref.write_bytes(legacy)
def one(item):
 name,selected=item;p=o/name;p.mkdir(exist_ok=True);shutil.copy2(h/'testcases/downed_y_cold_hook.py',p/'hook.py');shutil.copy2(h/'testcases/capture_native.py',p/'capture_native.py');(p/'spec.json').write_text('{"captures":{}}');(p/'battery.srm').write_bytes(bytes(65536));(p/'commands.txt').write_text('1100 press 1 Start 5\n1700 press 1 Start 5\n2300 press 1 Start 5\n2900 press 1 Start 5\n24000 exit\n');env={k:v for k,v in os.environ.items()if not k.startswith('GPGX_')};env.update(GPGX_DEV='1281',GPGX_DEV2='1281',GPGX_SRM=str(p/'battery.srm'),GPGX_HOOK=str(p/'hook.py'))
 with(p/'console.log').open('w')as f:subprocess.run(['nice','-n','19','python3',str(h/'test_core.py'),str(selected),str(p/'commands.txt'),str(p)],env=env,stdout=f,stderr=f,check=True,timeout=180)
 d=json.loads((p/'result.json').read_text());assert d['ROM_sha256']==sha(selected)and d['actual_solo_Full_from_cold_boot']and d['no_actor_clock_score_RNG_writes'];assert len(d['attempts'])>=60,'mash run truncated';return name,d
with ThreadPoolExecutor(max_workers=2)as ex:d=dict(ex.map(one,[('legacy',ref),('candidate',rom)]))
assert len(d['legacy']['stable_eligible_failures'])>0,'matched original downed-Y bug not reproduced'
assert not d['candidate']['stable_eligible_failures'],'valid Y requests still lost in genuine solo Full season'
assert any(z['success']and z['before']['actor_flags']&0x2000 for z in d['candidate']['attempts']),'no real downed-skater Y takeover'
(o/'results.json').write_text(json.dumps(dict(status='passed',ROM_sha256=sha(rom),native_ABI=json.loads((o/'abi/results.json').read_text()),matched_legacy_sha256=sha(ref),matched_legacy_failures=len(d['legacy']['stable_eligible_failures']),candidate_failures=0,actual_cold_solo_Full=True,no_healing_or_actor_clock_RNG_writes=True,scope=__doc__),indent=2)+'\n');print('PASS downed-Y full native ABI/guards plus original failure and corrected actual solo Full button-mash input',flush=True)
