Row scrolling for parallax effects

In the last post we looked at what tilemaps are and how they’re used in SF2, and ended with a mention of how multiple layers (or scrolls) of tilemaps can be moved at different amounts to achieve parallax scrolling, creating the illusion of depth.

Elephants overlapping by different amount depending on the camera position.

This really only works for elements that are standing upright, though. There’s no way we can use this effect for horizontal surfaces like floors and ceilings – we’d either need to use a lot more layers which we don’t have, or a huge amount of tiles to represent different perspective angles, which we’d rather not.

For horizontal surfaces like floors and ceilings, SF2 uses another technique called row scrolling (a.k.a. line scrolling).

We saw in the previous post that the CPS1 has three layers of tilemaps which can be scrolled independently: Scrolls 1 through 3. One of these layers is special, and allows for each individual row of pixels to be scrolled horizontally, in addition to the entire layer itself. We can see this effect on the the floor here:

Two different perspectives of the floor: one in the centre, compared with one at the far left of the stage.

This layer is Scroll 2. In SF2 we can think of this as the “main” scroll. The other two scrolls and the sprites are always positioned somewhere relative to this main scroll’s position, and the Z-depth of those scrolls.

We can think of an imaginary line just underneath the feet of our fighters as having a Z-depth of zero. A small amount of floor in front of the fighters has a negative Z-depth, with background elements having some positive Z-depth.

As the camera moves horizontally, elements with a negative Z-depth move more than elements at Z=0, which in turn move more than elements with a positive depth. Elements very far away (in photography and modelling we call this “at infinity”) don’t move at all.

This matches our experience with the real world, for example riding a train and looking out the window a far away mountain. In the space of a minute, the mountain seems not to move at all, nearer features appear, cross our vision and disappear from view, while the fence bordering the railways corridor whizzes past, the only object giving us a true sense of the train’s speed.

Let’s take a look at E Honda’s stage as it is the most interesting. All of the SF2 stages have a floor with parallax scrolling, only a few also have a ceiling, but E Honda’s also has a nice bath in the centre of the stage. Let’s look at how that stage is layered:

The three tilemap layers, Scrolls 1, 2, and 3 from left to right.

We have Scroll 1, with the front surface of the bath, Scroll 2 containing the floor, ceiling and the top surface of the bath, and Scroll 3 with the background.

The three layers combine with the sprites to create the full image shown below. Ken and Ryu’s sprites are anchored to Scroll 2, water dripping from the bath and a small tub are anchored to Scroll 1, which contains the front surface of the bath.

Note that while Scroll 1 is drawn on top of the other layers, the object it contains appears between the floor and the top of the bath. This works, because Scroll 1 moves as if it were located there. The key thing here is that the layer’s priority (which layers it is drawn on top of) and the layer’s displacement are two different things.

Note also that the scrolls don’t have to be layered with 1 on top and 3 at the bottom, it just happens to be this way for E Honda’s stage. Ryu’s stage uses Scroll 1 for drawing the sky, which places it underneath all the others. The clouds in the sky fly past independently of our movement on the stage – parallax isn’t used for it all.

Consecutive rows in Scroll 2 are grouped together depending on which surface they belong two, and each row within that group is displaced by a certain amount to create a shear transformation. I’ve drawn a rectangle around the floor area so its effect can be easily seen:

Three different floor perspectives, the extreme left of the stage, the centre, and extreme right.

In the centre of the stage, the horizontal offset is zero, and the tiles are drawn unmodified. As we move away from the centre of the stage, the rectangle becomes more and more skewed, and the entire layer also scrolls, giving the appearance of a flat horizontal plane behaving as if we were looking at a 3D scene.

Why can’t the front of the bath just go in Scroll 2? Because there are 16 rows of floor pixels that are beyond it. If the bath were in the same layer, it would be sheared along with the floor tiles, which wouldn’t look right. You can see the sheared floor rows disappearing behind the bath here:

The same effect is used on the ceiling and the top of the bath. Nothing remarkable happens with Scroll 3, the most distant, background layer, until the round ends and man holds up his sign. (I used to think it was meant to be some sort of candy bar commercial, but it turns out the kanji says something like “round complete”).

This has been a visual overview of how the CPS1 uses row scrolling in addition to layered tilemaps to create parallax effects. In the next post, we’ll look under the hood at the code that makes all this happen.

Row scrolling for parallax effects

What are tiles and scrolls?

