Jump to content
IGNORED

"Sierra Maestra", an Early WIP


vidak

Recommended Posts

I need some help. I have two issues:

 

How the Variable "Heights" Works in Stay Frosty

 

One thing doesn't add up in my analysis of how the Y Register in the Stay Frosty kernel is formed. This code forms the Heights variable:

LINE 2196:

        ldy #SECTIONS*DATA_BYTES_PER_SECTION-4-1+2+2
        ; -4 for the 4 unused platform bytes
        ; +2 for fireball size info
        ; +2 for level control init
.plLoop        
        lda (LevelDataPtr),y
        sta Heights,y
        dey
        bpl .plLoop

The SECTIONS constant equals 5. The DATA_BYTES_PER_SECTION constant equals 11. Cancelling out the minor adjustments, the first LDY operation should load Y with 54. As we know from the previous blog post, the LevelDataPtr is pointing to the Level1 label in ROM.

The first inconsistency is this: There is only 53 bytes of information apart from game logic in those sections of ROM.

 

The second inconsistency with my analysis is: there are only 6 or 7 bytes of information in ROM under those labels that could be construed as the Height of things.

The third inconsistency is this: There are only 5 bytes set aside for Heights in the RAM. This equals the number of sections of Height that the variable is supposed to store. The STA operation in the above code is using Absolute Indexed addressing. This means the Y register is used to increment the memory location relative to Heights, not the value in the Heights memory location. This means if we increment the memory location more than 5 times we are starting to store data in other memory labels than Heights.

I am thoroughly confused. If anyone can help me understand this, I would be very grateful. I feel as if I don't understand the order of operations being done with the constants in the first LDY operation. I also don't understand what DATA_BYTES_PER_SECTION refers to. I cannot find any groups of 11 bytes of data anywhere.

Preparing the Graphics Pointers for Sections 1 to 4

How does this code work?

LINE 2006:


        ldx #0
        ldy #0
pfLoop        
        clc                     
        lda FrostyImagePtr,y    
        adc Heights,x           
        iny ; 1                 
        iny ; 2                 
        sta FrostyImagePtr,y   ; Graphics Address (0) + Heights -> Graphics Address + 2
        dey ; 1                
        lda FrostyImagePtr,y   ; LDA Graphics Address + 1
        adc #0
        iny ; 2
        iny ; 3
        sta FrostyImagePtr,y   ; Graphics Address + 1 -> Graphics Address + 3

<Repeated Process for the Mask and Colours here>

        dey ; 2
        inx        
        cpx #SECTIONS-1
        bcc pfLoop

I think what is happening in the above code is this:

  • We loop through this above code 5 times - as many times as there are bands up the screen. (5 times)
  • Everytime we move through the loop, the Y Register increases by 2. So the 5 times we move through the loop Y increases through these values:
    • 0 -> 2, 1 -> 3
    • 2 -> 4, 3 -> 5
    • 4 -> 6, 5 -> 7
    • 6 -> 8, 7 -> 9
    • 8 -> 10, 9 -> 11

This means we are increasing the graphics pointer memory address by 2 every time--both the lower byte of the pointer, and the higher byte. We are shifting the line of graphics by 2 up every band of the screen. This is confusing - why do we need to do this?

 

What seems to be happening to the lower byte is that we increment its memory address by the number of bytes equal to the height of the band. Why don't we also do this to the higher byte? Wouldn't this lead to the lower byte increasing something like 30+2 locations forward, where the higher byte only moves 2 locations forward?

 

I need some clarification on the above issues.

Edited by vidak
Link to comment
Share on other sites

Been a long time since I wrote this. Was under major time constraint, as I was invited to join the project on October 29th, so comments aren't the best. Let's see what I can figure out... I'll do this as 2 separate replies.

 

First up - How the Variable "Heights" Works in Stay Frosty

 

here's the level 1 data that'll be copied into RAM

