#!/usr/bin/env python3
"""Execute native shot frames and inspect the sound ID consumed by $11132.

Usage: verify_critical_sound.py current.bin /private/work [--baseline previous.bin]
Only private reset-vector driver ROMs are written; no window or live SRAM.
The test sound stub replaces dispatch with counting and the native argument
cleanup, keeping all registers and the actual C538 call/stack contract intact.
"""
from pathlib import Path
import argparse
import json
import os
import struct
import subprocess

STUB = '''
    .org 0x300
sound_stub:
    movem.l %d0-%d1,-(%sp)
    move.w 12(%sp),%d0
    cmpi.w #14,%d0
    bne.s ordinary_sound
    addq.w #1,0xFFF610
    bra.s caller
ordinary_sound:
    cmpi.w #EXPECTED_SOUND,%d0
    bne.s wrong_sound
    addq.w #1,0xFFF612
    bra.s caller
wrong_sound:
    addq.w #1,0xFFF614
caller:
    cmpi.l #0xC53C,8(%sp)
    beq.s caller_ok
    addq.w #1,0xFFF616
caller_ok:
    movem.l (%sp)+,%d0-%d1
    jmp 0x1114E
'''


def main():
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument('rom', type=Path)
    ap.add_argument('work_dir', type=Path)
    ap.add_argument('--baseline', type=Path)
    ap.add_argument('--sound-id', type=int, default=123)
    args = ap.parse_args()
    here = Path(__file__).resolve().parent
    work = args.work_dir.resolve()
    work.mkdir(parents=True, exist_ok=True)
    source = (here/'testcases/critical_driver.s').read_text()
    reports = []
    cases = [('zero',0,24,False), ('half',50,24,False), ('max',100,24,False), ('alternate',100,20,False)]
    if args.baseline:
        cases += [('baseline',100,24,True)]
    for name, rating, original, baseline in cases:
        case = work/name
        case.mkdir(exist_ok=True)
        text = source.replace('move.w #-1,-(%sp)', f'move.w #{original},-(%sp)')
        text = text.replace('clr.w 0xFFF604', 'clr.w 0xFFF604\n    clr.l 0xFFF610\n    clr.l 0xFFF614')
        asm = case/'driver.s'
        asm.write_text(text+STUB.replace('EXPECTED_SOUND', str(original)).replace('cmpi.w #14,%d0', f'cmpi.w #{args.sound_id},%d0'))
        code = case/'driver-code.bin'
        subprocess.run([str(here.parent/'m68kasm.sh'),str(asm),'2FEA00',str(code)],check=True,stdout=subprocess.DEVNULL)
        data = bytearray((args.baseline if baseline else args.rom).read_bytes())
        driver = code.read_bytes()
        assert len(driver) < 0x600 and all(x == 255 for x in data[0x2FEA00:0x2FEA00+len(driver)])
        data[0x2FEA00:0x2FEA00+len(driver)] = driver
        struct.pack_into('>I',data,4,0x2FEA00)
        data[0x11132:0x11138] = bytes.fromhex('4ef9002fed00')
        data[0x2FF800+26*10+13] = rating
        rom = case/'driver.bin'
        rom.write_bytes(data)
        cmd = case/'commands.txt'
        cmd.write_text('1000 dump fff600 24 result\n1001 exit\n')
        env = {k:v for k,v in os.environ.items() if k not in ['GPGX_SRM','GPGX_HOOK','GPGX_WAV']}
        env['GPGX_DEV'] = '513'
        subprocess.run(['python3',str(here/'test_core.py'),str(rom),str(cmd),str(case)],env=env,check=True,stdout=subprocess.DEVNULL)
        raw = bytes.fromhex(cmd.with_suffix('.out').read_text().split('result ',1)[1])
        normal, critical, errors = struct.unpack_from('>3H',raw)
        glass, original_count, bad_sound, bad_caller = struct.unpack_from('>4H',raw,16)
        assert raw[8:12] == b'CRIT' and errors == bad_sound == bad_caller == 0
        assert normal+critical == glass+original_count == 10000
        assert glass == (0 if baseline else critical), (name,glass,critical)
        assert original_count == (10000 if baseline else normal)
        if rating == 0:
            assert critical == 0
        else:
            assert abs(critical/10000-rating/1000) < .015
        reports.append(dict(case=name,shots=10000,critical=critical,critical_sound_calls=glass,ordinary_sound_calls=original_count,
                            ordinary_sound_id=original,native_physics_and_stack='passed'))
    (work/'results.json').write_text(json.dumps(reports,indent=2)+'\n')
    print('PASS critical releases use the selected SFX once; ordinary/alternate sounds, native power/velocity and stack return verified')


if __name__ == '__main__':
    main()