[This post is a prologue to an upcoming post about parallax scrolling in Street Fighter 2]

Most games of the SF2 era were tile-based, meaning the graphics weren’t drawn in individual pixels, but with square tiles of pixels. This helped reduce the RAM that would be required to store individual pixels (often in multiple layers) and also reduce work for the CPU to update animations.

The Capcom Play System hardware which SF2 ran on uses three different tile sizes: 8×8, 16×16, and 32×32. Background graphics are displayed these tiles in three layers, each using one of these tile sizes, and the layers can be arranged in any order from foreground to background; transparent areas in one layer reveal the layers beneath it. These are called scrolls.

On top of these three scroll layers is the object layer, where sprites such as the playing characters, projectiles, some stage animations, as well as the energy gauge and score display. Tiles in the object layer are 16×16 pixels.

Let’s take a look at a diagnostic screen in SF2 which lets us look at individual tiles:

Some of Ryu’s tiles, each 16×16 pixels, numbered with a unique ID. I’ve displayed them all separated by gaps so you can see how big one tile is.

Often, the tiles are stored in ROM in a natural order as they are above so it’s easy to visualise the fully assembled image, but they don’t need to be. As far as the graphics hardware is concerned, each tile is selected by its identifier, and can be placed next to any other tile.

Many of the tiles are reused for different animation frames. For example, if Ryu’s legs look the same whether he’s standing or throwing a short punch, each of those tiles can be reused for that frame. This saves space in the tilemap ROMs. Background graphics especially are designed to exploit this, usually feature large areas of the same tile, or group of tiles, repeating.

Each pixel in a tile is assigned one of sixteen indexed colors, with the last color always being transparent. The graphics hardware looks this indexed color up in a palette, which determines the red/green/blue components of the pixel color, which is then finally drawn on-screen. In this way, tiles can be reused in different colors. This is used a lot for Ryu and Ken, who differ only in tiles containing their faces, and the color of their uniform.

Let’s take a look at that:

Some tiles of Ryu throwing a Hadouken. We can also some tiles of Ken’s head, but they don’t look right, because we’re displaying these tiles with Ryu’s palette.
Exactly the same tiles, but with Ken’s palette. Now Ryu’s head doesn’t look right, but those tiles won’t be used with this palette.

SF2 Championship Edition, which allowed fights between two of the same character also used this effect so each of them could be distinguished.

Additionally, each tile can be flipped horizontally or vertically. For example, a player facing left uses exactly the same tiles and if they were facing right, only flipped.

This is why Sagat’s eye-patch changes from one eye to the other when he turns around, and why a Shoryuken is thrown with a left arm when facing left, and vice versa. To do otherwise would have required a different set of tiles depending on which way the player was facing, which would have complicated the animation engine also.

Now let’s talk about the scroll layers used for the background. One limitation with these is that each layer is arranged in a single large grid. Tiles drawn this way can’t be moved around by individual pixels like the sprites can, so there’s a limit to what kind of animations that can be used there.

In SF2, animations in the background layers are constrained to simple sequences of a few repeating frames, such as these elephants, which move their trunks up and down, but don’t otherwise move around relative to the other tiles in the same layer. If they did, it’d need be by a multiple of 8, 16, or 32 pixels depending on what layer they were in.

However, each of the scrolls themselves can be moved with one-pixel resolution. The most noticeable use of this is seen as we move left and right along the stages, which – apart from the bonus stages – are wider than the screen.

But a more subtle effect (well, it was subtle for me, whose attention was mostly drawn to fighting, and usually losing) involves moving the scrolls by a different amount relative to each other, to achieve parallax scrolling and create the illusion of depth, or 2.5D:

Both players are near the middle of the stage, note the three elephants
Near the extreme left, note the elephants are starting to overlap each other

Another example of parallax can be seen on the floor. Note the orange rug changes perspective as we move across the room. This effect can’t be achieved with tiles alone, or at least not without creating lots of them for all of the different perspective angles.

This is a graphics effect on the CPS1 called line-scrolling (or row-scrolling), which involvs each individual row of pixels being translated left/right by a certain amount, and will be the topic of my next post. Until then!

What are tiles and scrolls?

Getting started in Reverse Engineering

In this post, I’m going to outline a few areas of study that will help you get started reverse engineering a game, and outline the method I took with World Warrior. I won’t be going into too much detail that can already be found elsewhere, only pointing you in the right direction so you can do your own googling 🙂

