EDIT: Apparently the problem was a bad rom version, it still seems strange to me that it could occur on any rom unless the bad rom was accessing something in the emulator it should not have been.
No further issues, but still an interesting predicament.
When brute forcing an outcome by inserting idle frames in various locations, the RNG result obtained while using a script does not sync with the emulated result of the same input. There is a possibility I just suck at coding, but it really seems to be a functionality or game related issue.
I was recently contacted for some assistance in lua scripting the first portion of
Pokemon Red, where idle frames need to be inserted in various locations in order to make the Trainer ID Low Byte $76 for a glitch.
I wrote the script, and I have checked the input thoroughly to see that it seems doing exactly what I want it to.
If I manually interrupt the lua script during an outcome and play the movie back, it does not sync. The bytes on the screen do not match what they do when I make the movie by inserting waits manually, and I get a desync.
I have done scripting similar to this for Megaman Battle Network and never had anything like this occur. Could it be a problem with lua not accurately saving/loading factors relating to the sporadic RNG of this game?
The following is the optimal input:
-Wait1
356:A
-Wait2 (waiting one or more frames here will make the game lag for 1 frame on the next input)
376:A
-Wait3
560:A
677:down
-Wait4
680:A
704:left
705:down
706:right
707:down
708:right
709:B
-Wait5
766:A
The script is currently setup to place the savestate on
frame zero. If the savestate is moved to another location, other incorrect values are achieved.
The first iteration of no wait seems accurate all (most of?) the time. Any other value based on the loaded state is wrong.
I have made it output to a file and display on the screen for convenience. The first four results should be 92, 51, 27, 23. Beyond 27 does not sync with the savestate in this position.
This thread is not about how mediocre my coding skills are. My code is probably inefficiently written and much overstated. I did my best to make it presentable. I had the script running without so much beginning/end time, but it's easier to troubleshoot when watching this way. If there are any errors in my code causing problems please let me know, but I did do my best to troubleshoot any problems that could be occurring.
Pokemon Red (Super Gameboy) starting from frame 0:
local tempstate = {}
local Maxframewait --Most frames to wait.
local value
local i
local Currentframewait -- Frames waited by current loop.
local Bestframewait -- Frames waited by best solution so far.
local TrainerIDLowByte
local wait1
local wait2
local wait3
local wait4
local wait5
moviefile = "FrameDump.vbm" --Output frame counts to a small text file.
dumpfile = moviefile..".dump"
io.output(dumpfile)
value = 0
tempstate[value] = savestate.create() --tempstate0 = base state
value = 1
tempstate[value] = savestate.create() --tempstate1 = optimal state
value = 0
savestate.save(tempstate[value])
value = 1
savestate.save(tempstate[value])
-------------------
TrainerIDLowByte = 0xD35A
Maxframewait = 3 -- change this to run longer. If you go much higher than 10 or so, the script may take quite a while.
Bestframewait = 1000 --arbitrary high number, just used as a comparison to start with.
-------------------
for wait1 = 0,Maxframewait do
for wait2 = 0,Maxframewait do
for wait3 = 0,Maxframewait do
for wait4 = 0,Maxframewait do
for wait5 = 0,Maxframewait do
Currentframewait = (wait1 + wait2 + wait3 + wait4 + wait5)
if wait2 > 0 then
Currentframewait = Currentframewait + 1 --There is a lag frame if wait2>0
end
if Currentframewait <= Maxframewait then
-- Lots of arbitrary loops that get skipped
value = 0
savestate.load(tempstate[value])
for i=1,351 do
vba.frameadvance()
end
vba.frameadvance()
vba.frameadvance()
vba.frameadvance()
vba.frameadvance()
----Commands go here-----------
for i=0,wait1 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true}) --356
for i=1,19 do
vba.frameadvance()
end
for i=0,wait2 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true}) --376
if wait2>0 then --There is a lag frame if wait2>0
vba.frameadvance()
end
for i=1,183 do
vba.frameadvance()
end
for i=0,wait3 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true}) --560
for i=1,117 do
vba.frameadvance()
end
joypad.set(1,{["down"]=true}) --677
for i=1,2 do
vba.frameadvance()
end
for i=0,wait4 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true}) --680
for i=1,24 do
vba.frameadvance()
end
joypad.set(1,{["left"]=true})
vba.frameadvance()
joypad.set(1,{["down"]=true})
vba.frameadvance()
joypad.set(1,{["right"]=true})
vba.frameadvance()
joypad.set(1,{["down"]=true})
vba.frameadvance()
joypad.set(1,{["right"]=true})
vba.frameadvance()
joypad.set(1,{["B"]=true}) --709
vba.frameadvance()
for i=1,55 do
vba.frameadvance()
gui.text(0,0,"Best=" .. Bestframewait)
end
for i=0,wait5 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true})
for i=1,200 do
vba.frameadvance()
gui.text(0,0,"ID=" .. memory.readbyte(TrainerIDLowByte))
end
------------------------------------------------
io.write(wait1)
io.write(" ")
io.write(wait2)
io.write(" ")
io.write(wait3)
io.write(" ")
io.write(wait4)
io.write(" ")
io.write(wait5)
io.write(" ")
io.write(memory.readbyte(TrainerIDLowByte))
io.write("\n")
if memory.readbyte(TrainerIDLowByte) == 118 then --$76 converted to decimal
--io.write(wait1, " ", wait2, " ", wait3, " ", wait4, " ", wait5, "\n")
if Currentframewait < Bestframewait then
Bestframewait = Currentframewait
value = 1
savestate.save(tempstate[value])
end
end
end --End "if Currentframewait <= Maxframewait"
end
end
end
end
end
value = 1
savestate.load(tempstate[value])
gui.text(0,0,"Best Wait " .. Bestframewait)
vba.pause()
---
---
---
(This is now being used as Kirkq's code dump.)
--Start on frame 31872
local tempstate = {}
local value
local BossHP = 0x7E2287
local TotalHP = 465
local Break
local Success
local a
local b
local x
local y
local st
local se
local u
local d
local l
local r
local lb
local rb
local at1 --temp variables
local bt1
local xt1
local yt1
local stt1
local set1
local ut1
local dt1
local lt1
local rt1
local lbt1
local rbt1
local at2
local bt2
local xt2
local yt2
local stt2
local set2
local ut2
local dt2
local lt2
local rt2
local lbt2
local rbt2
---------------------
moviefile = "FrameDump.smv" --I'm going to have it output frame counts to a small text file.
dumpfile = moviefile..".dump"
io.output(dumpfile)
-- Makes a text file to output data.
value = 0
tempstate[value] = savestate.create() --tempstate0 = base state
value = 1
tempstate[value] = savestate.create() --tempstate1 = optimal state
value = 0
savestate.save(tempstate[value])
value = 1
savestate.save(tempstate[value])
Success = 0
snes9x.speedmode("maximum")
snes9x.pause()
---------------------
for st=1,3 do --0 and 2 seem to do the same thing
for se=1,3 do
for lb=1,3 do
for rb=1,3 do
for u=1,3 do
for d=1,3 do
for l=1,3 do
for r=1,3 do
for a=1,3 do
for b=1,3 do
for x=1,3 do
for y=1,3 do
value = 0
savestate.load(tempstate[value])
--00
--01
--10
--11
if a == 0 then
at1 = nil
at2 = nil
elseif a == 1 then
at1 = nil
at2 = true
elseif a == 2 then
at1 = true
at2 = nil
elseif a == 3 then
at1 = true
at2 = true
end
if b == 0 then
bt1 = nil
bt2 = nil
elseif b == 1 then
bt1 = nil
bt2 = true
elseif b == 2 then
bt1 = true
bt2 = nil
elseif b == 3 then
bt1 = true
bt2 = true
end
if x == 0 then
xt1 = nil
xt2 = nil
elseif x == 1 then
xt1 = nil
xt2 = true
elseif x == 2 then
xt1 = true
xt2 = nil
elseif x == 3 then
xt1 = true
xt2 = true
end
if y == 0 then
yt1 = nil
yt2 = nil
elseif y == 1 then
yt1 = nil
yt2 = true
elseif y == 2 then
yt1 = true
yt2 = nil
elseif y == 3 then
yt1 = true
yt2 = true
end
if stt == 0 then
stt1 = nil
stt2 = nil
elseif st == 1 then
stt1 = nil
stt2 = true
elseif st == 2 then
stt1 = true
stt2 = nil
elseif st == 3 then
stt1 = true
stt2 = true
end
if se == 0 then
set1 = nil
set2 = nil
elseif y == 1 then
set1 = nil
set2 = true
elseif se == 2 then
set1 = true
set2 = nil
elseif se == 3 then
set1 = true
set2 = true
end
if u == 0 then
ut1 = nil
ut2 = nil
elseif u == 1 then
ut1 = nil
ut2 = true
elseif u == 2 then
ut1 = true
ut2 = nil
elseif u == 3 then
ut1 = true
ut2 = true
end
if d == 0 then
dt1 = nil
dt2 = nil
elseif d == 1 then
dt1 = nil
dt2 = true
elseif d == 2 then
dt1 = true
dt2 = nil
elseif d == 3 then
dt1 = true
dt2 = true
end
if l == 0 then
lt1 = nil
lt2 = nil
elseif l == 1 then
lt1 = nil
lt2 = true
elseif l == 2 then
lt1 = true
lt2 = nil
elseif l == 3 then
lt1 = true
lt2 = true
end
if r == 0 then
rt1 = nil
rt2 = nil
elseif r == 1 then
rt1 = nil
rt2 = true
elseif r == 2 then
rt1 = true
rt2 = nil
elseif r == 3 then
rt1 = true
rt2 = true
end
if lb == 0 then
lbt1 = nil
lbt2 = nil
elseif lb == 1 then
lbt1 = nil
lbt2 = true
elseif lb == 2 then
lbt1 = true
lbt2 = nil
elseif lb == 3 then
lbt1 = true
lbt2 = true
end
if rb == 0 then
rbt1 = nil
rbt2 = nil
elseif rb == 1 then
rbt1 = nil
rbt2 = true
elseif rb == 2 then
rbt1 = true
rbt2 = nil
elseif rb == 3 then
rbt1 = true
rbt2 = true
end
joypad.set(1,{["A"]=at1,["B"]=bt1,["X"]=xt1,["Y"]=yt1,["start"]=stt1,["select"]=set1,["up"]=ut1,["down"]=dt1,["left"]=lt1,["right"]=rt1,["L"]=lbt1,["R"]=rbt1})
snes9x.frameadvance();
joypad.set(1,{["A"]=at2,["B"]=bt2,["X"]=xt2,["Y"]=yt2,["start"]=stt2,["select"]=set2,["up"]=ut2,["down"]=dt2,["left"]=lt2,["right"]=rt2,["L"]=lbt2,["R"]=rbt2})
snes9x.frameadvance();
snes9x.frameadvance();
------2 Button presses are complete.
-----Start Turn 1
Break = 0
for i=1,131 do
snes9x.frameadvance()
end
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
snes9x.frameadvance()
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
for i=1,43 do
snes9x.frameadvance()
end
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
snes9x.frameadvance()
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
for i=1,160 do
snes9x.frameadvance()
end
if memory.readword(BossHP) > TotalHP - 130 then
Break = 1
end
--Turn 2
if Break == 0 then
for i=1,209 do
snes9x.frameadvance()
end
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
snes9x.frameadvance()
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
for i=1,45 do
snes9x.frameadvance()
end
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
snes9x.frameadvance()
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
for i=1,230 do
snes9x.frameadvance()
end
if memory.readword(BossHP) > TotalHP - 260 then
Break = 2
end
end
if Break == 0 then
for i=1,27 do
snes9x.frameadvance()
end
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
snes9x.frameadvance()
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
for i=1,137 do
snes9x.frameadvance()
end
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
snes9x.frameadvance()
joypad.set(1,{["A"]=true})
snes9x.frameadvance()
for i=1,250 do
snes9x.frameadvance()
gui.text(0,0,"HP: " .. memory.readbyte(BossHP))
gui.text(0,20,"Success: " .. Success)
end
end
io.write(Break," ",memory.readword(BossHP)," ",a,b,x,y,st,se,u,d,l,r,lb,rb, "\n")
if memory.readword(BossHP) == 0 then
value = 1
savestate.save(tempstate[value])
Success = 1
end
---------------------
end --loop ends
end
end
end
end
end
end
end
end
end
end
end
value= 1
savestate.load(tempstate[value])
snes9x.pause()
local tempstate = {}
local Maxframewait --Most frames to wait.
local value
local i
local Currentframewait -- Frames waited by current loop.
local Bestframewait -- Frames waited by best solution so far.
local TrainerIDLowByte
local wait1
local wait2
local wait3
local HoldTrigger
local HoldTime
moviefile = "FrameDump.vbm" --I'm going to have it output frame counts to a small text file.
dumpfile = moviefile..".dump"
io.output(dumpfile)
-- Makes a text file.
value = 0
tempstate[value] = savestate.create() --tempstate0 = base state
value = 1
tempstate[value] = savestate.create() --tempstate1 = optimal state
value = 0
savestate.save(tempstate[value])
value = 1
savestate.save(tempstate[value])
-------------------
EnemyHPAddress = 0xCFE7 --Updated
HoldTime = 15 --Time to hold A button for each alternate attempt.
Maxframewait = 16 -- If you go much higher than 20 or so, the script may take quite a while.
Bestframewait = 1000 --arbitrary high number, just used as a comparison to start with.
GoalHP = 0
-------------------
vba.pause()
--Start this on the exact frame 1 before you can hit A for "but it failed"
for wait1 = 0,Maxframewait do
for wait2 = 0,Maxframewait do
for wait3 = 0,Maxframewait do
for HoldTrigger = 0,3 do -- 0 = Just waits 1 = Hold 2 = Lag Mash 3 = Lag Mash and Hold
--You can change that to for HoldTrigger = 1,3 1,2 0,2 or whatever to not repeat searches you've already done.
Currentframewait = (wait1 + wait2 + wait3)
if Currentframewait < Bestframewait then
if Currentframewait <= Maxframewait then
-- Lots of arbitrary loops that get skipped
value = 0
savestate.load(tempstate[value])
----Commands go here-----------
for i=0,wait1 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true}) --but it failed
for i=1,8 do
vba.frameadvance()
end
for i=0,wait2 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true}) --click fight
for i=1,7 do
vba.frameadvance()
end
for i=0,wait3 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true}) --select attack
if HoldTrigger == 1 then
for i=1,HoldTime do
joypad.set(1,{["A"]=true})
vba.frameadvance()
end
elseif HoldTrigger >= 2 then
if Currentframewait < (Bestframewait - 2) then
if Currentframewait <= (Maxframewait-2) then
for i=1,7 do
vba.frameadvance()
end
joypad.set(1,{["A"]=true})
if HoldTrigger == 3 then
for i=1,HoldTime do
joypad.set(1,{["A"]=true})
vba.frameadvance()
end
end
Currentframewait = Currentframewait + 2
end
end
end
for i=1,150 do
vba.frameadvance()
gui.text(0,0,"HP=" .. memory.readbyte(EnemyHPAddress))
gui.text(0,20,"Best=" .. Bestframewait)
gui.text(0,10,wait1)
gui.text(10,10,wait2)
gui.text(20,10,wait3)
gui.text(30,10,HoldTrigger)
end
------------------------------------------------
io.write(wait1)
io.write(" ")
io.write(wait2)
io.write(" ")
io.write(wait3)
io.write(" ")
io.write(HoldTrigger)
io.write(" ")
io.write(memory.readbyte(EnemyHPAddress))
io.write("\n")
if memory.readword(EnemyHPAddress) == GoalHP then
--io.write(wait1, " ", wait2, " ", wait3, " ", wait4, " ", wait5, "\n")
if Currentframewait < Bestframewait then
Bestframewait = Currentframewait
value = 1
savestate.save(tempstate[value])
end
end
end --End "if Currentframewait <= Maxframewait"
end
end
end
end
end
value = 1
savestate.load(tempstate[value])
gui.text(0,20,"Best Wait " .. Bestframewait)
gui.text(0,0,"Enemy HP=" .. memory.readbyte(EnemyHPAddress))
vba.pause()