Level1 
       
        .byte 35,35,34,34,10 ; section heights add up to 148
        .byte 80, 0 ; section 0 fireballs
        .byte 80, 100 ; section 1 fireballs
        .byte 0, 0 ; section 2 fireballs
        .byte 0, 90 ; section 3 fireballs
        .byte 0, 0 ; section 4 fireballs
        .byte %00000000, %00011111; fireball size
        
        ; no section 0 platform - bottom of screen is fully on  
        .byte 0,zzX_______,zzXXXXXXXX,zz_______X ; Section 1 platform
        .byte 0,0,zz______XX,zzXXXXXXXX ; Section 2 platform
        .byte 0,zzXXX_____,zzXXXXXXX_,0 ; Section 3 platform
        .byte 0,0,0,0 ; Section 4 platform
        .byte 0, 0,0,zzXX_XX_XX ; Section 0 ice
        .byte 0,0,zzXX_XX_XX,0 ; Section 1 ice
        .byte 0,0,zz_______X,zz_XX_XX_X ; Section 2 ice
        .byte 0,zzXX______,zz_XX_XX__,0 ; Section 3 ice
        .byte 0,0,0,0 ; Section 4 ice
        .byte 0,0 ; level control

I count 55 values in there, not 53, so a miscount could be contributing to some of your confusion. As a side note - the zz constants are in graphics.h, I used to use those for graphic data so I could more easily see the graphics within the code. I now let jEdit colorize binary for me, which is even more legible than using the zz constants:

post-3056-0-59118100-1512236046.png

 

The bit of code starting at line 2196 is what copies level data from ROM into the following RAM:

SECTIONS = 5 ; sections 0-4, 0 is bottom area just above score
DATA_BYTES_PER_SECTION = 11
Heights          ds 1*SECTIONS    ; heights of the levels
FireBallX        ds 2*SECTIONS    ; fireball positions, 2 per section
                                  ; x=0 means no fireball
FireBallSize     ds 2             ; 1=large, 0=small
Platforms        ds 4*SECTIONS-4  ; platforms
IceData          ds 4*SECTIONS    ; ice blocks
LevelControl     ds 2             ; level specific data-moving platform state, etc)

The first part of the data is Heights, which is why the loop uses that label, but it's copying all of this data not just the Heights.

 

There's (1*5) + (2*5) + 2 + (4*5 - 4) + 4*5 + 2 bytes there, 55 bytes.

 

SECTIONS*DATA_BYTES_PER_SECTION-4-1+2+2 = 54. Looks off, but it's not because 55 bytes will be copied when Y decrements from 54 as the use of BPL means that Y will have a value of 0 the last time through the loop. If BNE had been used then only 54 bytes would have been copied.

 

DATA_BYTES_PER_SECTION refers to the 4 with SECTIONS:

Heights          ds 1*SECTIONS    ; heights of the levels
FireBallX        ds 2*SECTIONS    ; fireball positions, 2 per section
Platforms        ds 4*SECTIONS-4  ; platforms
IceData          ds 4*SECTIONS    ; ice blocks

1 + 2 + 4 + 4 = 11

 

so SECTIONS*DATA_BYTES_PER_SECTION-4 refers to that amount of data for Heights, FireBallX, Platforms, IceData. The bottom-most platform is always fully on, so no data is needed hence the -4.

 

The +2+2 refer to what's left:

FireBallSize     ds 2             ; 1=large, 0=small
LevelControl     ds 2             ; level specific data-moving platform state, etc)

while the -1 is due to using 54 to 0 for the copy loop instead of 55 to 1.

Link to comment
Share on other sites

Next up - Preparing the Graphics Pointers for Sections 1 to 4

 

The data being used is this:

 

Heights          ds 1*SECTIONS    ; heights of the levels
...
FrostyImagePtr   ds 2*SECTIONS


Which you can also think of as being:

Height0         ds 1
Height1         ds 1
Height2         ds 1
Height3         ds 1
Height4         ds 1
...
FrostyImagePtr0 ds 2
FrostyImagePtr1 ds 2
FrostyImagePtr2 ds 2
FrostyImagePtr3 ds 2
FrostyImagePtr4 ds 2

