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