Thursday, September 7, 2017

Blue Knuckles

In my previous post I just casually mentioned Blue Knuckles, which is a fairly well-known glitch. But who exactly is Blue Knuckles? Is he the remnants of a fourth playable character? Well, not really. He's just the consequence of how various character-specific stuff is programmed.

You see, the game uses three different RAM variables to keep track of which character you're currently playing as. The first two are the Player_mode and Player_option words at $FF08 and $FF0A. Player_mode is the one that's actually used for game logic, whereas Player_option is the character you have selected in the menus. However, Player_mode gets overwritten with the value of Player_option on each level load.
Player_mode =   ramaddr( $FFFFFF08 ) ; word ; 0 = Sonic and Tails, 1 = Sonic alone, 2 = Tails alone, 3 = Knuckles alone
Player_option = ramaddr( $FFFFFF0A ) ; word ; option selected on level select, data select screen or Sonic & Knuckles title screen

The third variable is the character_id byte at offset $38 in the player object's SST, which we learned about back when we looked at the breakable wall object. It is set to a specific, hardcoded value by each of the Sonic, Tails, and Knuckles objects, and is mostly used in object to object interactions.
character_id =  $38 ; byte ; 0 for Sonic, 1 for Tails, 2 for Knuckles
The Blue Knuckles glitch occurs when the Player_option variable, through RAM corruption or otherwise, gets set to an invalid value. You can force this by using the PAR code FFFF0A:0004.


When this happens, the game spawns a Knuckles object, but loads Sonic's palette, resulting in... well, a blue Knuckles. It also loads Sonic's lives icon and puts the player at Sonic's spawn coordinates, but without Sonic's level intro objects, such as the snowboard at the start of Icecap Zone, or Tails at the start of Carnival Night Zone and Mushroom Hill Zone.

But why a blue Knuckles rather than a red Sonic? Why Sonic's coordinates rather than Knuckles'? What are the rules?

There are no rules. It's completely arbitrary depending on how each case was programmed. For instance, consider the case in which the player objects are spawned:
    move.w  (Player_mode).w,d0
    bne.s   loc_6B1E
    move.l  #Obj_Sonic,(Player_1).w
    move.l  #Obj_DashDust,(Dust).w
    move.l  #Obj_Insta_Shield,(Shield).w
    move.w  #$B000,($FFFFCD2A).w
    move.l  #Obj_Tails,(Player_2).w
    move.w  ($FFFFB010).w,($FFFFB05A).w
    move.w  ($FFFFB014).w,($FFFFB05E).w
    subi.w  #$20,($FFFFB05A).w
    addi.w  #4,($FFFFB05E).w
    move.l  #Obj_DashDust,(Dust_P2).w
    move.w  #0,($FFFFF708).w
    rts
; ---------------------------------------------------------------------------

loc_6B1E:
    subq.w  #1,d0
    bne.s   loc_6B42
    move.l  #Obj_Sonic,(Player_1).w
    move.l  #Obj_DashDust,(Dust).w
    move.l  #Obj_Insta_Shield,(Shield).w
    move.w  #$B000,($FFFFCD2A).w
    rts
; ---------------------------------------------------------------------------

loc_6B42:
    subq.w  #1,d0
    bne.s   loc_6B64
    move.l  #Obj_Tails,(Player_1).w
    move.l  #Obj_DashDust,(Dust_P2).w
    addi.w  #4,($FFFFB014).w
    move.w  #0,($FFFFF708).w
    rts
; ---------------------------------------------------------------------------

loc_6B64:
    move.l  #Obj_Knuckles,(Player_1).w
    move.l  #Obj_DashDust,(Dust).w
    rts
; ---------------------------------------------------------------------------
Here's what this code says: if Player_mode is 0, spawn Sonic and Tails. Otherwise, if it's 1, spawn Sonic. Otherwise, if it's 2, spawn Tails. Otherwise, spawn Knuckles. All invalid values will spawn a Knuckles object. On the other hand, here's the code that loads the player palette:
    moveq   #3,d0
    cmpi.w  #3,(Player_mode).w
    bne.s   loc_61DA
    moveq   #5,d0

loc_61DA:
    bsr.w   LoadPalette_Immediate
    ...
If Player_mode is 3, load Knuckles' palette. Otherwise, load Sonic's palette. This time, all invalid values will instead load Sonic's palette. Knuckles is the exception here, even Tails uses Sonic's palette.
    moveq   #5,d0
    cmpi.w  #3,(Player_mode).w
    beq.s   loc_60DA
    moveq   #1,d0
    cmpi.w  #2,(Player_mode).w
    bne.s   loc_60DA
    moveq   #7,d0
    tst.b   (Graphics_flags).w
    bmi.s   loc_60DA
    moveq   #$52,d0

loc_60DA:
    bsr.w   Load_PLC
    ...
The lives icon behaves similarly. If Player_mode is 3, load Knuckles' lives icon. Otherwise, if it's 2, load Tails' lives icon. Otherwise, load Sonic's lives icon. Note the extra check in the Tails branch, though: if the sign bit on the Graphics_flags byte is set, the game instead picks the Miles lives icon.

The level results object swaps the logic on a few branches, and consequently we get a Miles/Tails results screen:
    lea     (ArtKosM_ResultsSONIC).l,a1
    cmpi.w  #1,(Player_mode).w
    bls.s   loc_2DB58
    lea     (ArtKosM_ResultsKNUCKLES).l,a1
    cmpi.w  #3,(Player_mode).w
    beq.s   loc_2DB58
    lea     (ArtKosM_ResultsMILES).l,a1
    tst.b   (Graphics_flags).w
    bpl.s   loc_2DB58
    lea     (ArtKosM_ResultsTAILS).l,a1

loc_2DB58:
    ...

loc_2DB58:
    jsr     (Queue_Kos_Module).l
    ...
And so on. By exploring different instances in which there's character-specific behavior, you can get a feel for whether the game is doing something like Player_mode == 3, Player_mode != 3, or sometimes even Player_mode < 3.

8 comments:

  1. I know a PAR code exists for a red hot Sonic (I said that on purpose). Is there a way to patch Blue Knuckles?

    ReplyDelete
    Replies
    1. What do you mean, "patch Blue Knuckles"? There are several ways you could replace Knuckles' palette with Sonic's but that would hardly be interesting in isolation.

      Delete
    2. I meant remove him, but given how fun he is combined with the code presented, that's unlikely.

      Delete
    3. I still don't understand what you mean. The glitch happens when the game's memory is set to an invalid state; obviously the correct way to remove the glitch is to fix all the oversights that set the game's memory to an invalid state. On the other hand, the PAR code forces the glitch to occur by forcing the relevant memory address into an invalid value.

      Delete
    4. Forget about it. I thought there was a way to change it so invalid values do not give Blue Knuckles, but the only other solution is to give a state in which loading an illegal value can crash the game. At least now I can try out all sorts of crazy character combinations.

      Delete
  2. On the results screen, why does it say Tails?

    ReplyDelete
  3. I am playing the blue knucles game at Y9

    ReplyDelete