the bit of code starting at line 1975 sets FrostyImagePtr0

 

Then the bit of code starting at line 2006 sets the rest like this:

FrostyImagePtr1 = FrostyImagePtr0 + Height0
FrostyImagePtr2 = FrostyImagePtr1 + Height1
FrostyImagePtr3 = FrostyImagePtr2 + Height2
FrostyImagePtr4 = FrostyImagePtr3 + Height3

As far as how it was implemented I'm sure there's better way to do it than all those INY and DEY instructions, but due to the time constraint we were under once I got a routine that worked I went on to the next.

 

The heights are all 1 byte values. The way to add a 1 byte value to a 2 byte value is this:

add ds 1
value ds 2

  ; value = value + add
  clc
  lda value
  adc add     ; if (low byte of value) + add > $ff then the carry flag gets set
  sta value
  lda value+1
  adc #0      ; ADC = ADD with Carry, if the carry flag is set this will add 1 to the high byte
  sta value+1

so that ADC #0 instruction is taking care of the higher byte. ADC handles a carry, which is why you should always clear the carry before the initial ADC for the low byte.

 

For the typical kernel loop you'll set Y to something like 180 at the top, then count down to 0 after which you'll have a separate kernel to draw the score. For Stay Frosty this count down is done 5 times, once for each section. This is kind of hard to see because I used a macro, found at line 123:

 

        MAC SECTION 
.SEC    SET {1}
        ldy Heights+.SEC                ;    load Y with height of current section
        lda #ICE_TOP_COLOR
        sta COLUPF
        lda (FrostyImagePtr+.SEC*2),y
        and (FrostyMaskPtr+.SEC*2),y
...
        ENDM

 

Within the game logic you'll find this at line 577:

        SECTION 4
        SECTION 3
        SECTION 2
        SECTION 1        
        SECTION 0

Those are replaced by the macro and anything with .SEC is replaced with the value specified above:

        ldy Heights+4                ;    load Y with height of current section
        lda #ICE_TOP_COLOR
        sta COLUPF
        lda (FrostyImagePtr+4*2),y
        and (FrostyMaskPtr+4*2),y
...
        ldy Heights+3                ;    load Y with height of current section
        lda #ICE_TOP_COLOR
        sta COLUPF
        lda (FrostyImagePtr+3*2),y
        and (FrostyMaskPtr+3*2),y
...
        ldy Heights+2                ;    load Y with height of current section
        lda #ICE_TOP_COLOR
        sta COLUPF
        lda (FrostyImagePtr+2*2),y
        and (FrostyMaskPtr+2*2),y
...
        ldy Heights+1                ;    load Y with height of current section
        lda #ICE_TOP_COLOR
        sta COLUPF
        lda (FrostyImagePtr+1*2),y
        and (FrostyMaskPtr+1*2),y
...
        ldy Heights+0                ;    load Y with height of current section
        lda #ICE_TOP_COLOR
        sta COLUPF
        lda (FrostyImagePtr+0*2),y
        and (FrostyMaskPtr+0*2),y
...

Having a separate kernel loop for each section is we have all those Frosty Pointers.

Link to comment
Share on other sites

I'm having a little trouble understanding how to set pointers.

I can't seem to draw the right image on the screen. Basically what I want to do is draw a little multi-coloured sprite at coordinates 40, 80.

 

I was using SET_POINTER Player0Ptr, Guerrill0 before, but that was drawing all $FFFFFFFF for the height of the character.

 

I set the pointers just before the Main Loop, and I put the pointer reference in the Over Scan.

 

Could someone help me? How do you set up pointers?

I know they're not necessary for this stage of experimentation and learning, I could use LDA Absolute,X - but at some point I will have to learn how to use pointers.

 

Please see the source code.

 

 

coloured_guerrilla005.asm

Link to comment
Share on other sites

I was able to debug this using the following steps. Hope this helps.

 

Search for GRP0 and then trace it back to the lda (Player0Ptr),y instruction.

Look at where Plater0Ptr is being set

 

lda ShapePtrLow
sta Player0Ptr
lda ShapePtrHi
sta Player0Ptr+1

 