To get stated, especially using the MAME debugger, you’ll at least need to become familiar with the 68000 CPU assembly instructions and how the processor works. While it’s hard to write code in assembly, it’s not actually that hard to learn the instructions and what they do.

Once you’ve learned the basic instructions, such as MOVE, TST, ADD, SUB, and Bcc, you’ll be able to play around in the MAME debugger to get familiar with how the CPU processes the instructions, what effect the different instructions have on the CPU status register, and how the different branch (Bcc) instructions work.

Next, you should learn about breakpoints and watchpoints in the MAME debugger. Breakpoints stop the debugger whenever execution reaches the address where you’ve set the breakpoint. You can use this to help confirm whether a piece of code does what you think it does – the breakpoint will “hit” (and the emulator will pause) whenever that address is reached.

For example, if you suspect a piece of code is responsible for creating a fireball, you can set a breakpoint at that address, and play the game, ideally in 2P mode so you don’t have to worry about a computer opponent acting by themselves. See if the breakpoint:
1) Always gets hit whenever you execute a fireball
2) Never gets in any other cases

If both of these are true, you’ve probably confirmed your suspicion, but keep an open mind: Does it only hit for Ryu’s fireball? Does it hit for Ryu and Ken? Or does it hit for general projectiles being fired for any player? Always be careful in your assumptions. As you progress you’ll make notes of the different things you’ll find. Make sure you’re not misleading your future self reading these notes again months, maybe years later, well after you’ve forgotten the context in which you wrote them.

The MAME Cheat XML files will be a lot of help to you, as they reveal the addresses of variable for things such as energy levels, time remaining, etc. which will be valuable in deciphering the code. This brings us to Watchpoints.

Watchpoints are similar to breakpoints, except they involve variables instead of instructions. Maybe you want to learn about how a player’s energy is adjusted. A good place to start might be to set a watchpoint on the address that holds that energy variable, and you can learn many such addresses from a cheat file.

When you set up a watchpoint, you can tell the debugger whether you want it to stop whenever that variable is written, read, or both. A ‘write’ watchpoint is useful to finding out what code is making changes to that variable, while a ‘read’ watchpoint can help you find what code is using that variable later on.

For example, a write WP on the energy variable will probably reveal the code that calculates damage, while a read WP is likely to be hit by a graphics routine that draws the players energy bar on screen (and also a number of other things, such as AI routines that make decisions based on how much energy their opponent has. Again, be careful in your assumptions).

Lastly, you’ll want to build a Memory Map of the game. This is a table of address ranges and what they are connected to. Looking at the MAME source for the driver of your game will help you with this, and they will be similar for all games on the same platform type.

A CPS1 game will have a memory map like this:

0x000000 to 0x3fffff Game ROM
0x800000 to 0x800040 Player inputs, coin counters, etc.
0x800100 to 0x80017f CPS chipset functions
0x800180 to 0x80018f Commands to the sound CPU
0x900000 to 0x92ffff Graphics RAM
0xff0000 to 0xffffff RAM

Keep a text editor window handy with this memory map until you memorise it. Eventually you’ll instinctively see a write to some address in 0x900000 and know it’s a graphics write you’re looking at.

That’s all for today. In my next post, we’ll take the bull by the horns and generate a machine language dump of the whole ROM set.

Getting started in Reverse Engineering

I’m back!

I’ve had a bit of contact through the blog lately, and have been helping someone who’s starting out on reverse engineering another game but doesn’t have much reveng experience, so I’m going to compile my communications with him into a series on how to get started on a project like this.

Most of it will be specific to m68k CPS1 games since that’s where I’ve got the most experience, but some of it might be useful for other CPUs and platforms.

I’m back!

Behind the name

I saw this blog got a lot of attention in the last few days, so I though I’d at least post to say I’m still around, even if I haven’t done much recently. I even woke up the old twitter account, so you can follow me there if you like:

https://twitter.com/sf2platinum

Here’s the story behind the name: Back in 2011 the 20th anniversary of SF2 was coming up, and I wanted to get a release of my own SF2 implementation out in time, ‘platinum’ being the stone associated with 20th anniversaries.

Unfortunately, life took a few turns that saw me far too busy to come through with it, and it never panned out. Sorry.

Since then I’ve come back to the project from time to time, but never as dedicated as I was in 2011. I’d like to keep making progress whenever I can, but things are still pretty busy with other projects that pay the bills.

Behind the name

The AI Engine

