#!/usr/bin/env python3
"""Native white/orange/red shot windups across pads and difficulties.
Runs private copies only; checks composed pixels/palette without palette writes.
The existing shot-panel integration separately verifies replay/pause/timeout.
"""
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);a=ap.parse_args();here=Path(__file__).resolve().parent;out=a.output.resolve();out.mkdir(parents=True,exist_ok=True)
image=a.rom.read_bytes();left_panel=image[0x2ed00c:0x2ed00e]==bytes.fromhex('6000');tested=a.rom
if left_panel:
 probe=out/'observer';probe.mkdir(exist_ok=True);code=probe/'code.bin';subprocess.run([str(here.parent/'m68kasm.sh'),str(here/'testcases/shot_bar_probe.s'),'2d9000',str(code)],check=True,stdout=subprocess.DEVNULL)
 b=bytearray(image);payload=code.read_bytes();assert b[0x2d9000:0x2d9000+len(payload)]==b'\xff'*len(payload);b[0x2d9000:0x2d9000+len(payload)]=payload
 needle=bytes.fromhex('4eb9002ed00c');at=b.find(needle,0x227000,0x228000);assert at>=0 and b.find(needle,at+1,0x228000)<0
 b[at:at+6]=bytes.fromhex('4eb9002d9000');tested=probe/'rom.bin';tested.write_bytes(b)
def run(spec):
 p=out/f"difficulty{spec['difficulty']}-pad{spec['pad']}";p.mkdir(exist_ok=True)
 (p/'commands.txt').write_text(f'100 load {a.state.resolve()}\n800 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_HOOK=str(here/'shot_colour_hook.py'),SHOT_PANEL_CASE=json.dumps(dict(kind='goal',pad=spec['pad'])),SHOT_COLOUR_CASE=json.dumps(spec))
 with (p/'log.txt').open('w') as log:r=subprocess.run(['nice','-n','19','python3',str(here/'test_core.py'),str(tested.resolve()),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,timeout=30)
 assert r.returncode==0,(spec,(p/'log.txt').read_text()[-2200:])
 d=json.loads((p/'colour-result.json').read_text());assert {'white','orange','red'}<=set(d['stages']);return d
with ThreadPoolExecutor(max_workers=3) as pool:results=list(pool.map(run,[dict(pad=pad,difficulty=difficulty) for pad in (1,2,3,4) for difficulty in (0,1,2)]))
(out/'results.json').write_text(json.dumps(results,indent=2)+'\n');print('PASS',len(results),'native white/orange/solid-red windups; all pads/difficulties; unchanged native palette')
