Monday, August 21, 2017

Level sizes

A while ago I talked about level sizes without getting into much detail. Basically, there's a set of four RAM values which serve as upper and lower bounds for the camera's X and Y position:
    Camera_min_X_pos = ramaddr( $FFFFEE14 ) ; word
    Camera_max_X_pos = ramaddr( $FFFFEE16 ) ; word
    Camera_min_Y_pos = ramaddr( $FFFFEE18 ) ; word
    Camera_max_Y_pos = ramaddr( $FFFFEE1A ) ; word
The origin point (0,0) of a level is at its top left corner, so Camera_min_X_pos and Camera_max_X_pos correspond to the left and right level boundaries, where as Camera_min_Y_pos and Camera_max_Y_pos correspond to the top and bottom level boundaries, respectively. When a level is first loaded, these addresses are initialized to the values in the LevelSizes data structure:
;                       xstart  xend    ystart  yend    ; Level
LevelSizes:     dc.w    $1308,  $6000,  $0,     $390    ; AIZ1
                dc.w    $0,     $4640,  $0,     $590    ; AIZ2
                dc.w    $0,     $6000,  $0,     $1000   ; HCZ1
                dc.w    $0,     $6000,  $0,     $1000   ; HCZ2
                dc.w    $0,     $6000,  -$100,  $1000   ; MGZ1
                dc.w    $0,     $6000,  $0,     $1000   ; MGZ2
                dc.w    $0,     $6000,  $0,     $B20    ; CNZ1
                dc.w    $0,     $6000,  $580,   $1000   ; CNZ2
                dc.w    $0,     $2E60,  $0,     $B00    ; FBZ1
                dc.w    $0,     $6000,  $0,     $B00    ; FBZ2
                ...
It should be noted that all the camera positions stored in these addresses correspond to the pixel displayed at the top left corner of the screen. That means in order to figure out the actual level dimensions, you need to add the screen's width (320 pixels) to the max X position, and the screen's height (224 pixels) to the max Y position.
                dc.w    $60,    $60,    $0,     $240    ; Gumball
                dc.w    $60,    $60,    $0,     $240    ; Gumball
                dc.w    $0,     $140,   $0,     $F00    ; Pachinko
                dc.w    $0,     $140,   $0,     $F00    ; Pachinko
For instance, in the pachinko/glowing spheres stage, although Camera_max_X_pos is set to $140 (320 pixels), since there are already 320 pixels horizontally on screen when Camera_X_pos is zero, scrolling to the right reveals another 320 pixels, for a total level width of 640 pixels. On the flipside, despite Camera_min_X_pos and Camera_max_X_pos both being set to $60, the gumball stage's width isn't actually zero. It just can't scroll horizontally. However, at the very least it's still as wide as the screen, so the actual level width is 320 pixels.


While debug mode is active, the score display is replaced by four counters, which show the player and the camera's current position. This makes it invaluable for testing events triggered by those values. In the example above, 0D00 is the player's X position and 0178 is their Y position, while 0C60 is the camera's X position and 00F8 is its Y position.

As an aside, the time display is also replaced by a counter. This one shows how many sprites are being displayed, in decimal. The reason it alternates between N and N+2 when you have no rings is because the "RINGS" portion of the HUD blinks in and out of existence, and since it's composed of two sprites, one for the "RING" and another for the S, there are two more sprites being displayed on frames in which it's visible.

Exercise: Why is the left boundary for Angel Island Zone 1 set to such a high value?

4 comments:

  1. I assume it is to prevent going back once the intro portion of AIZ1 is scrolled out of view, since after the main level graphics are loaded the intro chucks glitch out.

    That, and those chucks aren't solid so you'd inevitably fall into the pit if you could go back.

    ReplyDelete
    Replies
    1. Right. Due to the whole intro area at the start, the actual level only begins at $1300 or thereabouts. The left boundary is set up for the common case, and then there's special code that pulls it back to zero when you play as Sonic.

      Delete
  2. Presumably the negative number for MGZ1 is due to the level's vertical looping, and the unusually high number for CNZ2 is to offset for the similar looping in the miniboss arena?

    ReplyDelete
    Replies
    1. Good catch on CNZ2. The low ceiling is there to keep the act 1 boss scroll's guts out of reach, but there's already code elsewhere that resizes the level depending on your horizontal position, so the value there is actually pretty useless.

      Negative numbers do indeed turn infinite scrolling on. I'll get back to this at some point.

      Delete