By today’s standards, the Artificial Intelligence in Street Fighter 2 World Warrior isn’t very sophisticated. These days, when most people talk about AI they’re talking about machine learning. There’s not any of that in SF2. Anyone looking for some insight into how to write an AI engine for a game today will be disappointed.

Moves made by computer opponents are not made independently but are instead grouped into small scripts, written in a bytecode similar to machine language. A computer avatar has a repertoire of different scripts for each opponent they could face in the game*, and set of circumstances, such as a nearby fireball.

Instructions in the scripts can command the avatar to execute an attack (punch/kick/special/throw), walk or jump somewhere, and wait – either for a timer, or some condition such as the ability to throw another fireball.

Other instructions can directly manipulate variables in the AI state machine, test for certain conditions, and form primitive IF…END blocks. Here’s one of Ryu’s typical ‘easy’ attack routines: Throw three fireballs at you, and if you’re somehow silly enough to catch all three and get dizzy, run up to you and throw you.

ORG 0x99c88 sf2ua.bin
0x02,                   ; script header, type 2 
0x10, 0x50, 0x04, 0x00, ; throw a hi str fireball
0x00, 0x80,             ; wait until I have no fireball
0x10, 0x50, 0x04, 0x00, ; another hi str fireball
0x00, 0x80,             ; wait again
0x10, 0x50, 0x04, 0x00, ; another hi str fireball
0x92,                   ; are they dizzy?
0x04, 0x00, 0x18,       ;    walk until we're within 24 pixels  
0x00, 0x82,             ;    wait if they're still getting up
0x10, 0x84, 0x00, 0x00, ;    throw(4)
0x94,                   ; end if
0x00, 0x00, 
0x00, 0x00,             ; wait four frames
0x00, 0x00, 
0x00, 0x00, 
0x86,                   ; chain to another randomly chosen script

Apart from the header byte, the first byte of each row is the instruction byte. Instructions are grouped into avatar commands (0x0-0x7f) and control flow / variable access (0x80 – 0xff). Each of them mostly have a fixed number of parameters, for example the Attack instruction (0x10) always take three:

  1. The attack type (special moves are 0x50, 0x52, 0x54…, throws are 0x80, 0x82, 0x84…).
  2. The strength of the attack (in this case it’s a strong / fast fireball)
  3. A repetition count for attacks involving holds and multiple hits. (unused here)

 

The Wait (0x00) instruction usually takes one parameter, decoded as:

  • 0x0 – 0x7f: Wait for N frames
  • 0x80: Wait until I am able to throw a fireball
  • 0x81: Wait until opponent is within M pixels (additional second parameter)
  • 0x82: Wait until my opponent is attackable
  • 0xc0: Wait until opponent’s jump reaches height M (second param)

All of the instructions and many of the parameters are even multiples of two (there is no 0x01 instruction, for instance) so that they can be used directly against 16-bit jump tables. Low/mid/high strength translate to 0, 2 and 4, as are most internals in the game. Any odd numbers would cause a CPU bus error exception, which would result in the SF2 ROM restarting.

The AI engine has three main modes of operation:

  1. Waiting for an attack. Simple scripts are chosen at random which consist mainly of walking backwards/forwards small amounts.
  2. Actively attacking. Scripts such as the one above are selected.
  3. Reacting to an attack. Scripts suitable for countering the attack are selected. Sometimes. Depending on the AI difficulty setting the computer lets plenty of attacks through unguarded, of course.

For the first two modes, there are 8 levels of scripts, which are chosen based on how much time is left in the round. When reacting to an attack the scripts are chosen based on something called a yoke.

Each frame of animation for both avatars and projectiles contains a value for the yoke in the metadata, which the AI peeks at to select a script suitable for responding to that attack. The computer sees the yoke of your move as soon as you have input it, before the first animation frame has even displayed. As such it gets one more frame of advantage on top of your reaction time.

A question addressed in an earlier post in this blog was whether the AI “cheats”, and it certainly does. Charge moves such as blade kicks are simply executed as instructions, so they cannot fail. Guile can do a bladekick from a standing position simply because that’s what’s in the script. It’s probably possible for the AI to command special moves in the air. One instruction is available to disable collisions against the computer player for a certain number of frames. Using it, you could write a script to simply walk through an approaching fireball.

