-- Diagnostic stock-ROM experiment. Coin/start and move commands use ordinary -- inputs. The roster selection word, health, meter and positions are seeded -- before saving; both participants are human controlled. -- The ROM and move state are never patched; each trial restores the same save. local root = os.getenv('MASH_DIR') local move = os.getenv('MASH_MOVE') or 'qcf_pp' local char=tonumber(os.getenv('MASH_CHAR') or '32') local m = manager.machine local cpu = m.devices[':maincpu'] local mem = cpu.spaces.program local out = assert(io.open(root .. '/' .. move .. '.csv', 'w')) out:write('trial,frame,b0,state,hp1,hp2,held,pressed,budget,timer,child,child50,combo,x1,x2,y1,y2,human1,human2,e6,b6,b5,strength,mode_eb,subphase,lifetime,facingheld,facingpressed\n') local sampling=false local function r8(a) sampling=true;local v=mem:read_u8(a);sampling=false;return v end local function r16(a) sampling=true;local v=mem:read_u16(a);sampling=false;return v end local function w8(a,v) mem:write_u8(a,v) end local function w16(a,v) mem:write_u16(a,v) end local fields = {} for _,port in pairs(m.ioport.ports) do for name,f in pairs(port.fields) do fields[name]=f end end local function set(n) assert(fields[n],n):set_value(1) end local function clear() for name,f in pairs(fields) do if name:match('^P[12] ') or name:match('Start') or name:match('Coin') then f:set_value(0) end end end local modes={'none','none_repeat2','none_repeat3','hold_all','tap_all','tap_punch','tap_kick','directions','rotate','late_all'} local active=false local seen_attack=false local ended_attack=false local wanted=tonumber(os.getenv('MASH_SELECTOR') or '8') local seedx=tonumber(os.getenv('MASH_DISTANCE') or '45') local f,live,trial,t,loading=0,nil,0,0,0 local navpress,selected=nil,false _MASH_INPUT_TAP=mem:install_read_tap(0xff30d0,0xff30df,'input_reader',function(a,d,mask) if trial>0 and active and not sampling then print(string.format('INPUT mode=%s f=%d pc=%06x addr=%06x value=%04x mask=%04x',modes[trial],t,cpu.state.CURPC.value&0xffffff,a,d,mask)) end end) _MASH_STATE_TAP=mem:install_write_tap(0xff3000,0xff33ff,'state_writer',function(a,d,mask) if trial>0 and active and ((a>=0xff304c and a<=0xff3050) or a==0xff303e or a==0xff300a or a>=0xff3380 or a==0xff3120) then print(string.format('STATE mode=%s f=%d pc=%06x addr=%06x value=%04x mask=%04x',modes[trial],t,cpu.state.CURPC.value&0xffffff,a,d,mask)) end end) _MASH_EMIT_TAP=mem:install_write_tap(0xff0000,0xffffff,'child_allocation',function(a,d,mask) local pc=cpu.state.CURPC.value&0xffffff if trial>0 and (pc==0x9f442 or pc==0xa05de or pc==0xa05e4) then print(string.format('ALLOC mode=%s f=%d pc=%06x addr=%06x value=%04x mask=%04x',modes[trial],t,pc,a,d,mask)) end end) _MASH_NOTIFIER=emu.add_machine_frame_notifier(function() f=f+1; clear() if f%600==0 then print('FRAME '..f..' phase '..r16(0xff4104)) end if not live then if f>750 then w16(0xff40b0,char) end if f>600 and f%90<5 then set('Coin 1');set('Coin 2') end if f>750 and f%30<5 and r16(0xff4104)~=4 then set('1 Player Start');set('2 Players Start') end if r16(0xff4104)==4 then if f%24<4 then set('P2 Button 1') end if f%24==0 then navpress='P1 Button 1';selected=true print(string.format('NAV cursor=%x committed=%x press=%s',r16(0xff418c),r16(0xff40b0),navpress)) end if navpress and f%24<4 then set(navpress) end end if r16(0xff4104)==8 and r16(0xff4000)==0x0800 and r8(0xff3003)~=0 then if r16(0xff3052)~=char then print('FAIL wrong fighter '..r16(0xff3052));m:exit();return end;live=f; print(string.format('LIVE char=%x p2=%x',r16(0xff3052),r16(0xff3452))) end if f>5000 then print('FAIL no match');m:exit() end return end if trial==0 then if f-live==90 then w8(0xff3274,3);w16(0xff3272,0) w16(0xff3270,144);w16(0xff3670,144) w16(0xff300c,160);w16(0xff340c,160+seedx) end if f-live==120 then print(string.format('SAVE char=%02x target=%02x x1=%d x2=%d hp1=%d hp2=%d meter=%d',r16(0xff3052),r16(0xff3452),r16(0xff300c),r16(0xff340c),r16(0xff3270),r16(0xff3670),r8(0xff3274)));m:save(root..'/'..move..'.sta') end if f-live==125 then trial=1;t=0;m:load(root..'/'..move..'.sta');loading=4 end return end if loading>0 then loading=loading-1;return end t=t+1 if t>=1 and t<=4 then set(move:match('dp_') and 'P1 Right' or 'P1 Down') end if t>=5 and t<=8 then set('P1 Down');if not move:match('dp_') then set(move:match('qcb') and 'P1 Left' or 'P1 Right') end end if t>=9 and t<=12 then set(move:match('qcb') and 'P1 Left' or 'P1 Right');if move:match('dp_') then set('P1 Down') end if move:match('_kk') then set('P1 Button 4');set('P1 Button 5') elseif move:match('_pp') then set('P1 Button 1');set('P1 Button 2') else set('P1 Button 1') end end if r8(0xff30b0)==0x2a and r8(0xff30e6)==wanted and not ended_attack then active=true;seen_attack=true elseif active then active=false;ended_attack=true;print(string.format('END_ATTACK mode=%s f=%d',modes[trial],t)) end if t>12 and t<700 and active then local mode=modes[trial] if mode=='hold_all' or (mode=='tap_all' and t%2==0) then for j=1,6 do set('P1 Button '..j) end elseif mode=='tap_punch' and t%2==0 then for j=1,3 do set('P1 Button '..j) end elseif mode=='tap_kick' and t%2==0 then for j=4,6 do set('P1 Button '..j) end elseif mode=='rotate' then set('P1 '..({'Right','Down','Left','Up'})[math.floor(t/2)%4+1]) elseif mode=='directions' and t%2==0 then set('P1 '..({'Right','Down','Left','Up'})[math.floor(t/2)%4+1]) end end if modes[trial]=='late_all' and t>=650 and t<700 and t%2==0 then for j=1,6 do set('P1 Button '..j) end end local child=mem:read_u32(0xff3392)&0xffffff local cv=(child>=0xff0000 and child<0xffff00) and r16(child+0x50) or 0 out:write(table.concat({modes[trial],t,r8(0xff30b0),r16(0xff3008),r16(0xff3270),r16(0xff3670),r16(0xff30d0),r16(0xff30d4),r16(0xff3398),r16(0xff304e),child,cv,r8(0xff3120),r16(0xff300c),r16(0xff340c),r16(0xff3010),r16(0xff3410),r8(0xff3003),r8(0xff3403),r8(0xff30e6),r8(0xff30b6),r8(0xff30b5),r8(0xff304d),r8(0xff30eb),r16(0xff33a4),r16(0xff339e),r16(0xff30d6),r16(0xff30da)},',')..'\n') if t==25 or t==95 or t==200 or t==350 then m.screens[':screen']:snapshot(root..'/'..move..'_'..modes[trial]..'_'..t..'.png') end if t>=900 then out:flush();print(string.format("TRIAL_DONE mode=%s seen=%s ended=%s",modes[trial],tostring(seen_attack),tostring(ended_attack)));trial=trial+1;t=0;active=false;seen_attack=false;ended_attack=false if trial>#modes then out:close();print('DONE');m:exit();return end m:load(root..'/'..move..'.sta');loading=4 end end)