Look at what sets ShapePtr

 

ShapePtrLow:
.byte <(Guerrilla0 - PF_HEIGHT + 80)

ShapePtrHi:
.byte >(Guerrilla0 - PF_HEIGHT + 80)

 

Build the project and take note of the following label addresses:

Guerrilla0 f0d3
Player0Ptr 86
Player0Clr 8f

Run the game and set a breakpoint on the indexed load

~

break DoDrawGrp0

~

 

Look at the values of each pointer and Y register to determine where the load is pointing to and compare it with where it should be pointing to

Player0Ptr 86 = f105
Player0Clr 8f = 0000 <- not initialized

 

Realize the color pointer was never initialized. Review the code to see why. Found copy paste error.

lda ShapePtrLow
sta Player0Ptr
lda ShapePtrHi
sta Player0Ptr+1

lda ColourPtrLow
sta Player0Ptr <- storing to the wrong variable, now both pointers are wrong :(
lda ColourPtrHi
sta Player0Ptr+1 <- storing to the wrong variable, now both pointers are wrong :(

 

​Fix copy paste error, build, set breakpoint again, run until breakpoint is hit.

 

Pointers look better:

 

Player0Ptr 86 = f0ec
Player0Clr 8f = f105

Y = 8A

 

Compute the indexed load target address:

(Player0Ptr),y = f0ec + 8a = f176

 

This is much higher than it should be. Guerrilla0=f0d3 and we should be loading P0_HEIGHT=25 past that or f0ec. Hey! f0ec is the ptr value before we added Y offset.

 

Change pointer to account for Y offset. Y starts at PF_HEIGHT and decreases as the screen is drawn by the player offset. So we need to subtract the PF_HEIGHT from the Guerrilla0 label and then add back the player's vertical offset. In this case it's simply hardcoded to $80, but once you start moving the player you'll need to set the pointer correctly by recalculating with the new offset.

 

 

ShapePtrLow:
.byte <(Guerrilla0 - PF_HEIGHT + 80)

ShapePtrHi:
.byte >(Guerrilla0 - PF_HEIGHT + 80)

ColourPtrLow:
.byte <(GuerrillaClr0 - PF_HEIGHT + 80)
ColourPtrHi:
.byte >(GuerrillaClr0 - PF_HEIGHT + 80)
Link to comment
Share on other sites

Forgive me again, I'm stuck.

 

I noticed the pointers were loading data from memory locations that were 1 or 2 addresses too far in memory.

 

So I changed LDA #P0_HEIGHT to LDA #P0_HEIGHT-2.

 

This seems to load the graphics from the correct starting memory address, but for some reason the kernel only loads 24 out of the 25 lines of graphics.

 

What am I doing wrong? Am I decrementing the scanline counter improperly? Or are the pointers not set up properly?

 

For some reason SpiceWare has his pointers for Collect set up like this:

 

Player0Ptr = Label + P0_HEIGHT - 1 - Y position

 

Would that be a better method?

I'm inching closer and closer to understanding how all this works...

 

 

coloured_guerrilla006.asm

Link to comment
Share on other sites

For some reason SpiceWare has his pointers for Collect set up like this:

 

Player0Ptr = Label + P0_HEIGHT - 1 - Y position

 

Would that be a better method?

 

Probably. SpiceWare was very careful to make the Collect code serve as an example of the right way to do things. Make sure you keep in mind that the graphics are stored backwards vertically and it's 0 indexed. This is why P0_HEIGHT is added and 1 is subtracted. The -1 accounts for the fact that the Label is already pointing to the first byte of data and if you add P0_HEIGHT to that you end up pointing at the byte just after the last byte of data. Finally the Y position is subtracted because the y register is used to index into the array, but that y register is also used to track the full 192 lines of screen height. So subtracting Y position allows this to work when the player is moved vertically without having to waste another register or variable to keep track of the player graphics/color index.

  • Like 1
Link to comment
Share on other sites

 

 

i was not an active member of this forum in that years, i think that i'm here since 2011 but had to leave any dream about atari programming by personal issues

and now i'm doing my first steps since february.

 

SpiceWare TY 1000 TIMES FOR THIS DEVELOPMENT AND DOCUMENTATION
Link to comment
Share on other sites

SpiceWare TY 1000 TIMES FOR THIS DEVELOPMENT AND DOCUMENTATION

 

 

Your welcome!

 

Do note that Stay Frosty was developed under a tight schedule, so is not well documented and thus is not the best starting point for learning - I only pointed it out to vidak as he was asking how to implement bands/sections/zones for reusing players.

 

As such, you'll be better off looking at my tutorial Collect, which covers writing a 2K game from scratch. Because it's a tutorial, I put way more comments in the source than I normally do, so be sure to download and read the source for each step. If you have an questions just post them as a comments for step you're on.

  • Like 1
Link to comment
Share on other sites

 

 

Your welcome!

 

Do note that Stay Frosty was developed under a tight schedule, so is not well documented and thus is not the best starting point for learning - I only pointed it out to vidak as he was asking how to implement bands/sections/zones for reusing players.

 

As such, you'll be better off looking at my tutorial Collect, which covers writing a 2K game from scratch. Because it's a tutorial, I put way more comments in the source than I normally do, so be sure to download and read the source for each step. If you have an questions just post them as a comments for step you're on.

 

yes, i`m into a "debugger époque" trying to understand there what i don't fully understand in some source code.

once i will have learned a little more things... for sure Collect will be the next step.

 

ty again!!

 

cheers!!

Edited by zilog_z80a
Link to comment
Share on other sites

Well, this is a huge step forward for me - I managed to set the pointers completely correctly in order to display a multi-coloured Che Guevara on the screen.

 

I got there through sheer trial-and-error.

 

It turns out the pointer needs to be set like this:

LABEL - (Playfield Height) - 1 + (Y Position) - 1

Which works out to this:

LABEL - 192 - 1 + 80 - 1

Anyway here is a screenshot of my progress, and here is my source code.

 

Thank you so much to everyone who is teaching me the skills to figure out how to solve problems on my own.

 

coloured_guerrilla006.asm

post-61331-0-66838400-1512524705_thumb.png

Link to comment
Share on other sites

I have put some more work in over the last few days!

 

Please find the current version of the source code attached. I am currently at around 1KB.

 

Changing the Main Game Mechanics

I am going to change the main mechanic of the game from "snake with guns" to what a friend has labelled "lock and load". The reason for this change is not because I don't think I could have been able to make a kernel for "snake with guns", but because I liked the "lock and load" game so much better. This is how "lock and load" works:

 

It's like scissors, paper, rock, except the three actions you can perform are: LOAD, BLOCK, and SHOOT. The game is normally played with two people on the bus, or on a park bench etc. You don't need any equipment at all. The point of the game is to shoot your opponent. However, before you do the SHOOT action, you must LOAD. You normally slap your knees twice in between rounds. So playing a couple of rounds would be like this:

 

SLAP-SLAP

 

LOAD

 

SLAP-SLAP

SHOOT

 

In the game I have been taught, you can LOAD as many times as you like. However if you play LOAD, and your friend plays SHOOT, you lose. The idea is to LOAD while they are LOADING or BLOCKING. If you BLOCK, you will always defend yourself from a SHOOT. However if you always BLOCK, no-one will ever win, especially not you. The idea is to catch your opponent out LOADING while you shoot them.

 

It turns out that this game mechanic can be very easily inserted into the current kernel I am writing. I will draw several Batista soldiers on one side of the screen, and several Guerrillas on the other, and the point of the game will be to instruct each of your guerrillas to either LOAD, BLOCK or SHOOT, and a simple AI will control the soldiers. The point of this part of the game will be to defeat the Batista soldiers.

 

Stable Screen

I am currently drawing around 257-259 lines. This is not the proper 262. I need to fix this.

 

Movement and Animation

This part of the game I need a little help with. I am able to get a little Che to move around the screen, but:

 

  • I cannot seem to set the limits of the current screen properly - i.e. the bottom of the screen I have set does not actually appear to the bottom of the screen
  • I want to display one frame of movement every 10 screen frames, but the 10-frame delay routine I have written doesn't seem to work properly. It is especially bad when you hold the controller diagonally.

    What I want to do is: (a) move Che every frame, but (b) display a new frame of movement every 10 screen frames.

    Currently my code allows Che to move, but the animation isn't working at all.

 

Would it be alright if someone took a look? Any suggestions about how to cycle the animation every 10 frames IF Player0 is moving?

 

Perhaps what I could do is increment a frame counter and then divide it by 10...

Anyway it's the end of the day here, I just wanted to put this up to show this project is still alive.

coloured_guerrilla007.asm

seventhversion.bin

Edited by vidak
  • Like 1
Link to comment
Share on other sites

Haven't looked at the code, but if you are OK with animation every 8 frames instead of 10, you can take your frame counter (if you have one) and do an AND and SHIFT to control the animation frame. This would only work if the height of the graphics and number of animation frames are powers of two, but it is probably the easiest way.

Link to comment
Share on other sites

My graphics are 25 pixels tall, and I really like the current way they look, so I'm not sure your method is going to work... Thank you so much for your help anyway. It's always reassuring to get a reply hahahah

 

The frame counter I am using is a local one, within a subroutine. It is called "AnimFrameCounter".

 

Should I be using a global frame counter, and only updating the animation frames ever 10 frames of that counter? Maybe that would work...

 

I suppose what one should always do is check and see what's happening in the Stella Debugger.

Link to comment
Share on other sites

Should I be using a global frame counter, and only updating the animation frames ever 10 frames of that counter? Maybe that would work...

That's the way I have been doing it, using 1 byte of RAM as a frame counter, and incrementing it each frame, so after 255 it wraps to 0. The disadvantage is that it only works properly with an animation where the number of frames is a power of 2.

 

Otherwise you could switch to BCD mode when you increment the frame counter, ignore the right (low) nybble, and use the left (high) nybble to control the animation frame. This would give you a maximum of 10 animation frames, unless you used more RAM. If your animation is shorter than 10 frames, you would check the value of the frame counter after it is incremented and reset it when neccesary.

 

One thing about using this method is that the duration inital frame is not always the same, and almost never the full length, but after that it would work fine. You could fix this issue by resetting the frame counter when the animation is triggered.

Edited by JeremiahK
Link to comment
Share on other sites

Okay! That gives me some ideas.

 

I have made some progress in the meantime. I will put my improvements here just in case they help someone.

 

I have a counter for each direction of animation. What is meant to happen is when you move the joystick, the frame delay counter increments every frame. When the frame counter reaches 9 (0-9, so ten increments), it will increment the frame of animation that is meant to be displayed. The frame of animation is then stored into a global animation index, which then points to the animation frame in ROM. However I was not saving that global animation index properly:

AnimateUpYes:
    inc AnimateUp0

AnimateUpNo:
    lda AnimateUp0
    and #3
    sta AnimateUp0
    sta Animation0
    clc
    adc #7
    jmp SaveFrame

This is how the code is fixed:

AnimateUpYes:
    inc AnimateUp0

AnimateUpNo:
    lda AnimateUp0
    and #3
    sta AnimateUp0
    clc
    adc #7
    sta Animation0
    jmp SaveFrame

The second big mistake I made was calling a subroutine like this:

     bne AnimationDelay

...


Animation Delay:

...

     rts

Which was causing the subroutine to exit all the way back into the Vertical Blank subroutine calls.

 

So now, when you move the joystick, the first frame from the correct table in ROM shows moving in the right direction, but there is no animation.

 

All of this was discovered by using the Stella Debugger. It is very, very useful. I'd say anyone starting out making games should always look at the way their code is running in the Stella Debugger.

 

I'd even say it might be worthwhile writing an interpreter for ASM files, so you don't have to get confused about the loss of labels and variable names.

coloured_guerrilla008.asm

guerrilla8_5.bin

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...