One of the (more hidden) test screens in the game performs a sanity check on the AI bytecode. It’s not immediately obvious what it’s for as it just displays “OK” and nothing else, but the disassembly reveals the developers were perhaps testing and developing the AI code on CPS machines. The error messages in the test code reveal names for some of the instructions as MOSI, KON, KYON, TIGA, TAN, GTAN and END IF, but apart from the obvious latter name I haven’t figured what they might mean.

Viewers at home can find Ryu’s AI bytecode starting at ROM address 0x9966e on sf2ua. Set a breakpoint at 0x2ad2a to stop at the main entry to the AI code. Avatar AI state variables start at 0x200 from the player struct (0x5c6 and 0x8c6 for P1/2 respectively on sf2ua).

[* WW contains no scripts for battling the four bosses since that shouldn’t happen, and in any case the formulae for each of the opponents are all the same anyway! Maybe the developers ran out of ROM space or time, but the WW computer players use the same formula no matter who they’re fighting]

The AI Engine

Not just code, but data too

Originally the project included a lot of data structures dumped directly from the ROMs, such as sprite animation, palettes, hitboxes, AI bytecode and more. The only data not ported were the actual tiles; the game depended on four tile ROMs from the original game to be present.

Since all this data from the code ROMs is a considerable part of the original code, I decided to pull it all out and require the entire ROM set to be present, basically as much as you’d need to run the game in Mame.

This has its own difficulties, for a start we need to do endian-swapping each time we read anything larger than a byte. I wondered how much this would impact performance, but it turns out not very much. Some CPUs even have special instructions just to do this.

It also has some benefits. Getting the data out of the ROMs and into C-style structs and byte arrays was error prone, and the utility I wrote to do this sometimes messed up and produced incomplete / corrupt data. Many arrays are jagged. Sometimes sentinel elements are used. Sometimes I had to guess lengths of arrays and got it wrong. Going to the ROMs for all this ensures nothing is missing.

I wrote a compatibility layer to take care of locating these structures and doing the necessary byte-swapping.

As an example, here’s how an 2D jagged array of words would be stored.

00001ff0 lea 0x2000(%pc), %a6    ;get address of row table
00001ff4 move.w (%pc, %d0), %d0  ;get offset of row pointed by %d0
00001ff8 lea (%d0, %a6), %a6     ;add offset to base address
00001ffc jmp some_routine_that_uses_this_data

00002000 dc.w 0x0008   ;first row starts at 0x2000 + 0x8 = 0x2008
00002002 dc.w 0x0018   ;next row starts at 0x2000 + 0x18 = 0x2018
00002004 dc.w 0x001a   ;and so on
00002006 dc.w 0x0020

00002008 dc.w 0x1234   ;first element of first row
0000200a dc.w 0x5678   ;second element of first row
....
00002016 dc.w 0x8888   ;last element of first row
00002018 dc.w 0x1235   ;first (and only) element of second row
....
0000201a dc.w 0x1236   ;first element of third row

I guess they did it this way to save a little space at the cost of a couple of extra instructions, as opposed to just using 32-bit pointers, although there was plenty of free space in the ROMs.

Switch-style statements are very similar (and very frequently used throughout the code). Case variables are often kept to even numbers to avoid having to do pointer arithmetic. An example from the state-machine:

