1 2
16 17 18
24 25
Skilled player (1305)
Joined: 9/7/2007
Posts: 1354
Location: U.S.
What value is the camera position exactly GlitchMan? I think that you might be onto something.
Experienced player (620)
Joined: 8/28/2008
Posts: 443
Sonikkustar wrote:
What value is the camera position exactly GlitchMan? I think that you might be onto something.
It is located at $FFF700. It is also displayed when using the in-game debug mode at the top left corner where your score is supposed to be. I also found something else in this game that could be useful. When Sonic has 32 or more rings and he gets hit by something, the sprite limit from the rings that spilled can be used to stop other sprites from appearing that could get in your way. The ring count is located at $FFFE20. You can make Sonic have any number of rings you want and make Sonic lose rings where sprites would normally appear and not show up. You could play around with it a bit and see what you come up with.
Experienced player (620)
Joined: 8/28/2008
Posts: 443
I found a shortcut in Star Light Zone Act 1 where Sonic skips the entire act and beats it in 17 seconds (I saved 5 seconds (300 frames) off the published run): http://dehacked.2y.net/microstorage.php/info/1490665219/GlitchMan-Sonic%20the%20Hedgehog%20%28J%29-StarLightZoneAct1shortcut.gmv
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
Is this game hex-friendly? I might go on some frame hunting in the earlier levels when I have the time and mood.
feos wrote:
Only Aglar can improve this now.
Mitjitsu
He/Him
Banned User, Experienced player (531)
Joined: 4/24/2006
Posts: 2997
Aglar wrote:
Is this game hex-friendly?
It certainly is.
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
I would say that up to Labyrinth zone the game is hex friendly; then, after Labyrinth zone (but before Scrap Brain zone 3), the game is hex friendly again; then Final zone is hex friendly. Labyrinth zone and Scrap Brain zone 3 have water, and this causes differences in lag that can cause desynchs. If you are using my Sonic HUD, then I can help in this regard -- I have a script for saving/pasting levels (which I only tested in Sonic 1 and hacks) which does all of the lag management, but it depends on files from the Sonic HUD for its UI. Edit: note: the UI for the resyncher conflicts with part of the UI of the HUD itself, so it is best not to use both at the same time.
Marzo Junior
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
Tasing this game is quite addicting, but also tedious in its places. My times for the first world are:
Green Hill 1: 0:24:25 - Some different jumps and faster loop
Green Hill 2: 0:13:07 - New setup for the zipping (my first zip in a Sonic game ever)
Green Hill 3: 0:30:45 - Time gained at the boss, in order to deliver the final blow as fast as possible your y-subpixel value, when landing on the platform, must be < 80
http://dehacked.2y.net/microstorage.php/info/1263748122/Sonic%20The%20Hedgehog%20%28W%29%20%28REV%2001%29%20Aglar_world1.gmv Thanks to Sonikkustar for the his previous work that provided great help! Apparently, it's random how the animals that you set free are jumping and I lost some time here. Would the priority be to beat the level as fast as possible or wait some frames before hitting the button to get the best randomness? I like the former best (even if it's not enough for gaining an extra clock-second) as it's more consistent with the in-game time goal for the run.
feos wrote:
Only Aglar can improve this now.
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
You can also add small pauses to manipulate the animals; randomness there (the direction where each animal faces) is determined by how many times the v-int code has executed when the animal lands on ground. You want to watch the byte at $FFFFFE0F; if bit 4 is set (bits go 0-7), then the animal will go right; otherwise, it goes left. In your case, the problem comes from a pair of bunnies at the end that go to the wrong direction and cost you time. I will be writing a script to optimize it; but on your case, if you do a pause on frame 7481->7482 and release on 7485->7486, then pause again on 7730->7731 and release on 7732->7733, the bunnies will turn on a better way and the end of act will trigger 14 frames earlier. There may be a better combination, I don't know yet. Edit: I almost forgot: that is a nice job there. You made me smile in a couple points.
Marzo Junior
Skilled player (1305)
Joined: 9/7/2007
Posts: 1354
Location: U.S.
Thats pretty awesome Aglar, but it also kinda demotivates me to finish my run now unfourtunately. :( Anyways, nice work!
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Here is the script. It is not terribly optimized, and does a lot by chance; it also does not save its history, so you will want to leave it running as long as you can afford -- for example, overnight. That said, it is a suitable baseline for a better script :-) There are two important parameters: "maxpauses" and "counter". "maxpauses" control how many pauses you want, and ranges from 1 to 16 in the script. Each pause can last from 2 to 16 frames, randomly chosen. "counter" controls how many tries with that maximum number of pauses before incrementing the number of pauses. I arbitrarily set "counter" to range from 0 to 256 (256 tries), but you can tweak that. At any point, you can press stop and it will load the best state found so far (i.e., the one where the animals are all off-screen at the earliest frame). Since it doesn't save its history, it is best to leave it running overnight and see what it came out with. Download optimize-animals.lua
Language: lua

