Tuesday, December 26, 2017

You had it in you all along

In Launch Base Zone 1, you may have occasionally experienced a bug where you ride one of the tube elevator capsule thingies, and when you arrive at the other end, the door is already shut and you're immediately spat out of the lift.


So what's going on? First, let's get the basics of the tube elevator down. Even when you take another route through the level, or run far away and then come back, you'll always find a closed elevator sitting at the back end of the tube.


This suggests that the closed elevators are a permanent fixture of the stage, a fact confirmed by looking at the contents of the level's object layout: at the end of every tube is a second elevator object with bit 6 of its subtype set. A look at the object's code confirms that this causes it to spawn directly in the closed state:
Obj_LBZTubeElevator:
    move.l  #Map_LBZTubeElevator,$C(a0)
    move.w  #$2455,$A(a0)
    move.b  #$18,7(a0)
    move.b  #$30,6(a0)
    ori.b   #4,4(a0)
    move.w  #$80,8(a0)
    move.w  $10(a0),$44(a0)
    move.w  $14(a0),$46(a0)
    btst    #6,$2C(a0)
    beq.s   loc_29CEC
    move.b  #0,$22(a0)                  ; If BIT 6 of subtype set, the elevator remains closed
    move.b  #0,$26(a0)
    move.l  #Obj_LBZTubeElevatorClosed,(a0)
    bra.s   Obj_LBZTubeElevatorClosed

loc_29CEC:
    ...
Now we have a problem, though. When we do take the elevator, we don't want that second capsule waiting at the other end of the tube. This is accounted for by the closed elevator's code, which starts off with a rather complicated check:
Obj_LBZTubeElevatorClosed:
    lea     (Player_1).w,a1
    btst    #3,$2A(a1)
    beq.s   loc_29D8E
    tst.b   $2E(a1)
    beq.s   loc_29D8E
    movea.w $42(a1),a2
    cmpi.l  #Obj_LBZTubeElevatorActive,(a2)
    bne.s   loc_29D8E
    move.w  #$7FF0,$10(a0)

loc_29D8E:
    ...
Alright, so three things are being determined here. First, the object retrieves player 1's SST and checks whether bit 3 of its status bitfield is set: this is player 1's standing on object flag. If so, it then checks whether the player's object_control bitfield has any of its bits set: this is a sign that the player's movement is being controlled by another object.

If both those checks pass, then the elevator pulls out the big guns. It retrieves the word value stored at offset $42 of the player's SST, which we saw before contains the RAM address of the last object the player stood on. Since the player is currently standing on an object, this is the RAM address of the object the player is standing on right now.

It's time for the final check: the elevator looks at the code pointer of the object the player is currently standing on. If that object happens to be another elevator that isn't closed, then the first elevator assumes the second one is about to drop off the player at the back end of the tube, so it moves itself all the way to the end of the level, where it'll be collected by the object deletion routine.

So, does it work? Most of the time, yes. The elevator object bobs up and down inside the tube, which usually lifts Sonic off the floor, setting up the appropriate values in his SST.


However, when the elevator is at its lowest point, it is actually far enough away that Sonic will not snap onto its surface, which results in the tube ride starting while the player is still standing on solid ground.


When this happens, the player's object_control bitfield is set, but their status bitfield isn't, making the above checks fail. This causes the second elevator to spawn normally, and it is this elevator which squeezes Sonic out of the tube. (If you look closely, you'll be able to see both elevators bobbing up and down out of sync.)

1 comment: