Jump to content





Step 5 - automate Vertical Delay

Posted by SpiceWare, in Collect 04 July 2014 · 1,660 views

For this update, we're going to double the Y range of the player objects.  To use the new Y value for the 2LK we just need to divide it in half using the LSR command.  The remainder of the divide, which ends up in the Carry flag, will conveniently tell us if we need to turn on Vertical Delay.
 
This routine preps the 2LK data for player0 and turns on VDELP0 if required (if you're wondering, VDELP0 is turned off in VerticalSync):
    ; prep Humanoid's Y position for 2LK 
        ldx #1              ; preload X for setting VDELPx
        lda ObjectY         ; get the human's Y position
        lsr                 ; divide by 2 for the 2LK position
        sta Temp            ; save for position calculations
        bcs NoDelay0        ; if carry is set we don't need Vertical Delay
        stx VDELP0          ; carry was clear, so set Vertical Delay
NoDelay0:        
    ; HumanDraw = ARENA_HEIGHT + HUMAN_HEIGHT - Y position
        lda #(ARENA_HEIGHT + HUMAN_HEIGHT)
        sec
        sbc Temp
        sta HumanDraw
        
    ; HumanPtr = HumanGfx + HUMAN_HEIGHT - 1 - Y position
        lda #<(HumanGfx + HUMAN_HEIGHT - 1)
        sec
        sbc Temp
        sta HumanPtr
        lda #>(HumanGfx + HUMAN_HEIGHT - 1)
        sbc #0
        sta HumanPtr+1
 
 
One minor problem with the prior 2LK was that player1 could not show up on the topmost scanline of the Arena:
Attached Image
Closeup:
Attached Image
 
To fix this, we'll modify the kernel to prime GRP1 before it enters the loop that draws the Arena:
        ldy #ARENA_HEIGHT+1 ; 2  7 - the arena will be 180 scanlines (from 0-89)*2
        
    ; prime GRP1 so player1 can appear on topmost scanline of the Arena        
        lda #BOX_HEIGHT-1   ; 2  9 - height of the box graphics, 
        dcp BoxDraw         ; 5 14 - Decrement BoxDraw and compare with height
        bcs DoDrawGrp1pre   ; 2 16 - (3 17) if Carry is Set, then box is on current scanline
        lda #0              ; 2 18 - otherwise use 0 to turn off player1
        .byte $2C           ; 4 22 - $2C = BIT with absolute addressing, trick that
                            ;        causes the lda (BoxPtr),y to be skipped
DoDrawGrp1pre:              ;   17 - from bcs DoDrawGRP1pre
        lda (BoxPtr),y      ; 5 22 - load the shape for the box
        sta GRP1            ; 3 25 - @0-22, update player1 to draw box
        dey                 ; 2 27
        
ArenaLoop:                  ;   13 - from bpl ArenaLoop
 
 
 
The 2LK calculations for player1 used to be the same as for player0, but now must be modified to compensate for the priming of GRP1:
    ; prep box's Y position for 2LK
        lda ObjectY+1       ; get the box's Y position
        clc
        adc #1              ; add 1 to compensate for priming of GRP1
        lsr                 ; divide by 2 for the 2LK position
        sta Temp            ; save for position calculations
        bcs NoDelay1        ; if carry is set we don't need Vertical Delay
        stx VDELP1          ; carry was clear, so set Vertical Delay
NoDelay1:        
    ; BoxDraw = ARENA_HEIGHT + BOX_HEIGHT - Y position + 1
    ; the + 1 compensates for priming of GRP1
        lda #(ARENA_HEIGHT + BOX_HEIGHT +1)
        sec
        sbc Temp
        sta BoxDraw
        
    ; BoxPtr = BoxGfx + BOX_HEIGHT - 1 - Y position
        lda #<(BoxGfx + BOX_HEIGHT - 1)
        sec
        sbc Temp
        sta BoxPtr
        lda #>(BoxGfx + BOX_HEIGHT - 1)
        sbc #0
        sta BoxPtr+1
 
 
Added GRP1 priming which allows player1 to cover full Arena:
Attached Image
Closeup:
Attached Image
 
 
Lastly, I added a new Box graphic for player1
Attached Image  
 
 
ROM
Attached File  collect_20140704.bin (2KB)
downloads: 137
 
Source
Attached File  Collect_20140704.zip (44.99KB)
downloads: 174




Is this series something you'll eventually want me to put on my web site?

  • Report

If you'd like to that's fine with me.

 

I've come to the conclusion that even at this early stage of development that there's already too much information to present in a 1 hour presentation.  I'm still going to wrap it up though - I'm going to leave the current example in place then add a few slides about Collect with a "go here to see how the program was developed".

 

I've also decided to make Collect a 1 and 2 player game, will go into more detail on that in the next update.

  • Report

If you'd like to that's fine with me.

 

I've come to the conclusion that even at this early stage of development that there's already too much information to present in a 1 hour presentation.  I'm still going to wrap it up though - I'm going to leave the current example in place then add a few slides about Collect with a "go here to see how the program was developed".

 

I've also decided to make Collect a 1 and 2 player game, will go into more detail on that in the next update.

 

Thanks. I'll start working on it as soon as possible.

  • Report

Would you be able to elaborate or do a walk-through example on the code below?  I cannot understand how it fine tunes the X position.  It looks to me like the EOR would make the position's value meaningless, and then the ASLs would multiply that number.  Thanks:

 

PosObject:
        sec
        sta WSYNC
DivideLoop
        sbc #15        ; 2  2 - each time thru this loop takes 5 cycles, which is
        bcs DivideLoop ; 2  4 - the same amount of time it takes to draw 15 pixels
        eor #7         ; 2  6 - The EOR & ASL statements convert the remainder
        asl            ; 2  8 - of position/15 to the value needed to fine tune
        asl            ; 2 10 - the X position
        asl            ; 2 12
        asl            ; 2 14
        sta.wx HMP0,X  ; 5 19 - store fine tuning of X
        sta RESP0,X    ; 4 23 - set coarse X position of object
        rts            ; 6 29

  • Report

Would you be able to elaborate or do a walk-through example on the code below?


Time to learn Stella's debugger and let it take you on a detailed walk-through:

  • Make sure that files collect.lst and collect.sym are located in the same directory as collect.bin.  This is because Stella will reference the content of those files, allowing you to use the label names in the debugger.
  • Start up Stella and load collect.bin.
  • Hit the ` key (it also has the ~, to the left of 1 and above tab) to enter Stella's debugger.
  • In the prompt box, lower left of the debugger window, you should see a couple messages along the lines of:
    loaded /path/to/file/collect.lst OK
    loaded /path/to/file/collect.sym OK
  • If there's a dotted red line surrounding the prompt box, then the prompt has focus.  If not, click within the prompt box to give it focus.
  • To see a summary of the debugger commands, type the command:
    help
  • For now, set a break point at the start of PosObject by typing the following command (make sure P and O are capitalized):
    break PosObject
  • You should see a response of:
    Set breakpoint at F800
  • hit the ` key again to resume execution of collect.
  • The debugger will show up again.  Notice in the Disassembly box, lower right of the debugger window, that there's a red dot to the left of PosObject.  That's the breakpoint you set.  The green box that's highlighting PosObject SEC ... is the line of code that will next be executed.
  • Directly above the Disassembly box is the state of 128 bytes of RAM, and above that is the state of the CPU.  
  • In the CPU section make note of the value after the A: That's the current value of the Accumulator, which holds the X position we plan to position an object to.  The X: just below it is the X register, and it holds which object we're going to set - if it's 0 we're moving player0, if it's 1 we're moving player1.
  • In the top right corner is a Step button.  Press it once.  Anything that changed with the instruction will be shown with green text in a red box.  Watch the PC (the program counter, or next instruction to run), the value in the Accumulator, or the state of the Carry Flag (a capital C means Carry is Set while lower case means carry is clear) as the code runs.
  • Repeatedly press the Step button until you end up on the line EOR #$07.  Take note of the value of A - specifically the lower nybble (the right hex digit). That's basically the remainder of the X position / 15.  You can ignore the upper nybble, it'll be F and is not used.
  • Press Step button 5 more times and close watch what the EOR and ASL instructions do to value in the Accumulator.  

Also note that you can change the values you see, which makes it easier to test what happens when different values are encountered.  Let's force it to position the object at location 64, which is $40 in hex:

  • Hit ` again, you'll end up at the start of PosObject.
  • Double-click the value in the box after A:, you can now enter a new hex value - put in something like 40 then hit RETURN.
  • To the right of the 40 you'll see 64 (the value in decimal) and 01000000 (the value in binary)
  • Alternatively you can double-click the 2nd box after the A: if you'd rather enter a decimal value, or the 3rd box for a binary value.
  • Report

Per Step 1 you should have downloaded the Stella Programmer's Guide as a reference.  Open it up and look at the bottom of page 41 (page 46 per a PDF viewer) and read up on the section for HMP0, HMP1, etc.

 

In the list of values you should notice that only bits D7, D6, D5 and D4 are used.  That means those registers only use the value in the upper nybble, which is why we used the 4 ASL instructions to shift the modified remainder value from the lower nybble to the upper nybble.

  • Report

note to BNE Jeff - I was still editing the above two replies when you started reading, so you may need to look at them again for the final revision.

  • Report

Thanks! I'm going to run through the steps today.  I did consider that the shifts were just for moving the positions of the bits for some reason, but I think the EOR threw me off of that.

 

Also, I've read through the Stella guide several times.  And, (uncharacteristically for me with all of this stuff), the latest read-through was a breeze. I felt like I had at least a 95% understanding, except for all of the schematics which I only skimmed because they didn't seem very relevant. (feel free to correct me if I'm wrong on that)  I guess I only understood HMMO, etc. as useful for relative movement, not fine tuning something stationary.

 

I've used the debugger a few times as well, I can advance through the code step by step, but don't have any knowledge about it beyond that and hope to learn more about it.  It looks like it has a lot of capability.

  • Report

except for all of the schematics which I only skimmed because they didn't seem very relevant. (feel free to correct me if I'm wrong on that)


They're not helpful for me, but for others they can be. batari compared the heavy sixer schematics to those of later models and was able tofigured out why a Melody board revision was not working on the sixer switch systems.
 

I guess I only understood HMMO, etc. as useful for relative movement, not fine tuning something stationary.

Fine tuning is a must for position an object, even if stationary. With only 76 cycles per scanline, there's no way we could strobe RESPx at the correct time to position an object to all 160 X locations that it could appear on.

 

It looks like it has a lot of capability.

It does, that's why I figured a detailed answer showing you how to use it to research this particular question would be more useful than just explaining the subroutine in more detail :)
  • Report

As another pointer to research this question - step thru PosObject after you change A to 0, then 1, 2, etc thru 14. Then again for 15 thru 29.

At the end of PosObject, when PC is pointing at the RTS statement, switch from the Prompt tab to the TIA tab. Make note of values in Pos: and HM: for the object being moved (as indicated in the X register).

You can set a second break at the RTS if you don't wish to step thru each and every instruction in PosObject:
break F812
 
Don't forget that HM value is the adjustment to move the object left. Also don't forget that the HM value is a signed a 4 bit value that's encoded using two's complement.  A negative value results in the object moving right right.

 

0-7 are positive values: 0 = 0, 1 = 1, ...., 6 = 6, 7 = 7 

8-F are negative values:  F = -1, E = -2, ..., 9 = -7 and 8 = -8.

  • Report

I was typing this up before your later posts...

 

Thanks for the debugger guidance- I knew pretty much none of that.  I don't think I ever even noticed the left window..  And its especially nice to see the labels in the assembly code on the right instead of the random labels it would have generated itself. And the break is cool..

So, if I have this straight,

This little loop immediately follows a WSYNC which can only place a player at 15 color clock increments from the WSYNC.  Even though we are outside of the kernal, there is no harm in doing this now.

The divide loop is making those 15 color clock jumps.

EOR is switching the right three bits- 0's to ones, and 1s to zeros...  I'm still not sure why...Are we converting this remainder to two's compliment negative?  I know that TIA's direction of the x-axis is backwards.

Then they are moved into the correct position HMP0 for an eventual HMOVE to happen later.

But..
By the time we RESPO, isn't our course position timing off?  Didn't we use like 19 more cycles to get to it?

  • Report

So, if I have this straight,

Yep
 

EOR is switching the right three bits- 0's to ones, and 1s to zeros...  I'm still not sure why...Are we converting this remainder to two's compliment negative?

The remainder is based on the object being positioned at the first of the 15 locations, but it actually ends up in the middle of those 15 positions. The EOR 7 makes it so a remainder of 0 moves the object left by 7. A remainder of 1 moves the object left by 6, ... 6 moves left 1, and 7 becomes 0 so it doesn't move at all.
 

By the time we RESPO, isn't our course position timing off?  Didn't we use like 19 more cycles to get to it?

If you check the timing diagram in Step 3, you should notice that the initial 22 cycles are during the Horizontal Blank. If you strobe RESP0 during that time then player0 ends up at position 3.
  • Report

I had no idea EOR would do that. Unbelievable.  I went through each possible move on page 41 and it checks out!  I've known for a while what  EOR does, but never could figure out what use it could be for anything...

 

And I forgot about the Horizontal Blank...  So I assume we are using up 22 cycles in the subsequent code then, not 19 until the RESP0- right?

  • Report

Wait.. maybe 19 is close enough since TIA will put the position into the center of the 15 cycle course position anyway?.  Is that right?

  • Report
For positions 0-14, the RESP0 occurs on cycle 23, not 19.  Any store happens at the end of the instructions' execution.  If your code was this:
 
 sta WSYNC
 sta RESP0
 
the RESP0 happens at cycle 3, not 0.
  • Report

OK Thanks!  Got it.

  • Report
May I ask why automate the video delay, if you could've just set video delay for player 1 on?
  • Report

In step 4 you might have noticed that the Y values for the players in the 2 Line Kernel are $00-$59 (0-89 decimal).  Vertical Delay is under user control (via the difficult switches) in order to let you experiment with the delay to see how it works.

 

In step 5 the Y value was doubled to $00-$B3 (0-179 decimal).  However, the 2 Line Kernel is still limited to $00-$59, so the Y value must be divided in half before doing the calculations for the 2 Line Kernel. The remainder of the divide by 2 is the Vertical Delay value, which lets us automate it rather than having to control it via the difficulty switches.

  • Report

Search My Blog

Recent Entries

Recent Comments

Latest Visitors

2 user(s) viewing

0 members, 2 guests, 0 anonymous users