local init_state = savestate.create() local best_state = savestate.create() local init_frame = movie.framecount() local best_frame = 0 -- Save initial state savestate.save(init_state) local iter = 29 local function redraw(msg) iter = iter + 1 if iter == 30 then iter = 0 gui.drawtext(80, 100, msg) gens.redraw() end end local function find_animals() for addr=0xffd040,0xffd040+0x3f*0x40,0x40 do if memory.readbyte(addr)==0x28 then return true end end return false end local function enum_animals() local animals = {} for addr=0xffd040,0xffd040+0x3f*0x40,0x40 do if memory.readbyte(addr)==0x28 then table.insert(animals, addr) end end return animals end local function check_animal_presence(list) local remove_queue = {} for i,m in pairs(list) do if memory.readbyte(m)==0 then table.insert(remove_queue, 1, i) end end local todel = remove_queue[1] while todel do table.remove(remove_queue, 1) table.remove(list, todel) todel = remove_queue[1] end return list end local function check_animal_states(list) for i,m in pairs(list) do local rout = memory.readbyte(m+0x24) if rout==0x12 or rout<=2 then return true end end return false end -- Estabilish baseline -- First, wait for animals to appear while true do gens.emulateframeinvisible() redraw("Baseline: Waiting for animals to appear...") if find_animals() then break end end gens.redraw() local animals = {} -- Now, wait for them to land while true do gens.emulateframeinvisible() redraw("Baseline: Waiting for animals to land...") animals = enum_animals() if not check_animal_states(animals) then break end end -- Now, wait for them to go all away while true do gens.emulateframeinvisible() redraw("Baseline: Waiting for animals to go away...") animals = check_animal_presence(animals) if #animals == 0 then break end end gens.redraw() -- Save baseline (current best) best_frame = movie.framecount() savestate.save(best_state) gens.registerexit(function() savestate.load(best_state) gens.emulateframe() savestate.load(best_state) gens.redraw() end) -- Rewind savestate.load(init_state) local npauses = 0 local pause_seed = 0 local pause_len = 2 local last_pause = init_frame local function add_pause(count, max) if npauses >= max then return end if pause_len ~= movie.framecount()-last_pause then return end joypad.write(1,{start=true}) for i=0,pause_len do gens.emulateframeinvisible() joypad.write(1,{start=false}) end joypad.write(1,{start=true}) gens.emulateframeinvisible() joypad.write(1,{start=false}) npauses = npauses+1 pause_seed = math.random(0,math.floor(256/max)) pause_len = math.random(2,16) last_pause = movie.framecount() end for maxpauses=1,16 do local animals = {} local state = 0 -- 0: waiting for animals to spawn; 1: waiting for animals to land; 2: waiting for animals to go away for counter=0,256 do npauses = 0 last_pause = init_frame local msg = string.format("Please wait... 0x%04X", counter) add_pause(counter, maxpauses) -- First, wait for animals to appear while state == 0 do gens.emulateframeinvisible() redraw(msg) if find_animals() then state = 1 break end add_pause(counter, maxpauses) end -- Now, wait for them to land while state == 1 do gens.emulateframeinvisible() redraw(msg) animals = enum_animals() if not check_animal_states(animals) then state = 2 break end add_pause(counter, maxpauses) end -- Now, wait for them to go all away while state == 2 do gens.emulateframeinvisible() redraw(msg) animals = check_animal_presence(animals) if #animals == 0 then state = 0 break end end gens.redraw() if best_frame > movie.framecount() then -- Save best best_frame = movie.framecount() savestate.save(best_state) end -- Rewind savestate.load(init_state) end end savestate.load(best_state)
Marzo Junior
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
Sonikkustar wrote:
Thats pretty awesome Aglar, but it also kinda demotivates me to finish my run now unfourtunately. :( Anyways, nice work!
Of course I didn't intend stealing the run away from you, only to see if I could optimize some movements a little further - which eventually could be hexed into your run. But when one level was done, another one easily got done as well and now I'm actually somewhat interested in completing the run. Hope you're ok with that, otherwise we can discuss it over PM. Regarding the script, I don't know if I'm just untalented but I couldn't get it to fully work. I started the script at frame 7479 (1 frame before hitting the button) and let it run for like 5 minutes (just for test), then I stopped it and played back the run. When it got to the animal situation, the pause button was used for manipulation (which shouldn't be the case I guess) and the animals left the screen much later than what was shown after I'd stopped the script.
feos wrote:
Only Aglar can improve this now.
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
The script does use pause for manipulating the animals, and you started from about the place intended (which I now realize I forgot to mention... sorry). What is odd is that I have built-in checks to use the best time -- if that is without any pauses, then that is what the script should eventually use. Out of curiosity: what version of Gens are you using (just to rule out differences in Lua)? In any case, I will go about the code and see if I spot any mistakes. Also, something to try, just to be sure: after running the script, and while recording, could you try doing a manual savestate, advancing one frame then loading this state (another just in case)?
Marzo Junior
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
marzojr wrote:
Out of curiosity: what version of Gens are you using (just to rule out differences in Lua)?
11b
marzojr wrote:
Also, something to try, just to be sure: after running the script, and while recording, could you try doing a manual savestate, advancing one frame then loading this state (another just in case)?
Yeah, the problem remains. I improved GHZ2 by 2 frames so if you want to do some testings that should be performed on this gmv: http://dehacked.2y.net/microstorage.php/info/1214893835/Sonic%20The%20Hedgehog%20%28W%29%20%28REV%2001%29%20Act2improved.gmv - here I loaded the "best time" but you can see that they leave long after that. (Rerecords became a little to high when I hexed in the rest, I really only used 2/3 of that) And about the testing algorithm itself: What really (pretty much always) determines the final animal time are the last couple of bunnys. Ideally the last 4 (maybe 5) bunnys should jump out of one of the edges of the cage and then of course leave the screen in the direction where the distance to the edge of the screen is the shortest. Maybe you can adjust the script to set more priority of this?
feos wrote:
Only Aglar can improve this now.
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
So, I decided to go over the code and I found out a few things that may help. Manipulating early enough can alter the explosion pattern (if the last 3 bits of $FFFFFE0F are not zero, no explosion object is loaded*); otherwise, 1 explosion is loaded that frame. Each explosion loaded affects the RNG (used once per explosion to determine position of the explosion). This goes on as long as byte $FFD8A4** is $A. After 60 frames generating explosions, 8 animals will be generated in preset positions. These animals will start jumping 98 frames after this, and each will jump 8 frames after the other. Byte $FFD8A4** will change to $C and a 150 frame timer will be set. Edit: each of these animals use the RNG once to determine what type of animal they will be. During this time, animals are generated quasi-randomly: if the last 3 bits of $FFFFFE0F are not zero, no animal is loaded***; otherwise, 1 animal will be loaded that frame. Each animal uses the RNG once to determine its position, then once more to determine the type of animal. Each animal generated in this phase will jump 12 frames after being created. After the 150-frame timer expires, byte $FFD8A4** becomes $E and the capsule waits until all animals are offscreen. When it does, $FFD8A4** becomes 0 as the object checking it is deleted. Finally, as each animal land, byte bit 4 of $FFFFFE0F determines the direction that the animal goes, as I said earlier. So yeah, there is quite a bit to manipulate: you can turn the last bunnies into birds or even prevent them from loading with pauses in the right place. * Yes, it is possible to cause the capsule to explode without any explosions; it just suddenly appears broken after some time. ** This may change in different levels, because the object is dynamically loaded. If you use the Gens 11b with the Sonic TAS tools, turning numlock on will show the object's base address. In this case, you will look for the switch's address; it will show as something like $D880. Add $24 to it and put it in a RAM watch as $FFD8A4 to have the address to watch. While at it, the word at offset $1E from the object's RAM is the timer used between phases. *** It is possible to get down to 8 animals only, but this isn't all that useful -- most of them will get out much earlier than those generated afterward. But you can prevent the later ones from spawning; any bunnies that are created when the capsule timer is 142 or more are a waste.
Marzo Junior
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
Now I wonder what to do. It would suck to optimize animal cage situation right now and later in the run find out that one of the early levels can be improved so it must be manipulated again. The best thing would be to TAS all levels of the game and take care of all of them at the end. However since the water levels had some lag issues it might be troublesome as well. TASing the first 3 worlds before taking care of the cages shouldn't be any problem though I guess.
feos wrote:
Only Aglar can improve this now.
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
I am working on a better script that, instead of brute forcing, predicts what the animals are going to be given the current RNG. Right now, the script is limited to the initial 8 animals generated; I am still writing code to predict what each of the subsequent animals will be as they come. At its current state (i.e., only the initial 8 birds) I was already able to remove 32 frames (real time) with a few pauses by just picking the pauses that let me have the list with the highest proportion of birds in the slots with highest delay.
Marzo Junior
Patashu
He/Him
Joined: 10/2/2005
Posts: 4000
I thought the sonic run focused on in-game time over real time, as evidenced by ignoring the huge amount of time tallying up a time bonus takes. Why can't we ignore this too?
My Chiptune music, made in Famitracker: http://soundcloud.com/patashu My twitch. I stream mostly shmups & rhythm games http://twitch.tv/patashu My youtube, again shmups and rhythm games and misc stuff: http://youtube.com/user/patashu
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Patashu wrote:
I thought the sonic run focused on in-game time over real time, as evidenced by ignoring the huge amount of time tallying up a time bonus takes. Why can't we ignore this too?
Because it looks unpolished :-) Besides, the primary focus is in-game time, but that does not mean that real time can just be ignored; this is a TAS after all. Anyway: the predictor script is finished. Just load it when getting near a capsule (preferably just before hitting it). It displays the sequence of animals with timers until they are released; the information is only displayed until you can no longer change it anymore. The faster an animal is, the greener is his color; the slower said animal is, the redder. This is calibrated so that flickies (the fastest of them all) are full green while seals (the slowest of them all) are fully red. In yellow is a timer; it says how many frames before you can have an effect on the animal pattern (until the explosions are finished) or whether or not an animal will spawn (after the explosions are done). When the timer reads 1, you hold the start key and do a frame advance; then advance one frame without pressing start and then advance another frame unpausing. This will give a new pattern (if done while the explosions are still happening) or will delay the next animal spawn (delaying enough animal spawns will prevent the spawns; so if you get a near-perfect pattern except that it ends on a slow animal, you can force it not to spawn). You want to get fast animals (generally birds) in the last places in both lists. This generally more than makes up for the pauses that it takes. You can still try to manipulate the way an animal will go when he lands by adding pauses; this script does not cover this. You can manipulate this with pauses; it may take anywhere from 1 to 14 frames in a pause to convince an animal to change the direction he goes -- and it has to be done before he lands. Here is the script: Download predict-animals.lua
Language: lua

-- Function to find the capsule'e switch local function find_capsule() for addr=0xffd040,0xffd040+0x3f*0x40,0x40 do if memory.readbyte(addr)==0x3e and memory.readbyte(addr+0x24) > 2 then return addr end end return nil end -- Names for animals. local animals = {[0]={"Rabbit" ,{146, 109, 0, 255}}, [1]={"Chicken" ,{146, 109, 0, 255}}, [2]={"Eagle" ,{219, 36, 0, 255}}, [3]={"Seal" ,{255, 0, 0, 255}}, [4]={"Pig" ,{183, 72, 0, 255}}, [5]={"Flicky" ,{ 0, 255, 0, 255}}, [6]={"Squirrel",{ 73, 182, 0, 255}}} -- Map between zone ID and animal names. local typelist = {[0]={[0]=0, [1]=5}, [1]={[0]=2, [1]=3}, [2]={[0]=6, [1]=3}, [3]={[0]=4, [1]=5}, [4]={[0]=4, [1]=1}, [5]={[0]=0, [1]=1}} -- Adddress of capsule switch in RAM. local capsule_switch = nil -- A few important RAM locations. local v_random = 0xFFFFF636 local v_zone = 0xFFFFFE10 local v_vbla_byte = 0xFFFFFE0F -- Internal variables. local seed = 0 local last_seed = -1 local last_vbla = -1 -- Lua version of the Sonic 1 random number generator. local function GetRandom() local d1 = seed or 0x2A6D365A local d0 = d1 -- move.l d1,d0 d1 = SHIFT(d1, -2) -- asl.l #2,d1 d1 = d1 + d0 -- add.l d0,d1 d1 = SHIFT(d1, -3) -- asl.l #3,d1 d1 = d1 + d0 -- add.l d0,d1 d0 = OR(AND(d1, 0x0000FFFF), AND(d0, 0xFFFF0000)) -- move.w d1,d0 d1 = OR(SHIFT(AND(d1, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16)) -- swap d1 d0 = OR(AND(d1 + d0, 0x0000FFFF), AND(d0, 0xFFFF0000)) -- add.w d1,d0 d1 = OR(SHIFT(AND(d0, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16)) -- move.w d0,d1 \n swap d1 seed = d1 -- move.l d1,(v_random).w return d0 end function predict_animals() -- Try to find a capsule switch capsule_switch = find_capsule() if capsule_switch == nil then -- If it was not found, so update variables and leave last_seed = memory.readlong(v_random) last_vbla = memory.readbyte(v_vbla_byte) return end -- Update random seed to RAM value seed = memory.readlong(v_random) -- Capsule switch's routine counter local rout = memory.readbyte(capsule_switch+0x24) -- V-Int counter, used for pseudo-random numbers local vbla = memory.readbyte(v_vbla_byte) -- Special cases: if rout == 4 then -- Switch not pressed gui.drawtext(4, 4, "Capsule not broken yet.") last_seed = seed last_vbla = vbla return elseif rout == 0xE then -- All animals spawned and moving to leave screen gui.drawtext(4, 4, "Waiting for animals to leave screen.") last_seed = seed last_vbla = vbla return end -- Sometimes, the V-Int counter does not update; doing this "manual" -- update corrects the issue, which causes misprediction. if last_vbla == vbla then vbla = vbla + 1 end local row = 0 -- Draw container box. gui.drawbox(0, 0, 207, 21*8, { 0, 0, 0, 128}, {255, 255, 255, 255}) -- Get animal list for current zone. local zone = memory.readbyte(v_zone) local types = typelist[zone] local countdown = 0 if rout <= 0xA then -- Explosions are being generated. There are two parts to this: -- Part 1: counting the explosions local explosions_left = 0 -- Time left for explosion generation countdown = memory.readword(capsule_switch+0x1E) local dt = 7 - AND(vbla, 7) for i=1,countdown+1 do -- Has an explosion been generated? if AND(vbla, 7) == 0 then -- If yes, we need to account for it: each explosion needs -- one random number. explosions_left = explosions_left + 1 local rand = GetRandom() end -- Fake frame advance. vbla = vbla + 1 end -- If we have explosions left, we have opportunities for manipulating -- the animal pattern. if explosions_left > 0 then gui.drawtext(4, 132, string.format("Next oportunity to change\nanimal pattern: %d frame%s", dt, (dt == 1 and "") or "s"), {255, 255, 0, 255}) end row = 4 gui.drawtext(4, row, string.format("Explosions left: %d", explosions_left)) row = row + 16 gui.drawtext(4, row, "Initial animals:") row = row + 8 -- Build sequence of random numbers to be used for the initial batch -- of animals. Do this so we can print them in ascending order of delay. local randseq = {} for i=1,8 do local rand = GetRandom() table.insert(randseq, 1, rand) end -- Part 2: Initial animals for i=1,8 do -- Random number determines kind of animal. local rand = randseq[1] table.remove(randseq, 1) local ty = types[AND(rand, 1)] gui.drawtext(12, row, string.format("%-8s in %3d frames", animals[ty][1], countdown + 90 + 8*i), animals[ty][2]) row = row + 8 end end if rout <= 0xC then -- Single animals are being generated. -- Part 3: Pseudo-random animal generation row = 4 local saverow = row row = row + 8 local timer = (rout == 0xC and memory.readword(capsule_switch+0x1E)) or 150 local animals_left = 0 local dt = 7 - AND(vbla, 7) for i=1,timer+1 do if AND(vbla, 7) == 0 then -- First random number is used to determine the position -- offset from the switch. local rand = AND(GetRandom(), 0x1F) - 6 local pos = (seed > 0 and rand) or -rand -- Second random number determines animal type. rand = GetRandom() local ty = types[AND(rand, 1)] if countdown + i - 2 > 0 then gui.drawtext(116, row, string.format("%-8s in %3d frame%s", animals[ty][1], countdown + i - 2, ((countdown + i - 2) == 1 and "") or "s"), animals[ty][2]) row = row + 8 animals_left = animals_left + 1 end end vbla = vbla + 1 end -- If we have animals still to be generated, and we are not in the -- process of making explosions, we have an opportunity to change -- if the next animal will be loaded or not. if rout == 0xC and animals_left > 0 then gui.drawtext(4, 132, string.format("Next oportunity to prevent\nanimal spawn: %d frame%s", dt, (dt == 1 and "") or "s"), {255, 255, 0, 255}) end -- If there were any animals left, say so if animals_left > 0 then gui.drawtext(108, saverow, string.format("Animal spawns: %d left", animals_left)) end end last_seed = memory.readlong(v_random) last_vbla = memory.readbyte(v_vbla_byte) return end gens.registerafter(predict_animals) savestate.registerload(predict_animals)[/lua] With proper modifications, this could be used for Sonic 2 too.
Marzo Junior
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
I tested it a little and I'm having trouble with the initial animals, where it seems you need to manipulate all at once - if I've understood it right. Have you gotten a good result out of this? If a rabbit jumps out from the middle it takes about 83 frames for it to leave the screen (after it lands) while it takes 56 frames for a flicky to do so. If a rabbit jumps out from one of the edges and then goes in the best direction it takes 74 frames for it to leave the screen while it takes 49 frames for a flicky. So the maximum difference, given that you manipulate them to go in the best direction after they land is 34 frames which means that to make sure a rabbit doesn't waste time the last 9 animals should be flickys (5 from the initial list and 4 from the spawning list). Most likely it'll be enough for the 7 last animals to be flickys though. Maybe this could be of use for an eventual bot using the script.
feos wrote:
Only Aglar can improve this now.
Player (50)
Joined: 4/10/2009
Posts: 226
very excited about this script work. can't wait to see how the capsules turn out. also aglar, why did you roll off the hill near the start of act 1, shouldn't that slow you down slightly? the improvements are pretty awesome, it's amazing how fast GHZ is getting.
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
Rolling made me keep the 1536 speed one more frame when hitting the uphill. Landing in an uphill while rolling apparently make it count as flat ground for a frame.
feos wrote:
Only Aglar can improve this now.
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Yes, the initial animals must be manipulated all at once; they are all generated in the same frame (it can be either with the last explosion or up to 6 frames after it), the last ones being created first. For the new movie, I got the best results so far by taking the first 3 opportunities to manipulate the initial animals (frames 7484, 7493 and 7501) then letting it run with no further manipulation; this results in all animals being offscreen by frame 7804, with the last bunny going offscreen with the last flicky -- compared to frame 7829 without any manipulation (the old number (32 frames) was for the old movie). There are better manipulations possible (manipulating the initial animals for the first 6 times instead), but the added frame count for the pauses offsets the gains. Hm. Since I know the animal speeds, and can compute their initial positions, I could theoretically compute how long they would take to go offscreen; that would require simulating their movement out of the capsule so I know when they hit the ground (as terrain is somewhat uneven) to know when they start moving offscreen (so I know the direction they take). That would even allow me to compute (albeit approximately) the total time it takes. This will take some research, and would require converting some more functions from ASM to lua, so it will take some time.
Marzo Junior
marzojr
He/Him
Experienced player (742)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
After converting the functions from ASM to lua and debugging, it turns out that the predictor is not good at estimating the exact time when an animal leaves the screen ("exit prediction") except for the initial batch of animals or for when the animal is on the move. One reason I can for this is object placement: animals loaded in slots after the capsule will load one frame earlier than those loaded before the capsule; this 1-frame difference can change the direction that the animal will travel, and this will affect exit time. Another factor (which I account for) is that an animal loaded after the capsule will take one extra frame to register as missing. I have no idea what other factors are at play; but the fact is that the exit prediction is off by up to plus/minus 3 frames in my tests. The wait until the animals are spawned/start moving works perfectly, only the endpoint prediction that fails -- unless the animal is already on the move. The only ways out of this are simulating the object placement or emulating all frames until the animals leave. I be looking into the latter; in any case, here is the current version of the script: Download predict-animals-v2.lua
Language: lua

local v_objspace = 0xFFFFD000 local c_objsize = 0x40 -- Object SST constants. local obRender = 0x1 local obX = 0x8 local obY = 0xC local obVelX = 0x10 local obVelY = 0x12 local obFrame = 0x1A local obHeight = 0x16 local obWidth = 0x17 local obTimeFrame = 0x1E local obRoutine = 0x24 -- A few important RAM locations. local v_random = 0xFFFFF636 local v_zone = 0xFFFFFE10 local v_vbla_byte = 0xFFFFFE0F local v_collindex = 0xFFFFF796 local v_lvllayout = 0xFFFFA400 local AngleMap = 0x00062900 local CollArray1 = 0x00062A00 -- Function to find the capsule'e switch local function find_capsule() for addr=v_objspace + c_objsize,v_objspace + 0x40 * c_objsize,c_objsize do if memory.readbyte(addr) == 0x3e and memory.readbyte(addr+obRoutine) > 2 then return addr end end return nil end -- Function to enumerate all animals that matter for the capsule local function enum_animals() local animals = {} for addr=v_objspace + c_objsize,v_objspace + 0x40 * c_objsize,c_objsize do if memory.readbyte(addr)==0x28 and memory.readbyte(addr+obRoutine) > 0 then table.insert(animals, addr) end end return animals end local function speed2color(speed, min, max) local green = math.floor(((speed - min) * 255) / (max - min)) local red = math.floor(((max - speed) * 255) / (max - min)) local blue, alpha = 0, 255 return {red, green, blue, alpha} end local minspd, maxspd = 0x140, 0x300 -- Names for animals. local animals = {[0]={"Rabbit" , speed2color(0x200, minspd, maxspd), -0x200, -0x400}, [1]={"Chicken" , speed2color(0x200, minspd, maxspd), -0x200, -0x300}, [2]={"Eagle" , speed2color(0x180, minspd, maxspd), -0x180, -0x300}, [3]={"Seal" , speed2color(0x140, minspd, maxspd), -0x140, -0x180}, [4]={"Pig" , speed2color(0x1C0, minspd, maxspd), -0x1C0, -0x300}, [5]={"Flicky" , speed2color(0x300, minspd, maxspd), -0x300, -0x400}, [6]={"Squirrel", speed2color(0x280, minspd, maxspd), -0x280, -0x380}} -- Map between zone ID and animal names. local typelist = {[0]={[0]=0, [1]=5}, [1]={[0]=2, [1]=3}, [2]={[0]=6, [1]=3}, [3]={[0]=4, [1]=5}, [4]={[0]=4, [1]=1}, [5]={[0]=0, [1]=1}} -- Adddress of capsule switch in RAM. local capsule_switch = nil -- Internal variables. local seed = 0 local last_seed = -1 local last_vbla = -1 -- Lua version of the Sonic 1 random number generator. local function GetRandom() local d1 = seed or 0x2A6D365A local d0 = d1 -- move.l d1,d0 d1 = SHIFT(d1, -2) -- asl.l #2,d1 d1 = d1 + d0 -- add.l d0,d1 d1 = SHIFT(d1, -3) -- asl.l #3,d1 d1 = d1 + d0 -- add.l d0,d1 d0 = OR(AND(d1, 0x0000FFFF), AND(d0, 0xFFFF0000)) -- move.w d1,d0 d1 = OR(SHIFT(AND(d1, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16)) -- swap d1 d0 = OR(AND(d1 + d0, 0x0000FFFF), AND(d0, 0xFFFF0000)) -- add.w d1,d0 d1 = OR(SHIFT(AND(d0, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16)) -- move.w d0,d1 \n swap d1 seed = d1 -- move.l d1,(v_random).w return d0 end -- Lua version of the Sonic 1 function to find the nearest tile. local function FindNearestTile(objRender, objX, objY) -- move.w d2,d0 -- lsr.w #1,d0 -- andi.w #$380,d0 -- move.w d3,d1 -- lsr.w #8,d1 -- andi.w #$7F,d1 -- add.w d1,d0 local index = AND(SHIFT(objY, 1), 0x380) + AND(SHIFT(objX, 8), 0x7F) -- Later: moveq #-1,d1 local tilenum = memory.readbyte(v_lvllayout + index) -- lea (v_lvllayout).w,a1 \n move.b (a1,d0.w),d1 if AND(tilenum, 0x80) ~= 0 then -- bmi.s @specialtile -- @specialtile: tilenum = AND(tilenum, 0x7F) -- andi.w #$7F,d1 if AND(objRender, 0x40) ~= 0 then -- btst #6,obRender(a0) \n beq.s @treatasnormal tilenum = tilenum + 1 -- addq.w #1,d1 if tilenum == 0x29 then -- cmpi.w #$29,d1 \n bne.s @treatasnormal tilenum = 0x51 -- move.w #$51,d1 end end -- @treatasnormal: tilenum = AND(tilenum - 1, 0xFF) -- subq.b #1,d1 if AND(tilenum, 0x80) ~= 0 then -- ext.w d1 tilenum = OR(tilenum, 0xFF00) end tilenum = OR(SHIFT(AND(tilenum, 0x7F), -9), SHIFT(AND(tilenum, 0xFF80), 7)) -- ror.w #7,d1 -- move.w d2,d0 -- add.w d0,d0 -- andi.w #$1E0,d0 -- add.w d0,d1 -- move.w d3,d0 -- lsr.w #3,d0 -- andi.w #$1E,d0 -- add.w d0,d1 return OR(AND(tilenum + AND(2 * objY, 0x1E0) + AND(SHIFT(objX, 3), 0x1E), 0xFFFF), 0xFFFF0000) end if tilenum > 0 then tilenum = AND(tilenum - 1, 0xFF) -- subq.b #1,d1 if AND(tilenum, 0x80) ~= 0 then -- ext.w d1 tilenum = OR(tilenum, 0xFF00) end tilenum = OR(SHIFT(AND(tilenum, 0x7F), -9), SHIFT(AND(tilenum, 0xFF80), 7)) -- ror.w #7,d1 -- move.w d2,d0 -- add.w d0,d0 -- andi.w #$1E0,d0 -- add.w d0,d1 -- move.w d3,d0 -- lsr.w #3,d0 -- andi.w #$1E,d0 -- add.w d0,d1 return OR(AND(tilenum + AND(2 * objY, 0x1E0) + AND(SHIFT(objX, 3), 0x1E), 0xFFFF), 0xFFFF0000) end -- @blanktile: return 0xFFFFFF00 -- movea.l d1,a1 \n rts end -- Lua version of the Sonic 1 secondary function to find the floor. local function FindFloor2(objRender, objX, objY, solidbit, delta, initangle, flipmask) local angle = initangle local tileaddr = FindNearestTile(objRender, objX, objY) -- bsr.s FindNearestTile local floordist = 0 local tile = memory.readword(tileaddr) -- move.w (a1),d0 \n move.w d0,d4 -- andi.w #$7FF,d0 \n beq.s @isblank2 \n btst d5,d4 \n bne.s @issolid local tileid = AND(tile, 0x7FF) if tileid ~= 0 and AND(tile, solidbit) ~= 0 then -- @issolid: local colptr = memory.readlong(v_collindex) -- movea.l (v_collindex).w,a2 local block = memory.readbyte(colptr + tileid) -- move.b (a2,d0.w),d0 if block == 0 then -- beq.s @isblank2 -- @isblank2 return 0xF - AND(objY, 0xF), tileaddr, angle end angle = memory.readbytesigned(AngleMap + block) -- lea (AngleMap).l,a2 \n move.b (a2,d0.w),(a4) block = SHIFT(block, -4) -- lsl.w #4,d0 local xcopy = objX -- move.w d3,d1 if AND(tile, BIT(0xB)) ~= 0 then -- btst #$B,d4 \n beq.s @noflip xcopy = -objX-1 -- not.w d1 angle = -angle -- neg.b (a4) end -- @noflip: if AND(tile, BIT(0xC)) ~= 0 then --- btst #$C,d4 \n beq.s @noflip2 angle = -0x80 - angle -- addi.b #$40,(a4) \n neg.b (a4) \n subi.b #$40,(a4) end -- @noflip2: xcopy = AND(objX, 0xF) + block -- andi.w #$F,d1 \n add.w d0,d1 local colhgt = memory.readbytesigned(CollArray1 + xcopy) -- lea (CollArray1).l,a2 \n move.b (a2,d1.w),d0 \n ext.w d0 tile = XOR(tile, flipmask) -- eor.w d6,d4 if AND(tile, BIT(0xC)) ~= 0 then -- btst #$C,d4 \n beq.s @noflip3 colhgt = -colhgt -- neg.w d0 end -- @noflip3: if colhgt == 0 then -- tst.w d0 \n beq.s @isblank2 -- @isblank2 return 0xF - AND(objY, 0xF), tileaddr, angle elseif colhgt < 0 then -- bmi.s @negfloor -- @negfloor: local dist = AND(objY, 0xF) -- move.w d2,d1 \n andi.w #$F,d1 if dist + colhgt < 0 then -- add.w d1,d0 \n bpl.w @isblank2 return -dist-1, tileaddr, angle -- not.w d1 \n rts end else local dist = AND(objY, 0xF) -- move.w d2,d1 \n andi.w #$F,d1 return 0xF - (dist + colhgt), tileaddr, angle -- add.w d1,d0 \n move.w #$F,d1 \n sub.w d0,d1 \n rts end end -- @isblank2: -- move.w #$F,d1 -- move.w d2,d0 -- andi.w #$F,d0 -- sub.w d0,d1 -- rts return 0xF - AND(objY, 0xF), tileaddr, angle end -- Lua version of the Sonic 1 function to find the floor. local function FindFloor(objRender, objX, objY, solidbit, delta, initangle, flipmask) local angle = initangle local tileaddr = FindNearestTile(objRender, objX, objY) -- bsr.s FindNearestTile local floordist = 0 local tile = memory.readword(tileaddr) -- move.w (a1),d0 \n move.w d0,d4 -- andi.w #$7FF,d0 \n beq.s @isblank \n btst d5,d4 \n bne.s @issolid local tileid = AND(tile, 0x7FF) if tileid ~= 0 and AND(tile, solidbit) ~= 0 then -- @issolid: local colptr = memory.readlong(v_collindex) -- movea.l (v_collindex).w,a2 local block = memory.readbyte(colptr + tileid) -- move.b (a2,d0.w),d0 if block == 0 then -- beq.s @isblank -- @isblank floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask) return floordist + 0x10, tileaddr, angle end angle = memory.readbytesigned(AngleMap + block) -- lea (AngleMap).l,a2 \n move.b (a2,d0.w),(a4) block = SHIFT(block, -4) -- lsl.w #4,d0 local xcopy = objX -- move.w d3,d1 if AND(tile, BIT(0xB)) ~= 0 then -- btst #$B,d4 \n beq.s @noflip xcopy = -objX-1 -- not.w d1 angle = -angle -- neg.b (a4) end -- @noflip: if AND(tile, BIT(0xC)) ~= 0 then --- btst #$C,d4 \n beq.s @noflip2 angle = -0x80 - angle -- addi.b #$40,(a4) \n neg.b (a4) \n subi.b #$40,(a4) end -- @noflip2: xcopy = AND(objX, 0xF) + block -- andi.w #$F,d1 \n add.w d0,d1 local colhgt = memory.readbytesigned(CollArray1 + xcopy) -- lea (CollArray1).l,a2 \n move.b (a2,d1.w),d0 \n ext.w d0 tile = XOR(tile, flipmask) -- eor.w d6,d4 if AND(tile, BIT(0xC)) ~= 0 then -- btst #$C,d4 \n beq.s @noflip3 colhgt = -colhgt -- neg.w d0 end -- @noflip3: if colhgt == 0 then -- tst.w d0 \n beq.s @isblank -- @isblank floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask) return floordist + 0x10, tileaddr, angle elseif colhgt < 0 then -- bmi.s @negfloor -- @negfloor: local dist = AND(objY, 0xF) -- move.w d2,d1 \n andi.w #$F,d1 if dist + colhgt < 0 then -- add.w d1,d0 \n bpl.w @isblank -- @maxfloor floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask) return floordist - 0x10, tileaddr, angle end elseif colhgt == 0x10 then -- cmpi.b #$10,d0 \n beq.s @maxfloor -- @maxfloor: -- sub.w a3,d2 -- bsr.w FindFloor2 -- add.w a3,d2 -- subi.w #$10,d1 -- rts floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask) return floordist - 0x10, tileaddr, angle else -- move.w d2,d1 -- andi.w #$F,d1 -- add.w d1,d0 -- move.w #$F,d1 -- sub.w d0,d1 -- rts return 0xF - (AND(objY, 0xF) + colhgt), tileaddr, angle end end -- @isblank: -- add.w a3,d2 -- bsr.w FindFloor2 ; try tile below the nearest -- sub.w a3,d2 -- addi.w #$10,d1 ; return distance to floor -- rts floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask) -- beq.s @isblank return floordist + 0x10, tileaddr, angle end -- Lua version of the Sonic 1 function to find distance to the floor. local function ObjFloorDist(objX, objY, objH, objR) --local objX = memory.readword(obj+obX) -- move.w obX(a0),d3 --local objY = memory.readword(obj+obY) -- move.w obY(a0),d2 --local objH = memory.readbytesigned(obj+obHeight) -- moveq #0,d0 \n move.b obHeight(a0),d0 \n ext.w d0 --local objR = memory.readbyte(obj+obRender) --objY = objY + objH -- add.w d0,d2 -- lea (v_anglebuffer).w,a4 -- move.b #0,(a4) -- movea.w #$10,a3 -- move.w #0,d6 -- moveq #$D,d5 local floordist, tileaddr, angle = FindFloor(objR, objX, objY + objH, BIT(0xD), 0x10, 0, 0) -- bsr.w FindFloor if AND(angle, 1) ~= 0 then angle = 0 -- move.b (v_anglebuffer).w,d3 \n btst #0,d3 \n beq.s locret_14E4E \n move.b #0,d3 end return floordist, tileaddr, angle -- locret_14E4E: rts end -- Lua version of the Sonic 1 function to make an object "fall". local function ObjectFall(objX, objY, objVX, objVY) --local memory.readlong(obj+obX) -- move.l obX(a0),d2 --local memory.readlong(obj+obY) -- move.l obY(a0),d3 --local memory.readwordsigned(obj+obVelX) -- move.w obVelX(a0),d0 \n ext.l d0 objX = AND(objX + SHIFT(objVX, -8), 0xFFFFFFFF) -- asl.l #8,d0 \n add.l d0,d2 --local memory.readwordsigned(obj+obVelY) -- move.w obVelY(a0),d0 \n ext.l d0 objY = AND(objY + SHIFT(objVY, -8), 0xFFFFFFFF) -- asl.l #8,d0 \n add.l d0,d3 objVY = objVY + 0x38 -- addi.w #$38,obVelY(a0) return objX, objY, objVX, objVY -- move.l d2,obX(a0) \n move.l d3,obY(a0) \n rts end -- Lua version of the Sonic 1 function loc_912A. local function animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla) -- tst.b obRender(a0) -- bpl.w DeleteObject objX, objY, objVX, objVY = ObjectFall(objX, objY, objVX, objVY) -- bsr.w ObjectFall if objVY >= 0 then -- tst.w obVelY(a0) \n bmi.s loc_9180 local floordist = ObjFloorDist(SHIFT(objX, 16), SHIFT(objY, 16), objH, objR) -- jsr ObjFloorDist if floordist < 0 then -- tst.w d1 \n bpl.s loc_9180 objY = objY + floordist -- add.w d1,obY(a0) objVX = objNVX -- move.w $32(a0),obVelX(a0) objVY = objNVY -- move.w $34(a0),obVelY(a0) -- move.b #1,obFrame(a0) -- move.b $30(a0),d0 -- add.b d0,d0 -- addq.b #4,d0 -- move.b d0,obRoutine(a0) -- tst.b (v_bossstatus).w -- beq.s loc_9180 if AND(vbla, BIT(4)) ~= 0 then -- btst #4,(v_vbla_byte).w \n beq.s loc_9180 objVX = -objVX -- neg.w obVelX(a0) objR = XOR(objR, BIT(0)) -- bchg #0,obRender(a0) end end end -- loc_9180: -- bra.w DisplaySprite return objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla end local function simulate_animal_delay_fall_escape(obj, objX, objY, objVX, objVY, objNVX, objNVY, objWait, vbla, capX) local objH = 0xC local objR = 0x85 local objW = 8 local initvbla = vbla -- If we have a real object: if obj ~= nil then objX = memory.readlong(obj+obX) objY = memory.readlong(obj+obY) objVX = memory.readwordsigned(obj+obVelX) objVY = memory.readwordsigned(obj+obVelY) objNVX = memory.readwordsigned(obj+0x32) objNVY = memory.readwordsigned(obj+0x34) objWait = memory.readword(obj+0x36) - 1 end -- Simulate wait if needed if objWait > 0 then vbla = vbla + objWait + 1 end -- Simulate falling while objVX == 0 do vbla = vbla + 1 objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla = animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla) end -- Compute time to get offscreen local le = SHIFT(capX - 160 - objW, -8) local re = SHIFT(capX + 160 + objW, -8) -- Lets use relative position here. objX = SHIFT(objX, 8) local lastvbla = vbla if objVX > 0 then vbla = vbla + math.abs(math.floor((re - objX + objVX - 1) / objVX)) + 1 else vbla = vbla + math.abs(math.floor((objX - le - objVX - 1) / -objVX)) + 1 end return objVX, vbla end function print_animal_objects(vbla) local animal_list = enum_animals() if #animal_list > 0 then row = 4 gui.drawtext(4, row, "Spawned animals:") row = row + 8 gui.drawtext(12, row, "Animal Wait D Exit at") row = row + 8 local capX = memory.readword(capsule_switch+obX) local list1 = {} local list2 = {} for i,m in pairs(animal_list) do local ty = memory.readbyte(m+0x30) local delay = memory.readword(m+0x36) local vx, endvbla = simulate_animal_delay_fall_escape(m, nil, nil, nil, nil, nil, nil, nil, vbla, capX) local dir = (vx > 0 and ">") or "<" local endpt = movie.framecount() + endvbla - vbla + 1 table.insert((delay == 0 and list2) or list1, {endpt, function(rw) gui.drawtext(12, rw, string.format("%-8s %4d %s %7d", animals[ty][1], delay, dir, endpt), animals[ty][2]) end}) end table.sort(list1, function(item1, item2) return item1[1] < item2[1] end) table.sort(list2, function(item1, item2) return item1[1] < item2[1] end) for i,m in pairs(list1) do m[2](row) row = row + 8 end gui.drawtext(12, row, "========= Free =========") row = row + 8 for i,m in pairs(list2) do m[2](row) row = row + 8 end end end function predict_animals() -- Try to find a capsule switch capsule_switch = find_capsule() if capsule_switch == nil then -- If it was not found, so update variables and leave last_seed = memory.readlong(v_random) last_vbla = memory.readbyte(v_vbla_byte) return end -- Update random seed to RAM value seed = memory.readlong(v_random) -- Capsule switch's routine counter local rout = memory.readbyte(capsule_switch+obRoutine) -- V-Int counter, used for pseudo-random numbers local vbla = memory.readbyte(v_vbla_byte) -- Sometimes, the V-Int counter does not update; doing this "manual" -- update corrects the issue, which causes misprediction. if last_vbla == vbla then vbla = vbla + 1 end local savevbla = vbla -- Special case: if rout == 4 then -- Switch not pressed gui.drawtext(4, 4, "Capsule not broken yet.") last_seed = seed last_vbla = savevbla return end local row = 0 -- Draw container box. gui.drawbox(0, 0, 223, 28*8-1, { 0, 0, 0, 128}, {255, 255, 255, 255}) if rout == 0xE then -- All animals spawned and moving to leave screen gui.drawtext(128, 12, "Waiting for animals\nto leave screen.", {255, 255, 0, 255}) print_animal_objects(vbla) last_seed = seed last_vbla = savevbla return end -- Get animal list for current zone. local zone = memory.readbyte(v_zone) local types = typelist[zone] if rout <= 0xA then -- Explosions are being generated. There are two parts to this: -- Part 1: counting the explosions local explosions_left = 0 -- Time left for explosion generation local countdown = memory.readword(capsule_switch + obTimeFrame) local dt = 7 - AND(vbla, 7) for i = 1, countdown + 1 do -- Has an explosion been generated? if AND(vbla + i - 1, 7) == 0 then -- If yes, we need to account for it: each explosion needs -- one random number. explosions_left = explosions_left + 1 local rand = GetRandom() end -- Fake frame advance. end -- If we have explosions left, we have opportunities for manipulating -- the animal pattern. if explosions_left > 0 then gui.drawtext(112, 180, string.format("Next oportunity to change\nanimal pattern: %d frame%s", dt, (dt == 1 and "") or "s"), {255, 255, 0, 255}) end row = 4 gui.drawtext(4, row, string.format("Explosions left: %d", explosions_left)) row = row + 16 gui.drawtext(4, row, "Initial animals:") row = row + 8 gui.drawtext(12, row, "Animal Wait D Exit at") row = row + 8 -- Build sequence of random numbers to be used for the initial batch -- of animals. Do this so we can print them in ascending order of delay. local randseq = {} for i = 1, 8 do local rand = GetRandom() table.insert(randseq, 1, rand) end local capX = memory.readword(capsule_switch+obX) local capY = memory.readword(capsule_switch+obY) + 0x20 -- Part 2: Initial animals for i = 1, 8 do -- Random number determines kind of animal. local rand = randseq[1] table.remove(randseq, 1) local ty = types[AND(rand, 1)] local objX = SHIFT(capX + 0x1C - 7 * i, -16) local objY = SHIFT(capY, -16) local delay = countdown + 90 + 8 * i local vx, endvbla = simulate_animal_delay_fall_escape(nil, objX, objY, 0, -0x400, animals[ty][3], animals[ty][4], delay, vbla, capX) local dir = (vx > 0 and ">") or "<" gui.drawtext(12, row, string.format("%-8s %4d %s %7d", animals[ty][1], delay, dir, movie.framecount() + endvbla - vbla), animals[ty][2]) row = row + 8 end end print_animal_objects(vbla) if rout <= 0xC then -- Single animals are being generated. -- Part 3: Pseudo-random animal generation row = 4 local saverow = row row = row + 8 gui.drawtext(124, row, "Animal Spawn D Exit at") row = row + 8 local animals_left = 0 local countdown = (rout < 0xC and memory.readword(capsule_switch+obTimeFrame)) or 0 local dt = 7 - AND(vbla + countdown, 7) local timer = (rout == 0xC and memory.readword(capsule_switch+obTimeFrame)) or 150 local capX = memory.readword(capsule_switch+obX) local capY = memory.readword(capsule_switch+obY) + ((rout < 0xC and 0x20) or 0) for i = 1, timer + 1 do if AND(vbla + countdown + i - 1, 7) == 0 then -- First random number is used to determine the position -- offset from the switch. local rand = AND(GetRandom(), 0x1F) - 6 local pos = (seed > 0 and rand) or -rand -- Second random number determines animal type. rand = GetRandom() local ty = types[AND(rand, 1)] if countdown + i - 2 > 0 then local objX = SHIFT(capX + pos, -16) local objY = SHIFT(capY, -16) local delay = countdown + i + 12 local vx, endvbla = simulate_animal_delay_fall_escape(nil, objX, objY, 0, -0x400, animals[ty][3], animals[ty][4], delay, vbla, capX) local dir = (vx > 0 and ">") or "<" gui.drawtext(124, row, string.format("%-8s %4d %s %7d", animals[ty][1], delay-14, dir, movie.framecount() + endvbla - vbla + (rout < 0xC and 1 or 0)), animals[ty][2]) row = row + 8 animals_left = animals_left + 1 end end end -- If we have animals still to be generated, and we are not in the -- process of making explosions, we have an opportunity to change -- if the next animal will be loaded or not. if rout == 0xC and animals_left > 0 then gui.drawtext(112, 180, string.format("Next oportunity to prevent\nanimal spawn: %d frame%s", dt, (dt == 1 and "") or "s"), {255, 255, 0, 255}) end -- If there were any animals left, say so if animals_left > 0 then gui.drawtext(116, saverow, string.format("Animal spawns: %d left", animals_left)) end end last_seed = memory.readlong(v_random) last_vbla = savevbla return end gens.registerafter(predict_animals) savestate.registerload(predict_animals)
Marzo Junior
Expert player (3556)
Joined: 11/9/2007
Posts: 375
Location: Varberg, Sweden
New WIP: http://dehacked.2y.net/microstorage.php/info/535041267/Sonic%20The%20Hedgehog%20%28W%29%20%28REV%2001%29%20WIP_Aglar.gmv MZ2 suited me as a TASer really well. I didn't improve time to 0:33 but I'm still pleased with the result. I'll check more into the last part of this level later, and also try to have more style during waiting periods. I need to do something about the rerecord count as well, since this wouldn't look too nice in the statistics.
feos wrote:
Only Aglar can improve this now.
Experienced player (620)
Joined: 8/28/2008
Posts: 443
I just found glitch that can be really useful for glitching into walls and zipping through the level. When the screen scrolls upward the sprites disappear below him and when he takes damage he falls right through the platforms. I made a WIP of Marble Zone. It saved time on Act 2 and Act 3 (Act 2 is beaten in 15 seconds and Act 3 reaches the boss area at 12 seconds): http://dehacked.2y.net/microstorage.php/info/1241470559/GlitchMan-Sonic%20the%20Hedgehog%20%28J%29-MarbleZoneshortcuts.gmv
1 2
16 17 18
24 25