Joined: 5/8/2023
Posts: 2
Hi, I searched in the Sega Genesis section for a topic on this game but I only found one for the SNES version, which seems to work technically different in at least the aspect I am studying, so I thought it'd be a good idea to create a topic for it. For anyone not familiar with the game, the main game happens in a roulette wheel, which works like a Level Select screen. The roulette spins over and over again, until the player presses a button. Once a button is pressed, the roulette slowly slows down, until a level is selected. I am trying to understand the inner mechanics of said roulette so I can manipulate which level to do first. In the SNES version, this is not necessary as the first time the roulette works always the same way: if the player presses a button in a certain position of the roulette, then it will always stop at the exact same point and so the first level can be selected easily. The times after the first one, it seems to follow other mechanics though, but I am not sure if the mechanics are the same as in the Sega Genesis or not. Given that the first time the mechanics are for sure not the same, I'll assume they are still different and so I am focusing on the Sega Genesis only (as far as I know, there's no analysis on how it works for the SNES the times after the first though, so in that sense, maybe the clarification is not necessary, but still, just to give more context). So far, what I discovered is that $C1EA is a 2-byte value that contains the position of the roulette, while $C1EF is a 1-byte value that contains the speed of the roulette. As long as the player is not pressing any button, the roulette is spinning at a constant speed of C0, no acceleration, no changes, just increments of C0 every frame. Part of this I discovered by using the Trace Logger and the Debugger in BizHawk (inserting a breakpoint whenever $C1EA/$C1EF were being written and Ctrl+F on the file written by the Trace Logger to have the full picture), and another part by just monitoring the addresses with RAM Search/RAM Watch. I also discovered that what is actually random is not the rate at which the speed decreases every frame after the player presses a button (that's what I thought), but actually the moment at which the speed starts decreasing (I also discovered this with the help of breakpoints). After the player presses a button, the speed can start decreasing after 9 frames, or after 10 frames, or 32 frames, or 22 frames, or other values (I haven't been able to find the range of possible values) and this causes the roulette to stop at seemingly random points. After the decreasement starts happening, the speed slows downs by 1 each frame (so in other words, the acceleration is a constant -1). And what I also discovered is that the game continues to process data even during lag frames, which really called my attention. So to the naked eye, Bart (or the roulette) might appear to be in one position, but internally it's in another. In regards to the ASM code (it's Motorola 68K code more specifically), I am leaving this analysis in a separate paragraph as it's where my doubts are. So what I noticed is that every frame while the roulette spins, this instruction 005F12: 4ED6 jmp (A6) is being run on every frame, also when the roulette starts slowing down. But what makes the speed decrease is the address contained at A6: when the roulette starts slowing down, it contains the address 0087AC, where the instruction 0087AC: 5338 subq.b #1, ($C1EF) is contained. Whereas when the roulette is still spinning, it contains other addresses, like 005488. Now, I know this is not really random, but rather pseudo-random, so I am trying to tie together the final pieces of the puzzle, but I am a bit lost now, so I wanted to ask for some help to you TASers. And also, I wanted a review of my work if possible (advice, things I could have done another way, etc. etc.). Do you know of any ways of setting breakpoints whenever a register changes values? Or do you have any other suggestions on how to solve the final pieces of the roulette's inner mechanics puzzle? In particular, what exactly makes A6 have the address 005488.
Active player (399)
Joined: 10/4/2015
Posts: 98
You can save yourself time just by trying every frame until you get the level you want. By your own analysis you shouldn't have to try more than 100 frames.
Joined: 5/8/2023
Posts: 2
Meerkov wrote:
You can save yourself time just by trying every frame until you get the level you want. By your own analysis you shouldn't have to try more than 100 frames.
Oh yeah, I continued this discussion on Discord, I’ve been planning to update on here for quite a few weeks ago but I was always postponing it for later. I am going to take this opportunity to update. Still thanks for your suggestion Meerkov! So I had two objectives here: one was to optimize a way to select the first level, and the second was to find a way to consistently select a given level for a real-time run. After a lot of further analysis, we found that the game actually uses the H/V counter from the Genesis' VDP (Video Display Processor) to decide when to start stopping the roulette. More specifically, whenever the player presses an input, it asks itself whether it is Start, A, B or C (though I don't remember off the top of my head if it was at that order, but it's done via bit testing of RAM address 0xC772, which stores the current inputs pressed), and then proceeds to update 0xC1E8, in this way: it reads from 0xC00008 (H/V counter of VDP, or current electron beam position in CRT television), it performs an AND bit operation on the least significant 5 bits and then stores the result in 0xC1E8 (or in shorter words, it stores the result of 0xC00008 & 1F). Then the roulette remains at speed 0xC0 for the amount of frames in 0xC1E8 + 1, since speed only starts decreasing when it reaches -1. So in even shorter words, the formula is (assuming the button wasn't pressed on a lag frame, which by the way, I also learned that lag frames have nothing to do with frame processing happening or not, just with inputs being polled or not, so it makes sense that the game continues to process data during lag frames, oh and also assuming that this is being done on emulator, since on console there's no input lag and therefore the formula will look slightly different, maybe it's not necessary to multiply by 2 at the beginning): C1EAf = (C1EAi + 192*2 + 192*(C1E8 + 1) + 9120) % 9216 Here: f: final value i: initial value I used decimal representations for the formula because honestly it was giving me such a headache in hex 😄 . Of course, it could be even more shortened, but I prefer it this way for being more explicit about how the calculations are being done. Oh and the 9216 is because that's the value after which the roulette just restarts (so 9215 + 192 results in position 191 and not in 9407). And the 9120 is because that's the amount advanced after speed starts decreasing (it's pretty much a whole lap so for strategy optimization purposes it could be ignored, but still, for the sake of accuracy and completeness). There are quite a few conclusions to get from this formula, which are extremely helpful particularly for real-time runs: If you press the button on any given position, there is at least one level which will absolutely never be selected (this comes from calculating the minimum and maximum possible values of C1EAf based on the fact that C1E8 can only be between 0 and 31, both inclusive), and depending on the exact position at which you press the button, there can be two levels (including the life +1/-1 "level") with less chances of being selected. So in other more practical words, if you press the button at certain position, there's around 20% chances of getting 1 out of 4 levels (two have a much lower chance, something like 10%). This can be particularly important for reducing the amount of resets to get a level among the first (for a speedrun, this means riskier strategies with a lower cost, for a No Damage run, this means you get to do a , and it can mean different things for other types of real-time runs of course, the info is out here to be used as each player deems necessary). For a TAS, it means you can play with pressing different input combinations during a level and/or the roulette (Start, A, B or C) to get the next level you want, which can be the one that makes C1E8 have the smallest value (so the roulette stops spinning faster, saving a bunch of frames). This is not all but I mean this post to be exhaustive, so here's some additional information: I think I didn't mention it in the original post, but the reason why freezing different memory addresses affect the final level selected is because another way to see the RNG is to think in terms of "how much code has run until the VDP read happens". And if there are some memory addresses frozen, that is going to impact the amount of code run, as some branches are going to be taken, some won't, and overall this is going to make the code reach the VDP read at different points, thus changing the final result. So in other words a morale of this story is that if you are freezing random memory addresses and you get different results, there may be an H/V counter behind, so don't always be looking for RNG addresses, it doesn't always work that way. Also, the H/V counter is handled differently on console than on emulator. On console, the H/V counter is truly random (not pseudo-random) because its value depends on the current electron beam position of the CRT television, which is pretty much impossible to know beforehand. And in this sense, on console, every time you turn on the Sega Genesis you'll get a different initial H/V counter, so it's going to be impossible to fully manipulate it (you can still use the formula above to get higher chances of getting the level you want though!). On emulator, and depending on which emulator you use (Gens, Kega Fusion and BizHawk all handle H/V counter differently), you can manipulate the H/V counter because it always starts with the same value. Not sure why, if anyone knows, please let me know, I'd love to know! (if I had to guess though, I'd say it's because it's a very hard randomness source to replicate in emulation, and it would make TASes impossible to reproduce consistently, so to preserve mental sanity, the deterministic approach was chosen). In BizHawk, using genplus-gx core, there's a 5-frame window at the beginning of the game (if you hold Start from the beginning of the game and skip the opening as fast as possible, then this is from frame 738 to frame 742, both included), in which if you press the start button and keep holding it for buffered inputs, you will ALWAYS get the water level first. Also, for the sake of completeness, you actually can't just buffer Start from the power-on, since after skipping the opening, the game stops buffering it (probably to avoid a player accidentally both skipping the opening AND selecting Start Game instead of, say, Practice Area, in case they wanted to practice first), so you have to let go of Start and then press it again in any frame you want. For the 5-frame window, I am not sure if it will be reproducable on other screen/display settings, but for me, one of the Is in the background gets to the right vertical border of my monitor during the 5-frame window, so I press there and I can get it very consistently. And why would you want the water level first and chosen this way specifically? Well, it turns out the game does use a pseudorandom generator for some events in the game, and it's contained in C676. The exact algorithm is as follows: (prev_val*0x8705) + 0x3619) % 0x10000 So an LCG (Linear Congruential Generator). And the interesting thing is that it doesn't run every frame. It actually only advances whenever it is used, so it can advance more than once per frame, only once, or not advance at all! And since it doesn't advance until the roulette starts spinning, if you hold Start and get the water level first for example, you can get a very convenient route. And you can play with this to get the route that you want, the fastest, the safest, etc. etc. This information is of course helpful for TASing the game too. Oh and more thing that I was forgetting. After a level is completed, the algorithm is slightly changed: if after C1E8 reaches -1, Bart is currently at a position such that the currently selected level is already completed, then Bart will continue spinning, until Bart's position reaches a non-completed level, and only THEN his speed will start decreasing. So in practice, this means that if for example the water and picture/orange levels have already been completed, then you get THRICE the chance of getting the motorbike level selected next (assuming you keep pressing the button always on the same location of course). For a TAS though, this of course means more frames spinning, so yeah, it's definitely something to play with and see what works and what doesn't. There are times, however, when for whatever reason after Bart's speed reaches 0, the level selected is a completed one. In this case, Bart "magically" (I mean, it's kind of funny how it happens, it's almost as if black magic is making Bart move) keeps moving, very slowly, until it reaches a non-completed level. This is very rare though, I think I tried for around 30 times so far and it only happened once so far. I am still learning about the game mechanics and I plan to post further updates further on the road, but so far I found that the RNG (the pseudorandomly generated one) only advances during the water, pig, and picture (or orange as I like to call it) levels. And as the roulette spins of course. Reminder for myself: upload a .wch file with all the RAM addresses information.