experimental/verify_forum_fixes.py

Download file

Release 2026-11-04-r2 · SHA256 c0564956cfcc71d5c997ca4b4f2bbe56547faf323b5ecf0e6c6656abfc1cfb61 · Click a line number for a direct link.

1#!/usr/bin/env python32"""Native 68000 regressions for forum fixes. Private reset drivers; no live saves.34Calls the installed instruction paths, with tiny return stubs after the tested5native slice. --baseline reports the old results without asserting the new ones.6Full gameplay/controller and shootout regressions remain separate suites.7"""8from pathlib import Path9import argparse,json,os,struct,subprocess10ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);ap.add_argument('--baseline',action='store_true');a=ap.parse_args()11here=Path(__file__).resolve().parent;out=a.output.resolve();out.mkdir(parents=True,exist_ok=True);reports=[]1213def run(name,body,size,stubs=()):14 p=out/name;p.mkdir(exist_ok=True)15 source='''    .text16    .globl _start17_start:18    move.w #0x2700,%sr19    movea.l #0xFFF800,%sp20    lea 0xFFF300,%a321    lea 0xFF4000,%a422'''+body+'''23    move.l #0x464F5255,0xFF3FF024end: bra.s end25'''26 (p/'driver.s').write_text(source);code=p/'driver-code.bin'27 subprocess.run([str(here.parent/'m68kasm.sh'),str(p/'driver.s'),'2FEA00',str(code)],check=True,stdout=subprocess.DEVNULL)28 d=bytearray(a.rom.read_bytes());b=code.read_bytes();assert len(b)<0x600 and d[0x2fea00:0x2fea00+len(b)]==b'\xff'*len(b)29 d[0x2fea00:0x2fea00+len(b)]=b;struct.pack_into('>I',d,4,0x2fea00)30 for addr in stubs:d[addr:addr+2]=bytes.fromhex('4e75')31 (p/'driver.bin').write_bytes(d);cmd=p/'commands.txt';cmd.write_text(f'1000 dump ff3ff0 4 done\n1000 dump ff4000 {size} result\n1001 exit\n')32 env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env['GPGX_DEV']='513'33 with (p/'log.txt').open('w') as log:subprocess.run(['nice','-n','19','python3',str(here/'test_core.py'),str(p/'driver.bin'),str(cmd),str(p)],env=env,check=True,stdout=log,stderr=log,timeout=30)34 rows={line.split()[1]:bytes.fromhex(' '.join(line.split()[2:])) for line in cmd.with_suffix('.out').read_text().splitlines()};assert rows['done']==b'FORU',(name,'driver failed to return')35 return rows['result']3637b=run('friction', '''38    moveq #0,%d739loop:40    move.b %d7,0x62(%a3)41    clr.w 0x18(%a3)42    move.w #8192,0x28(%a3)43    move.w #-8192,0x2A(%a3)44    jsr 0xAC1045    clr.w (%a4)46    move.b 0x62(%a3),1(%a4)47    addq.l #2,%a448    move.w %d2,(%a4)+49    move.w 0x28(%a3),(%a4)+50    move.w 0x2A(%a3),(%a4)+51    addq.w #1,%d752    cmp.w #256,%d753    bne.s loop54''',2048,[0xac46])55rows=list(struct.iter_unpack('>4H',b));errors=[]56for flags,(got,shift,vx,vy) in enumerate(rows):57 expected=flags&~1 if flags&8 else flags;sh=9 if expected&1 else 658 if (got,shift,vx,vy)!=(expected,sh,8192-(8192>>sh),(-8192-(-8192>>sh))&65535):errors.append(flags)59if not a.baseline:assert not errors,('friction',errors)60reports.append(dict(test='friction',cases=256,failures=len(errors),human_sliding_after_one_step=rows[9],cpu_glide_after_one_step=rows[1]))6162b=run('pass-flight', '''63    moveq #0,%d664outer:65    move.w %d6,0x34(%a3)66    moveq #0,%d767inner:68    move.w %d7,%d269    jsr 0xBF5470    move.w %d2,(%a4)+71    addq.w #1,%d772    cmp.w #41,%d773    bne.s inner74    addq.w #1,%d675    cmp.w #2,%d676    bne.s outer77''',164,[0xbf5e])78values=list(struct.unpack('>82H',b));expected=[max(3,min(t,24)+1) for t in range(41)]+[max(2,min(t,24)) for t in range(41)]79errors=sum(x!=y for x,y in zip(values,expected))80if not a.baseline:assert not errors,('passflight',values)81reports.append(dict(test='pass-flight',cases=82,failures=errors,goalie=values[:5],skater=values[41:46]))8283b=run('penalty-shot', '''84    moveq #0,%d785loop:86    move.b %d7,0x62(%a3)87    move.w #12345,0xFFB77288    move.w #-12345,0xFFB77489    move.w #200,0xFFB77690    jsr 0xED9691    move.w 0xFFB772,(%a4)+92    move.w 0xFFB774,(%a4)+93    move.w 0xFFB776,(%a4)+94    clr.w (%a4)95    move.b 0x62(%a3),1(%a4)96    addq.l #2,%a497    bset #1,0x62(%a3)98    jsr 0xEF9299    move.w %sr,(%a4)+100    clr.w (%a4)101    move.b 0x62(%a3),1(%a4)102    addq.l #2,%a4103    jsr 0xEF92104    move.w %sr,(%a4)+105    addq.w #1,%d7106    cmp.w #256,%d7107    bne.s loop108''',3584,[0xed9e,0xef98])109rows=list(struct.iter_unpack('>7H',b));errors=[]110for flags,(vx,vy,vz,award,sr,setup,sr2) in enumerate(rows):111 if (vx,vy,vz,award,setup)!=(0,0,0,flags|4,flags&~6) or sr&4 or not sr2&4:errors.append(flags)112if not a.baseline:assert not errors,('penalty-shot',errors)113reports.append(dict(test='penalty-shot',cases=256,failures=len(errors),award_and_setup=list(rows[0]),zero_flag_preserved=True))114115b=run('awareness', '''116    move.b #7,0x6A(%a3)117    move.b #29,0x6B(%a3)118    jsr 0xD0DE119    move.b 0x40(%a3),(%a4)+120    jsr 0xD2D6121    move.b 0x40(%a3),(%a4)+122''',2,[0xd0e4,0xd2dc])123if not a.baseline:assert b==bytes([7,7]),('awareness',b.hex())124reports.append(dict(test='offensive-awareness',cases=2,wing_and_center=list(b)))125126# Actual native scan and our fallback: the closest candidate is boxed (-1),127# another is available (+1). Capture the selected object, not a made-up ranking.128for native in [True,False]:129 b=run('native-receiver' if native else 'fallback-receiver','''130    lea 0xFFB04A,%a0131    moveq #0,%d0132    move.w #383,%d7133clear:134    move.w %d0,(%a0)+135    dbra %d7,clear136    lea 0xFFB04A,%a1137    moveq #5,%d3138init:139    move.w #-1,0x34(%a1)140    adda.w #128,%a1141    dbra %d3,init142    lea 0xFFB2CA,%a3143    clr.w 0x34(%a3)144    move.w #5,0x52(%a3)145    move.w #10,0xFFB04A146    move.w #50,0xFFB0CA147    move.w #1,0xFFB0FE148    clr.w 0xFFB74A149    clr.w 0xFFB75E150    move.w #2,0xFFBEDC151    clr.w 0xFFC2F2152    moveq #-1,%d4153    movea.l #0x1234,%a0154'''+('''155    lea 0xFFB04A,%a1156    moveq #5,%d3157    jsr 0xBCF8158''' if native else '    jsr 0x2FF180\n')+'''159    move.l %a0,(%a4)+160''',4,[0xbd64,0xbd6c])161 target=int.from_bytes(b,'big')&0xffffff162 if not a.baseline:assert target==0xffb0ca,('boxed receiver',native,hex(target))163 reports.append(dict(test='native-receiver' if native else 'fallback-receiver',selected=hex(target),boxed='0xffb04a',available='0xffb0ca'))164165# Native moving-target intercept and velocity arithmetic, not just the time clamp.166b=run('short-moving-passes', '''167    lea 0xFFB04A,%a3168    lea 0xFFB0CA,%a0169    move.w #1,0x34(%a3)170    move.w #180,0xFFBEDE171    moveq #50,%d6172outer:173    move.w #-7000,%d7174inner:175    move.w %d7,0x28(%a0)176    clr.w 0x2A(%a0)177    moveq #45,%d5178    bsr.s pass179    move.w 0xFFB772,(%a4)+180    move.w 0xFFB774,(%a4)+181    move.w 0xFFB776,(%a4)+182    subi.w #1000,%d7183    cmp.w #-10000,%d7184    bge.s inner185    addq.w #1,%d6186    cmp.w #70,%d6187    ble.s outer188    bra.s after189pass:190    clr.w -(%sp)191    move.w %d6,-(%sp)192    jmp 0xBEE8193after:194''',504)195passes=list(struct.iter_unpack('>hhH',b));peak=max(abs(x) for x,y,t in passes)196if not a.baseline:assert peak<=16000 and all(x>0 and y==0 for x,y,t in passes),('rocket-pass',peak,passes)197reports.append(dict(test='short-moving-passes',cases=len(passes),peak_velocity=peak,min_flight=min(t for x,y,t in passes),max_flight=max(t for x,y,t in passes)))198199(out/'results.json').write_text(json.dumps(reports,indent=2)+'\n');print(json.dumps(reports,indent=2));print('PASS native forum instruction-path checks' if not a.baseline else 'BASELINE recorded')200