The Labyrinth Zone Water Effect

Discussion in 'Discussion and Q&A Archive' started by TheStoneBanana, May 1, 2016.

?

Was this helpful?

  1. Yes

    100.0%
  2. No

    0 vote(s)
    0.0%
  1. TheStoneBanana

    TheStoneBanana banana Member

    Joined:
    Nov 27, 2013
    Messages:
    602
    Location:
    The Milky Way Galaxy
    (I wasn't sure where to put this type of thread, so please feel free to move it where ever its supposed to be.)

    [​IMG]
    Labyrinth Zone.
    Originally intended to be the second stage of the original Sonic the Hedgehog, the level is considered by many players to be frustrating and an unwelcome change of pace. Game play aside, the zone uses an interesting effect to create an immersive illusion of water, something many games of the era didn't do so well. The effect allows there to be double the colors on screen at once. What is this, and more importantly, how does this work?

    An Intro Vertical and Horizontal Blanks

    Televisions today work differently than the standard CRT television of the 80s and 90s. Basically, an electron gun fires off a beam of color down the Cathode Ray Tube, projecting light onto the screen. I left a lot out there, but you've got the basic idea.
    When the beams come to the edge of the screen, what is known as a Horizontal Blank takes place. The beams turn themselves off (to prevent retracing the picture the opposite direction) and then re position themselves back to the opposite side of the screen to prepare to draw the next line in the picture.
    When the beams come to the very, very end of the picture, they once again shut themselves off, but this time, they make their way up towards the topmost edge of the screen to prepare to draw the next frame of the picture. This time is known as a Vertical Blank.
    The Mega Drive/Sega Genesis has the ability, through the VDP, to use what are known as Vertical and Horizontal Interrupts. Essentially, during a VBlank or HBlank period, the Mega Drive will stop whatever it is doing and jump to the V-Int or H-Int vector respectively. In the Sonic 1 2005 Hivebrain Disassembly, these routines are labelled as loc_B10 (V-Int) and PalToCRAM (H-Int). They are the key to the Water Effect.

    Examining the Vertical Interrupt Routine
    During V-Int, there is a ton that happens. However, for the topic at hand, I'll only be focusing on what handles the Water Effect.
    If the VBlank Routine Counter ($FFFFF62A in RAM) is set to #$00, then we are taken to loc_B88. This is start of the heart of the effect.
    Code:
    loc_B88:         ; XREF: VBlank; off_B6E
         cmpi.b   #$8C,($FFFFF600).w
         beq.s   loc_B9A              ; if so, branch ahead
         cmpi.b   #$C,($FFFFF600).w   ; is a level being played?
         bne.w   loc_B5E              ; if not, branch to something dealing with music
    
    loc_B9A:
    ...
    
    This is a simple check to make sure that we are indeed playing a level. If we aren't, we branch to a routine that deals with music.
    Code:
    loc_B9A:
            cmpi.b    #1,($FFFFFE10).w   ; is the current level Labyrinth Zone?
            bne.w    loc_B5E             ; if not, branch to something dealing with music
            move.w    ($C00004).l,d0
            btst    #6,($FFFFFFF8).w     ; is the Mega Drive a PAL unit?
            beq.s    loc_BBA             ; if not, branch
            move.w    #$700,d0           ; set how long to wait
    
    loc_BB6:
            dbf    d0,loc_BB6            ; loop and wait
    
    loc_BBA:
    ...
    
    Here, we check to make sure that the current level is Labyrinth Zone. We also check to see if the Mega Drive is PAL. If it is, we have to wait a bit, as PAL televisions operate 10 Hz slower than NTSC televisions. This means that the Mega Drive's timing for HBlanks and VBlanks has to be adjusted.
    Code:
    loc_BBA:
            move.w    #1,($FFFFF644).w  ; set the flag to transfer the water palette on the next HBlank
            move.w    #$100,($A11100).l ; stop the Z80
    
    loc_BC8:
            btst    #0,($A11100).l      ; has the Z80 stopped?
            bne.s    loc_BC8          ; if not, branch
    
    Pretty self explanatory. We set a flag in RAM that will allow the H-Int routine to copy the water palette to CRAM during the next HBlank. We also stop the Z80 for good measure.
    Code:
            tst.b    ($FFFFF64E).w        ; is the water's height above the screen?
            bne.s    loc_BFE                ; if so, branch
    
            ; write the dry palette to CRAM
            lea    ($C00004).l,a5
            move.l    #$94009340,(a5)
            move.l    #$96FD9580,(a5)
            move.w    #$977F,(a5)
            move.w    #$C000,(a5)
            move.w    #$80,($FFFFF640).w
            move.w    ($FFFFF640).w,(a5)
    
            bra.s    loc_C22
    ; ===========================================================================
    
    loc_BFE:                ; XREF: loc_BC8
            ; write the water palette to CRAM
            lea    ($C00004).l,a5
            move.l    #$94009340,(a5)
            move.l    #$96FD9540,(a5)
            move.w    #$977F,(a5)
            move.w    #$C000,(a5)
            move.w    #$80,($FFFFF640).w
            move.w    ($FFFFF640).w,(a5)
    
    loc_C22:
    ...
    
    Here's the good stuff! We check if the water is above the screen, and if so, we DMA the underwater palette to CRAM. If it isn't, then we just DMA the normal palette to CRAM.​
    Code:
    loc_C22:                ; XREF: loc_BC8
            move.w    ($FFFFF624).w,(a5)
            move.w    #0,($A11100).l        ; restart the Z80
            bra.w    loc_B5E
    
    The H-Int counter is copied to the VDP, the Z80 is restarted, and we branch to the routine dealing with music. From there, the music is dealt with, the unused VBlank counter is increased, the registers are restored, and we return to whatever was going on before the interrupt. Phew!
    In some of the other VBlank Routines, there actually is some code to check for water just like loc_BC8 and onward but I don't think I really need to explain them seeing as how I just kind of did.

    Examining the Horizontal Interrupt Routine
    Now that V-Int has ever so kindly set everything up for us, we just need to wait for the H-Int to trigger. It has a few relatively simple jobs.
    Code:
    PalToCRAM:
            move    #$2700,sr      ; disable interrupts
            tst.w    ($FFFFF644).w  ; should the water palette be transferred?
            beq.s    locret_119C    ; if not, branch
            move.w    #0,($FFFFF644).w ; clear the flag
            movem.l    a0-a1,-(sp)  ; copy registers a0 and a1 to the stack for safe keeping
            lea    ($C00000).l,a1
            lea    ($FFFFFA80).w,a0 ; load the underwater palette from RAM
            move.l    #$C0000000,4(a1) ; set VDP to write to CRAM
            move.l    (a0)+,(a1)    ; copy palette to CRAM
            move.l    (a0)+,(a1)
            move.l    (a0)+,(a1)
            ...
            move.l    (a0)+,(a1)
            move.w    #$8ADF,4(a1)  ;reset H-Int counter
            movem.l    (sp)+,a0-a1  ; restore the registers
            tst.b    ($FFFFF64F).w  ; should the sound driver be run during this HBlank?
            bne.s    loc_119E       ; if so, branch
    
    locret_119C:
            rte   ; return from the interrupt
    ; ===========================================================================
    
    loc_119E:                ; XREF: PalToCRAM
            clr.b    ($FFFFF64F).w
            movem.l    d0-a6,-(sp)
            bsr.w    Demo_Time
            jsr    sub_71B4C
            movem.l    (sp)+,d0-a6
            rte 
    ; End of function PalToCRAM
    
    First and foremost, we store the registers to the stack and we check if the flag to transfer the water palette is set. If it isn't we don't do it. Groundbreaking, I know. If it is, we directly copy the underwater palette from RAM to CRAM. Then, we reset the H-Int counter for later, restore registers, and check if the sound driver needs to be ran during the remaining HBlank time. If so, deal with it, and if not, don't. After all is said and done and a frame is completed, we get something a little like THIS:

    [​IMG]
    See those little lines right above the water? Those are artifacts from changing the palette mid-display. Most emulators actually do NOT emulate these which is why I had to take a screencap using Exodus. There's no way to really get rid of these so they just get covered up with the surface of the water.

    [​IMG]
    It rapidly flashes back and forth, and honestly, you never are going to notice the dots unless you really focus and are looking for them.

    And with that, the Labyrinth Zone Water Effect has been completely explained! :D
    Hopefully it's a little easier to understand, and perhaps some of you can figure out how to use it for other purposes.
    Special thanks to Ralakimus and Natsumi for proofreading this documentation to make sure I was making sense. You guys are the bomb.
     
    Last edited: May 4, 2016
  2. FireRat

    FireRat Do Not Interact With This User, Anywhere!!! Exiled

    Joined:
    Oct 31, 2009
    Messages:
    535
    Cool effort. Only one bad question: How do we know, or what is the safest way, to know how many cycles to wait for these artifacts to not show up? I do not have the real hardware, nor my CPU is capable of booting Exodus (because it is dual, not quad). Sonic 3 waste some cycles to avoid this issue, (even though it means sacrifying some extra scanlines).
     
  3. TheStoneBanana

    TheStoneBanana banana Member

    Joined:
    Nov 27, 2013
    Messages:
    602
    Location:
    The Milky Way Galaxy
    There are no bad questions. The only bad questions are the ones that go unasked.

    I, honestly, am not entirely sure. I haven't peeked into Sonic 3 or anything so I'll take a blind guess. I assume that the game specifically times itself so that only so many colors are written during the H-Blank period. Note that, during the stock Sonic 1 interrupt, we are writing all of the colors at once to the screen, and since we are writing a color at the same time that the Mega Drive is trying to draw a color, the written color gets drawn on-screen instead, creating the artifacts. If you were to only write colors during times when the display isn't drawn (once again, H-Blank) the artifacts would most likely be gone or partially taken away.

    That's fine. As long as you're not a hardware nerd who needs everything to be entirely accurate you don't need to use Exodus or any fancy schmancy emulator. Use what you like.
    Also, if I must say, I do recommend you buy the real hardware. It makes the games feel more... authentic? Not sure how to put it, but it feels different playing on an actual console compared to on a computer.
     
    FireRat likes this.
  4. AURORA☆FIELDS

    AURORA☆FIELDS so uh yes Exiled

    Joined:
    Oct 7, 2011
    Messages:
    759
    You can calculate the cycle count, actually. Bear in mind this is different on PAL and NTSC Mega Drives dues to different refresh rates. Also its good to note you've got a very little time to DMA, you can probably get max 1 line and few colours without any artifacts (maybe a bit more on PAL, maybe up to 3 colours)
     
    FireRat likes this.
  5. LazloPsylus

    LazloPsylus The Railgun Member

    Joined:
    Nov 25, 2009
    Messages:
    Location:
    Academy City
    A few things. PAL doesn't run 10Hz slower, per se. The actual difference in the master clock is in the order of .4MHz. The difference you're referring to is the refresh rate, which refers to how many times per second the image on the screen is refreshed. Put simply, on an NTSC display, the image on the screen is updated 60 times per second by the TV's hardware. On a PAL display, it's 50 times per second. Meanwhile, calculations for how long these interrupts last is a fun mess that most don't generally need to worry about, because you're not trying to push the limits of the interrupts. Really, though, there's a decent amount of usable info in the Genesis Software Manual that can be found in a variety of places, and I would recommend you look through it sometime regarding interest in some of what the spec'd limits are.
     
    AURORA☆FIELDS likes this.
  6. TheStoneBanana

    TheStoneBanana banana Member

    Joined:
    Nov 27, 2013
    Messages:
    602
    Location:
    The Milky Way Galaxy
    Finally changed my wording within the OP to be a bit more accurate. I've also begun to look over the Genesis Software Manual upon your request, Lazlo.

    Now, for everyone, I'd like to ask what else that people are interested in seeing dissected and explained. I'm already planning to do something on parallax scrolling, but otherwise, I don't know what you guys want to see.
     
  7. Niko

    Niko All's well that ends well, right? Member

    Joined:
    Mar 1, 2014
    Messages:
    245
    Location:
    $C800
    What I'd really like to see explained in depth is the Revision's water warping effect, since this makes it look more like underwater than changing to unrealistic colours does.
     
  8. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,376
    Location:
    your mom
    If you're referring to REV01 (which I think you are), then the effects come from deformation. On the MegaDrive, there is a mode for horizontal scrolling for each scanline, which allows for deformation to work. Each value for this mode indicates how many pixels to scroll a scanline to the right. The first scanline that is used to start the effect is calculated from the current height of the water relative to the camera's y position. The horizontal scrolling values for each scanline in water for the background are calculated from a table of values. The first value to use in the table comes from a value that increments each time the deformation routine is run, which acts as an index for the table. The incrementation of this value allows for the waves in the background to appear to be animated. For the foreground, it's the same way, but it uses a different table of values. The values are then put into the horizontal scroll buffer.
     
    Last edited: May 29, 2016
    Pacca, Niko and TheStoneBanana like this.