000077dc  302d 000c   move.w (game_mode_3, %a5), %d0
000077e0  323b 0006   move.w (#6, %pc, %d0.w), %d1
000077e4  4efb 1002   jmp    (#2, %pc, %d1.w)
;the jump-offset table follows
000077e8  000a        dc.w 0x000a ;jump to 0x77f2
000077ea  0016        dc.w 0x0016 ;jump to 0x77fe
;... several more ...
; here is case 0:
000077f2  546d 000c   addq.w #2, (game_mode_3, %a5) ;next time do the next case
000077f6  4eb8 2794   jsr 0x2794   ; reset display state
000077fa  4ef8 2c8a   jmp 0x2c8a   ; initialize both players (tail call)

000077fe  546d 000c   addq.w #2, (game_mode_3, %a5) ;again, next time to the next case
00007802  4eb8 1698   jsr 0x1698   ; set up palettes
00007806  4ef8 1ae2   jmp 0x1ae2   ; set up scrolls (tail call again)

This is more or less equivalent to

switch (g_game_mode_3) {
    case 0:
        g_game_mode_3 += 2;
        reset_display_state();
        init_players();
        break;
    case 2:
        g_game_mode_3 += 2;
        set_up_palettes();
        set_up_scrolls();
        break;
    /* more cases */
}

If the case is odd, the machine will crash with a bus error exception. This is why 2 is added to g_game_mode_3. If the case is greater than the number of elements in the jump-offset table, the behaviour is undefined. I’ve put default clauses in all my switch statements to land in the debugger to let me know something’s up.

Not just code, but data too

Random number generator

The random number generator is pretty simple. A 16-bit seed is stored which for convenience in code is split into two 8-bit values. The routine is at 0xdd4 in the sf2ua ROM. Here’s the equivalent C code:

short sf2rand(void) {
    int x = (g.randSeed1 <> 8;
    g.randSeed2 += x;
    g.randSeed1  = x;
    return g.randSeed2;
}

As far as PRNGs go it’s pretty poor, but good enough for our purpose here. The output of it is often bitmasked to limit its values (e.g. masked with 0xf to get a number from 0-15, masked with 0xe to get an even number from 0-14, and so on), and also used as a bit index with the BTST instruction to get a 1-in-N chance.

For example, to get a 1-in-4 chance:

if( 0x88888888 & (1 << (sf2rand() & 0x1f)))

The RNG is seeded with exactly the same value every time the machine starts (there’s nothing else to seed it with, no RTC, nothing), so it always produces the same string of random numbers.

If you start the machine, wait for the player invite screen to start, insert a coin and play with Ryu, you will face Dhalsim on your first fight. If you insert your coin while the copyright warning is still printing, you’ll face Blanka instead.

One of these days I should analyse it to see when the seed returns to its initial value, i.e. when the sequence starts repeating.

EDIT: Make sure you check out DIVVS·IVLIVS’s comments below, who’s studied that output from the RNG in detail

Random number generator

Guile’s World Warrior bugs

One of the first things I went looking for was to see what the cause of Guile’s Invisible Throw bug (and others). This is why I started with the earliest, buggiest version, SF2UA, date 910206.

A ‘move’ in Street Fighter is encoded using three variables, one to signal whether the move a standing / crouching / jumping attack, another for punch / kick, and an ID. Another variable tracks whether they are standing, walking, attacking, or executing a power move. These are used in finite state machines which are called each frame to process the animations.

The bugs result from still checking for power move input after an ordinary attack has been selected (because players are allowed to charge back while still executing other moves), and a nasty side effect of the button decoder setting the Punch/Kick variable directly without even know the player’s context.

First, we’ll look at the ‘Stance’ bug. You get it by being close enough to your opponent to do the upside-down kick (within 70 pixels), charging back, pressing roundhouse to begin the kick, and then doing a sonic boom before the kick completes. You have to press fierce after the roundhouse, and within the same window that a Sonic Boom would be allowed. Guile executes his upside-down kick, but instead of returning to a standing position, he gets stuck on the last frame of the upside down kick animation. He can be hit out of this, but the AI players don’t usually do this as they think he’s still doing some sort of kick that seems to last forever. Vega gets very confused and does his slide attack repeatedly.

The upside-down kick is Standing, Kicking move #6.

What happens is the standing kick gets selected, and the animation begins normally. The Sonic and Blade state machines keep running throughout attacks; this is necessary to allow players to charge back/down while still executing a move. At this stage, because we’ve charged back for long enough, and are now pressing forward, the Sonic Boom state machine is now checking for a button press, and when it detects one it calls the button decoder.

The button decoder (you’ll find it at address 0x3296 in the SF2UA ROMs) is basically a chain of ‘if’ statements that test a bit for each button, and set the corresponding Punch/Kick and Strength, bailing out at the first match.  The last condition, a strong kick, isn’t even tested for, it’s returned by default is no other buttons are detected. It only makes sense to call the subroutine if it’s already known a button is down.

Punches are tested before kicks, and when we press our punch button the player Punch/Kick variable gets set to ‘Punch’, so our player’s state is now Standing, Punching move #6 which actually represents one of Guile’s throws. The animation doesn’t change, but the state machine for the throw runs instead. This is at 0x2f922.

Where the state machine for the kick simply waits for the animation to finish and then sets the player’s state and sprite to a normal standing pose, the state machine for the throw is different. It looks for a special flag in the animation that signals that the character being thrown should now be released, but this never comes so Guile gets stuck in this state.

All of Guile’s bugs centre around this setting of one of his kicks, which then get turned into punches with the same ID by this bug in the button decoder, and were fixed in the next release (sf2ub, sf2eb, and sf2jb).

Guile’s World Warrior bugs