Posts for marzojr

marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Basic route for Quartz Quadrant 1 destroying the badnik teleporter and entering a giant ring (0'21"75): Link to video TAS version of Werster's new Wacky Workbench 2 route (0'19"36): Link to video
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
I added a Sonic CD Map Pack, based on maps from Zone 0 with solidity added by me using SonLVL and Gimp.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
New faster versions of some TASes: Link to video Has a zip courtesy of Sonikkustar, a new route, pause trick to load the boss faster (gains in-game time at the expense of real time, corrupts the HUD) and a better boss fight. Link to video Improved Bunnie TAS with new zips and better optimization. And abusing a double-hit trick on FZ boss, which Rodolfo Leonardo de la Riva pointed out to me in a comment. There are probably frames to gain overall, and it comes agonizingly close to 999990 points. With no-cost route changes, I can increase this by about 1500 points, but I can't see a way to go beyond that without losing a lot of time.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Link to video Sonic games traditionally focus on in-game time, including the GG version of this game. If you really want to showcase improvements of less than 1 second, use the in-game frame counter. If memory serves, it is not reset between levels in the SMS/GG games, so you need to note what value it was at the start of each level (and this may end up removing a second from the game clock because the frame counter did not start at zero).
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Without looking at the movie (no BizHawk for me), I would guess that it is perfect pixel+subpixel starting position so that the availble speed allows the underflow. I don't know about the SMS/GG games, but at least the Genesis games do not depend on the animation, just on the position and speed.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Well, MS-DOS also fails a bit further on with GP 4 in instruction FE/7. So it seems like Ultima 7 remains the base of emulator developers. So I guess there will be no TAS for it after all, until they either fix it or DOSBox TASing becomes a reality.
Marzo Junior
Post subject: Ultima 7 + Serpent Isle
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
So, I have been thinking of TASing these games. Which is fitting, since I am probably the person who knows most about it on the planet. But right off the bat, I found a couple issues which would require a ruling of some sort. As is well know and dreaded by x86 emulator developers, Ultima 7 and Serpent Isle both use a custom memory manager, Voodoo, which puts the machine in unreal mode. That, by itself, is probably not a problem (jpc-rr supports this mode). Moreover, both games are memory hogs as far as conventional memory goes. This is not much of a problem either, as one can comfortably get them to fit by moving stuff to upper memory (although this is far easier to do nowadays with emulators than it ever was in real machines, except for DOS 7). The real issue is that the way the games detect available conventional memory (and also probably the way they allocate it) is completely incompatible with FreeDOS. This is a known problem by FreeDOS devs, who just don't care enough about a couple of oddball games to make an exception to their memory manager. So there would be three routes for this: (1) use MS-DOS instead; (2) bypass the memory check; (3) hack the game to be compatible with FreeDOS. I really don't want to do (3) (and only one person has ever done it, for Windows compatibility). (2) can be done either by hacking intro.exe to bypass the check or by bypassing intro.exe/mainmemu.exe and running "u7.exe c150 p" directly. The latter eventually fails with a GPF in instruction FF/7 (don't know if it fails because of jpc-rr or because of FreeDOS). I think it is highly likely that the former will also fail in the same way. So there remains (1), with all of the legal issues. I have been able to run U7 with a (slightly tweaked) DOS 6.22 disk image. How acceptable would this be for submitting a movie? The only changes from stock DOS 6.22 is autoexec.bat and config.sys.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
EEssentia wrote:
If you don't inject the signal so that it starts off in the system at the beginning of each clock period, you're likely going to have timing violations or other weirdness. How can you guarantee that you're balancing the wires from the oscillator such that it arrives at exactly the right time in the system?
I... what? He was talking about completely removing the crystal oscillators that generate the clock signal in the consoles; there would be no more clock signal in the console that you need to synch anything to. Instead of the stock clock, an external signal would be supplied to take over that function. It would be supplied in the exact same place where the original crystal oscillator was before, so there would be no need to consider in-console clock skew (it was considered when they designed the console); clock skew between consoles might be a factor, and would need to be dealt with. Using the same length of wires to distribute clock signal from the clock driver to all consoles would deal with that nicely. And yes, systems with multiple crystal oscillators would pose more of an issue because you would need to consider clock skew when distributing the multiple clocks; but many of those older systems used one single higher frequency crystal and used clock divisors to generate signals for the multiple components.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Yeah, then I am out of ideas...
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
WST wrote:
Recently I’ve been unable to TAS because my Gens stopped to work under wine. I have no idea why.
X Error of failed request:  BadValue (integer parameter out of range for operation)
  Major opcode of failed request:  154 (GLX)
  Minor opcode of failed request:  3 (X_GLXCreateContext)
  Value in failed request:  0x0
  Serial number of failed request:  221
  Current serial number in output stream:  222
Out of curiosity: are you trying to TAS on a laptop with a NVIDIA GPU with the Optimus technology? And if so, are you using Nouveau (the open source driver) or the proprietary driver?
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
TASeditor wrote:
1. Calculating the best (X;Y) positions on the analoag stick for a given angle and a given minimum radius. [snip details]
Here is an unorthodox idea: you know the "perfect" angle (357.22 in your example), and you have your "calculation radius" (127 in your example). So you draw a line using Bresenham's line drawing algorithm from (0, 0) to (radius*cos(angle), radius*sin(angle)). At each step in the line, you take the computed (x, y) values; if they are such that x**2 + y**2 < minimum_radius, you just go to the next point in the line; otherwise, you compute newangle = atan2(y, x) and see how close it is to the perfect angle, picking it if it is better than the best one seen so far. When you reach the endpoint of the line, you don't need to go any further (as it will be outside of the analog stick's range) and you will have the best angle possible. This is guaranteed because Bresenham's algorithm minimizes the error between the ideal line and the raster approximation it computes, so you will always be checking the points closest to the ideal line. In a more algorithmic, pseudo-C fashion:
var findOptimalAnalogPosition(var minRadius, var maxRadius, var idealAngle) {
	var point_list = Bresenham(0.0 , 0.0, maxRadius * cos(idealAngle), maxRadius * sin(idealAngle));
	var bestPt;
	var bestDist = INFINITY;
	for each (pt in point_list) {
		if ((pt.x * pt.x + pt.y + pt.y) >= minRadius) {
			var newDist = abs(atan2(pt.y, pt.x) - idealAngle);
			if (newDist < bestDist) {
				bestDist = newDist;
				bestPt = pt;
			}
		}
	}
	return pt;
}
Using this, I was able to find your optimal position of (103, -5) (you flipped the values at some point). In an actual implementation, you could "inline" Bresenham's algorithm and check the angle as you go about the line. For a byte-per-direction analog stick, you will need at most 127 steps to find the optimal angle.
TASeditor wrote:
2. At a given analog stick position and a given minimum radius, what is the next analog stick position that gives the next greater/smaller input angle and how can it be calculated.
The above can be used for this: you have the initial analog position, so you can use the (x, y) to compute the angle and use same algorithm above.
TASeditor wrote:
3. Preventing walking in the wrong direction.
The best way to do this for sure is to use a target position and periodically (every frame?) recompute the ideal angle from current position to target position and change the analog stick to point that way.
TASeditor wrote:
4. Rotate the coordinate system, so that there's an axis that is the player position on that axis depending on the angle the player wants to follow( and another axis that is not interesting).
I am not sure I understand what you want here. Can you elaborate?
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
True wrote:
Also Z80 R register. Also VDP/CPU clock cycle offset and VDP register state.
Z80 R register is memory refresh :-p But I had forgotten about the starting VDP offset, yeah. A custom game could work around it to a high degree of certainty by waiting for the first vblank interrupt, then waiting for vblank interval to finish, then doing a trick to synchronize the 68k with the raster beam. But stock games are out of luck, though. And you are correct, this should be discussed on another thread; I only mentioned it because of Weatherton's suggestion of tying a SNES and a Genesis together to show why it would be hard, if not impossible, to synch reliably live on AGDQ.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
True wrote:
Genesis is non-deterministic.
The only significant source of nondeterminism on Genesis are memory refresh cycles, which are poorly understood and not emulated at all (and may turn out to be deterministic with enough knowledge). And maybe ROM access speeds for older ROMs. The rest is lack of proper emulation either because of lack of knowledge or because no one bothered to emulate it properly: most emulators use incorrect cycle counts for some instructions because the Motorolla manual lists them wrong; bus access contention between 68k and z80 is not properly emulated because that would require much more precise knowledge of the 68k than is available, and would probably require micro-opcode emulation of the 68k (and much better z80 emulation too, as well as keeping them in more perfect synch); keeping the VDP in synch with the other processors because it can cause delay on the 68k if the 68k sends it a command and the VDP's FIFO is full (likely to happen only during active display, but...); and some other things. These all combine to make the 68k run effectively slower, with some nondeterministic slowdown also on the z80 if it is using main RAM (but this is very rare). If the game is sufficiently "simple", these innacuracies will not matter much: if you can guarantee that the game will always finish a frame with "ample" time to spare, poll controllers during vblank interval, don't use computational time as a source of pseudo-randomness (directly or indirectly) and don't do anything too tricky in regards to timing, the game will most likely synch on console with relatively minor changes (say, removing all lag-frame input). Most Genesis games do only one of these (poll controllers onon vblank internal), so the effects of those emulation innacuracies matter a lot.
True wrote:
Previous TAS verifies were done with a loader and input file manipulation. Work needs to be done to improve emulation and strip away the need for a loader.
What did the loader do? I find it odd, as games generally followed the Sega docs closely and cleared all RAM (main and z80) and set VDP, YM2612 and PSG to known states at the first boot.
Marzo Junior
Post subject: Re: Dominoes
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Nach wrote:
I think this is a bad idea.
Oh, yeah, I forgot to add that Genesis emulation (even GPGX, the core used in BizHawk) is not good enough to guarantee synch on all cases on console unless the game follows some very strict rules.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Weatherton wrote:
For example, create two ROM hacks that pass information between two consoles to enable a co-operative game of some type. I think this would be particularly interesting if the two consoles were different models. So, for example, a competitive (or co-operative) Mario/Sonic game in which one player controls Mario using a SNES and the other player controls Sonic using a Genesis.
This would be possible, but a nightmare to synchronize. On the Genesis side, you would need to find one of the rare Sega modems (or build one using the extension port), with neither case giving a large baud rate. And the SNES may have some issues due to its cycle-starved processor (but I can't speak with authority on the SNES). Out of curiosity, what kind of game were you thinking of? A racer like in the video you posted?
Weatherton wrote:
Blast processing vs. Mode 7;
With a good enough hacker/programmer on the Genesis side, these could plausibly compete at an even footing (granting the Super FX co-processor for the SNES, of course).
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
WST wrote:
I have no idea where to watch it (and, honestly, feel not so much interest to it), but strongly wishing you good luck
You can watch it here, and there is a schedule here using your local time; search for 'TASBot'.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Good luck; we will all be watching you in a few hours, and hoping all goes well.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Warp wrote:
So "approaches zero" means "is not zero", and "approaches infinity" means "is infinity". Because reasons.
Because mathematical rigor. The defining traits of a positive "infinitesimal" are (1) it is greater than zero and (2) is smaller than any positive real number you come up with. Likewise, the defining traits of a positive "infinity" are (A) it is greater than zero, and (B) it is greater than any positive real number you come up with. The definitions for negative infinities and negative infinitesimals should be obvious. Also, note that the similarities in these traits are rather striking: in fact, you might say (and in non-standard analysis, it is said) that infinities are reciprocals of infinitesimals (and the converse). In standard analysis, on the other hand, this means that neither infinities nor infinitesimals are numbers, but concepts: they represent limiting processes. So "approaches zero" means "infinitesimal" means "nonzero" by definition; and "approaches infinity" means "infinity" because infinity is that limiting process. When the limiting process is well-defined (I am not going into that), "approaches zero" can indeed be zero; as a rule of thumb, any case when you have a singularity is not well-defined (but you can't assume this!).
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
xnamkcor wrote:
The spindash is an integral part of getting speed in the glide. The statement refers to the combo of both actions together.
The (horizontal) glide speed is actually completely unrelated to speed before you start gliding; it is set to 1024 subpixels/frame independent of how high or low it was. Most likely, the spindash reference was a holdover from the KiS1 and TiS1 TASes, for which it was true.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
I use the following script, derived from the encoding guidelines:
Language: bash

#!/bin/bash # Generate filenames # Input file avifile="$1" # Intermediate files mp4file="${avifile%avi}mp4" wavfile="${avifile%avi}wav" oggfile="${avifile%avi}ogg" # Output file mkvfile="${avifile%avi}mkv" # Split, resize (4x scale) and encode video into a temporary MP4 file x264 --vf resize:1280,896,method=point --fps 60 --qp 0 --keyint 600 --range tv --colorprim bt709 --transfer bt709 --colormatrix bt709 --output "$mp4file" "$avifile" # Split audio to wav and encode to temporary OGG file mplayer -ao pcm:waveheader:file="$wavfile" -novideo "$avifile" oggenc -q 10 "$wavfile" # Put everything back together into an MKV file mkvmerge --engage no_simpleblocks --compression -1:none "$mp4file" "$oggfile" -o "$mkvfile" # Clean up temporary files rm -f "$mp4file" "$wavfile" "$oggfile"
I then add the following tag to the YouTube video:
yt:resize=4:3
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
skychase wrote:
I followed the Encoding Guide and even though it's not HD, the framerate seems perfect and the sound is synced.
Until you upload to YouTube; then, it will become a blurry mess at 30fps. In the reencoding process, YouTube equates "low resolution" with "low bitrate", and will destroy the video. It also does not support 60fps except for HD video. There is a page in the encoding guidelines that explains how to get a good quality encode for uploading to YouTube (I'd link to it, but I don't want to search for it from my tablet...). Basically, upscaling by a factor of 4 allows you to use YUV420 with no loss of color information; then encode with x264 with high quality options and upload the HD video for 60fps. When encoding it this way I get files with similar sizes as the original lagarith-encoded lossless dump, which is pretty good considering it was upscaled by a factor of 4.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Dashjump wrote:
As far as I know, rings don't affect speed and jump in Sonic Advance 3. I've never seen a difference in the velocity values in the RAM addresses.
I asked about jump because speedrunners say you can jump further away; but I guess this ends up being because you have a higher speed limit then.
Dashjump wrote:
I hope this clears up some of your questions!
It does, yes; thanks.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
I think you rushed too much for this submission; you should have manipulated all animals first, as well as making sure all possible improvements were done before submitting. Right now, there is at least TeeNTee's new DEZ.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
Out of curiosity, is there a place where the mechanics of boost mode are described in detail? Specifically, how long it takes to reach it when running, how it is affected by rings, how much it changes top speed and jumping power (if at all)? The closest I got was a post by Nitsuja saying it increases top speed by 50%.
Marzo Junior
marzojr
He/Him
Experienced Forum User, Published Author, Experienced player (752)
Joined: 9/29/2008
Posts: 964
Location: 🇫🇷 France
I could sworn I had released one, but I can't seem to find it. The script is below; I have no recollection on how good or bad it is (it is that old). The major issue with animal manipulation in S2 is that you have very few opportunities to do so compared to S1: you can only manipulate them by adding or deleting boss explosions (which is annoying because you need to use something like TASMovieEditor or have to redo everything from the explosion to the capsule hit) or by deleting or adding additional animals. Both animals and boss explosions can be deleted by pausing a game so that the frame when the byte at FFFFFE0F has the low 3 bits clear (in RAM watch, make it a hex value, the rightmost digit must be 0 or 8). So pause right before, unpause right after. To add animals or explosions, you want to pause in such a way that these frames happen more often when the game is unpaused; for example, pausing right after them, and unpausing right before them. Anyway, the script:
Language: lua

local object_size = 0x40 local Object_RAM = 0xFFFFB000 local Dynamic_Object_RAM = Object_RAM + 16 * object_size -- Object SST constants. local render_flags = 0x1 local x_pos = 0x8 local y_pos = 0xC local x_vel = 0x10 local y_vel = 0x12 local mapping_frame = 0x1A local y_radius = 0x16 local x_radius = 0x17 local anim_frame_duration = 0x1E local routine = 0x24 local routine_secondary = 0x25 local animal_ground_routine_base = 0x30 local animal_ground_x_vel = 0x32 local animal_ground_y_vel = 0x34 -- A few important RAM locations. local RNG_seed = 0xFFFFF636 local Current_Zone = 0xFFFFFE10 local Vint_runcount = 0xFFFFFE0C local Collision_addr = 0xFFFFF796 local Level_Layout = 0xFFFF8000 local ColCurveMap = 0x00042D50 local ColArray = 0x00042E50 local Camera_X_pos = 0xFFFFEE00 local Camera_Y_pos = 0xFFFFEE04 -- Function to find the main capsule object local function find_capsule() for addr=Dynamic_Object_RAM,Object_RAM + 0x2000,object_size do if memory.readbyte(addr) == 0x3e and memory.readbyte(addr+routine) == 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=Dynamic_Object_RAM,Object_RAM + 0x2000,object_size do if memory.readbyte(addr)==0x28 and memory.readbyte(addr+routine) > 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]={"Penguin" , 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}, [7]={"Eagle" , speed2color(0x280, minspd, maxspd), -0x280, -0x300}, [8]={"Mouse" , speed2color(0x200, minspd, maxspd), -0x200, -0x380}, [9]={"Beaver" , speed2color(0x2C0, minspd, maxspd), -0x2C0, -0x300}, [10]={"Turtle" , speed2color(0x140, minspd, maxspd), -0x140, -0x200}, [11]={"Bear" , speed2color(0x200, minspd, maxspd), -0x200, -0x300}} -- Map between zone ID and animal names. local typelist = { [0]={[0]= 6, [1]= 5}, -- EHZ [1]={[0]= 6, [1]= 5}, -- Zone 1 [2]={[0]= 6, [1]= 5}, -- WZ [3]={[0]= 6, [1]= 5}, -- Zone 3 [4]={[0]= 9, [1]= 7}, -- MTZ [5]={[0]= 9, [1]= 7}, -- MTZ [6]={[0]= 9, [1]= 7}, -- WFZ [7]={[0]= 9, [1]= 7}, -- HTZ [8]={[0]= 8, [1]= 3}, -- HPZ [9]={[0]= 8, [1]= 3}, -- Zone 9 [10]={[0]= 2, [1]= 3}, -- OOZ [11]={[0]= 8, [1]= 1}, -- MCZ [12]={[0]=11, [1]= 5}, -- CNZ [13]={[0]= 0, [1]= 7}, -- CPZ [14]={[0]= 4, [1]= 1}, -- DEZ [15]={[0]= 2, [1]= 5}, -- ARZ [16]={[0]=10, [1]= 1}} -- SCZ -- Adddress of capsule switch in RAM. local main_capsule,capsule_body,capsule_switch = nil -- Internal variables. local seed = 0 local last_seed = -1 local last_vbla = -1 -- Lua version of the Sonic 2 random number generator. local function RandomNumber() 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,(RNG_seed).w return d0 end -- Lua version of the Sonic 2 function to find the nearest tile. local function Floor_ChkTile(objRender, objX, objY) -- move.w d2,d0 ; y_pos -- add.w d0,d0 -- andi.w #$F00,d0 ; rounded 2*y_pos -- move.w d3,d1 ; x_pos -- lsr.w #3,d1 -- move.w d1,d4 -- lsr.w #4,d1 ; x_pos/128 = x_of_chunk -- andi.w #$7F,d1 -- add.w d1,d0 ; d0 is relevant chunk ID now local d4 = SHIFT(objX, 3) local index = AND(AND(SHIFT(objY, -1), 0xF00) + AND(SHIFT(d4, 4), 0x7F),0xFFFF) -- moveq #-1,d1 -- clr.w d1 -- lea (Level_Layout).w,a1 -- move.b (a1,d0.w),d1 ; move 128*128 chunk ID to d1 local chunknum = memory.readbyte(Level_Layout + index) -- add.w d1,d1 -- move.w word_1E5D0(pc,d1.w),d1 -- move.w d2,d0 ; y_pos -- andi.w #$70,d0 -- add.w d0,d1 -- andi.w #$E,d4 ; x_pos/8 -- add.w d4,d1 local chunkaddr = 0xFFFF0000 + AND(memory.readword(0x1E5D0 + 2*chunknum) + AND(objY,0x70) + AND(d4, 0xE), 0xFFFF) -- movea.l d1,a1 ; address of block ID -- rts return chunkaddr end -- Lua version of the Sonic 2 secondary function to find the floor. local function FindFloor2(objRender, objX, objY, solidbit, delta, initangle, flipmask) local angle = initangle local tileaddr = Floor_ChkTile(objRender, objX, objY) -- bsr.s Floor_ChkTile local floordist = 0 local tile = memory.readword(tileaddr) -- move.w (a1),d0 \n move.w d0,d4 -- andi.w #$3FF,d0 \n beq.s loc_1E88A \n btst d5,d4 \n bne.s loc_1E898 local tileid = AND(tile, 0x3FF) if tileid ~= 0 and AND(tile, solidbit) ~= 0 then -- loc_1E898: local colptr = memory.readlong(Collision_addr) -- movea.l (Collision_addr).w,a2 local block = memory.readbyte(colptr + tileid) -- move.b (a2,d0.w),d0 if block == 0 then -- beq.s loc_1E88A -- loc_1E88A return 0xF - AND(objY, 0xF), tileaddr, angle end angle = memory.readbytesigned(ColCurveMap + block) -- lea (ColCurveMap).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(0xA)) ~= 0 then -- btst #$A,d4 \n beq.s + xcopy = -objX-1 -- not.w d1 angle = -angle -- neg.b (a4) end -- + if AND(tile, BIT(0xB)) ~= 0 then --- btst #$B,d4 \n beq.s + angle = -0x80 - angle -- addi.b #$40,(a4) \n neg.b (a4) \n subi.b #$40,(a4) end -- + xcopy = AND(objX, 0xF) + block -- andi.w #$F,d1 \n add.w d0,d1 local colhgt = memory.readbytesigned(ColArray + xcopy) -- lea (ColArray).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(0xB)) ~= 0 then -- btst #$B,d4 \n beq.s + colhgt = -colhgt -- neg.w d0 end -- + if colhgt == 0 then -- tst.w d0 \n beq.s loc_1E88A -- loc_1E88A return 0xF - AND(objY, 0xF), tileaddr, angle elseif colhgt < 0 then -- bmi.s loc_1E900 -- loc_1E900: 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 loc_1E88A 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 -- loc_1E88A: -- 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 2 function to find the floor. local function FindFloor(objRender, objX, objY, solidbit, delta, initangle, flipmask) local angle = initangle local tileaddr = Floor_ChkTile(objRender, objX, objY) -- bsr.s Floor_ChkTile local floordist = 0 local tile = memory.readword(tileaddr) -- move.w (a1),d0 \n move.w d0,d4 -- andi.w #$3FF,d0 \n beq.s loc_1E7E2 \n btst d5,d4 \n bne.s loc_1E7F0 local tileid = AND(tile, 0x3FF) if tileid ~= 0 and AND(tile, solidbit) ~= 0 then -- loc_1E7F0: local colptr = memory.readlong(Collision_addr) -- movea.l (Collision_addr).w,a2 local block = memory.readbyte(colptr + tileid) -- move.b (a2,d0.w),d0 \n andi.w #$FF,d0 if block == 0 then -- beq.s loc_1E7E2 -- loc_1E7E2 floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask) return floordist + 0x10, tileaddr, angle end angle = memory.readbytesigned(ColCurveMap + block) -- lea (ColCurveMap).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(0xA)) ~= 0 then -- btst #$A,d4 \n beq.s + xcopy = -objX-1 -- not.w d1 angle = -angle -- neg.b (a4) end -- + if AND(tile, BIT(0xB)) ~= 0 then --- btst #$B,d4 \n beq.s + angle = -0x80 - angle -- addi.b #$40,(a4) \n neg.b (a4) \n subi.b #$40,(a4) end -- + xcopy = AND(objX, 0xF) + block -- andi.w #$F,d1 \n add.w d0,d1 local colhgt = memory.readbytesigned(ColArray + xcopy) -- lea (ColArray).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(0xB)) ~= 0 then -- btst #$B,d4 \n beq.s + colhgt = -colhgt -- neg.w d0 end -- + if colhgt == 0 then -- tst.w d0 \n beq.s loc_1E7E2 -- loc_1E7E2 floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask) return floordist + 0x10, tileaddr, angle elseif colhgt < 0 then -- bmi.s loc_1E85E -- loc_1E85E: 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 loc_1E7E2 -- loc_1E86A: 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 loc_1E86A -- loc_1E86A: -- 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 -- loc_1E7E2: -- 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) return floordist + 0x10, tileaddr, angle end -- Lua version of the Sonic 2 function to find distance to the floor. local function ObjCheckFloorDist(objX, objY, objH, objR) --local objX = memory.readword(obj+x_pos) -- move.w x_pos(a0),d3 --local objY = memory.readword(obj+y_pos) -- move.w y_pos(a0),d2 --local objH = memory.readbytesigned(obj+y_radius) -- moveq #0,d0 \n move.b y_radius(a0),d0 \n ext.w d0 --local objR = memory.readbyte(obj+render_flags) --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(0xC), 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 2 function to make an object "fall". local function ObjectMoveAndFall(objX, objY, objVX, objVY) --local memory.readlong(obj+x_pos) -- move.l x_pos(a0),d2 --local memory.readlong(obj+y_pos) -- move.l y_pos(a0),d3 --local memory.readwordsigned(obj+x_vel) -- move.w x_vel(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+y_vel) -- move.w y_vel(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,y_vel(a0) return objX, objY, objVX, objVY -- move.l d2,x_pos(a0) \n move.l d3,y_pos(a0) \n rts end -- Lua version of the Sonic 2 function loc_11ADE. local function animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla) -- tst.b render_flags(a0) -- bpl.w DeleteObject objX, objY, objVX, objVY = ObjectMoveAndFall(objX, objY, objVX, objVY) -- bsr.w ObjectMoveAndFall if objVY >= 0 then -- tst.w y_vel(a0) \n bmi.s + local floordist = ObjCheckFloorDist(SHIFT(objX, 16), SHIFT(objY, 16), objH, objR) -- jsr (ObjCheckFloorDist).l if floordist < 0 then -- tst.w d1 \n bpl.s + objY = objY + floordist -- add.w d1,y_pos(a0) objVX = objNVX -- move.w animal_ground_x_vel(a0),x_vel(a0) objVY = objNVY -- move.w animal_ground_y_vel(a0),y_vel(a0) -- move.b #1,mapping_frame(a0) -- move.b animal_ground_routine_base(a0),d0 -- add.b d0,d0 -- addq.b #4,d0 -- move.b d0,routine(a0) -- tst.b objoff_38(a0) -- beq.s + if AND(vbla, BIT(4)) ~= 0 then -- btst #4,(Vint_runcount+3).w \n beq.s + objVX = -objVX -- neg.w x_vel(a0) objR = XOR(objR, BIT(0)) -- bchg #0,render_flags(a0) end end end -- + -- 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+x_pos) objY = memory.readlong(obj+y_pos) objVX = memory.readwordsigned(obj+x_vel) objVY = memory.readwordsigned(obj+y_vel) objNVX = memory.readwordsigned(obj+animal_ground_x_vel) objNVY = memory.readwordsigned(obj+animal_ground_y_vel) objWait = memory.readword(obj+0x36) - 1 end -- Compute time to get offscreen local le = SHIFT(Camera_X_pos - objW, -8) local re = SHIFT(Camera_X_pos + 320 + objW, -8) if objX <= le or objX >= re then return 0, vbla 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 -- 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_body+x_pos) 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 if m < capsule_switch then endpt = endpt - 1 end 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 main_capsule = find_capsule() if main_capsule == nil then -- If it was not found, so update variables and leave last_seed = memory.readlong(RNG_seed) last_vbla = memory.readbyte(Vint_runcount+3) return end capsule_body = 0xFFFF0000 + memory.readword(main_capsule+0x3C) capsule_switch = 0xFFFF0000 + memory.readword(main_capsule+0x38) -- Update random seed to RAM value seed = memory.readlong(RNG_seed) -- Capsule switch's routine counter local rout = memory.readbyte(main_capsule+routine_secondary) -- V-Int counter, used for pseudo-random numbers local vbla = memory.readbyte(Vint_runcount+3) -- 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 == 0 and memory.readword(capsule_switch+0x32) ~= 1 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 memory.readbyte(capsule_body+routine) == 0xA 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(Current_Zone) local types = typelist[zone] local le = Camera_X_pos - 8 local re = Camera_X_pos + 320 + 8 if rout == 0 then -- Waiting for initial animals to be created. row = 20 gui.drawtext(4, row, "Initial animals:") row = row + 8 gui.drawtext(12, row, "Animal Wait D Exit at") row = row + 8 local capX = memory.readword(main_capsule+x_pos) local capY = memory.readword(main_capsule+y_pos) row = row + 56 -- Initial animals for i = 1, 8 do -- Random number determines kind of animal. local rand = RandomNumber() local ty = types[AND(rand, 1)] local objX = SHIFT(capX - 0x1C + 7 * i, -16) local objY = SHIFT(capY, -16) local delay = 0x9A + 8 - 8 * i if le <= objX and objX <= re then 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]) end row = row - 8 end end print_animal_objects(vbla) if rout >= 2 then -- Single animals are being generated. -- 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 dt = 7 - AND(vbla, 7) local timer = (memory.readbyte(capsule_body+routine_secondary) == 2 and memory.readword(capsule_body+anim_frame_duration)) or 0xB4 local capX = memory.readword(capsule_body+x_pos) local capY = memory.readword(capsule_body+y_pos) for i = 1, timer + 1 do if AND(vbla + i - 1, 7) == 0 then -- First random number is used to determine the position -- offset from the switch. local rand = AND(RandomNumber(), 0x1F) - 6 local pos = (seed >= 0 and rand) or -rand -- Second random number determines animal type. rand = RandomNumber() local ty = types[AND(rand, 1)] if i - 2 > 0 then local objX = SHIFT(capX + pos, -16) local objY = SHIFT(capY, -16) local delay = 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 "<" if le <= objX and objX <= re then 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 end 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 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}) gui.drawtext(116, saverow, string.format("Animal spawns: %d left", animals_left)) end end last_seed = memory.readlong(RNG_seed) last_vbla = savevbla return end gens.registerafter(predict_animals) savestate.registerload(predict_animals)
Marzo Junior