Guerrilla Game #5.2
I Problems
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:
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.
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.
I know there are 4 unused bytes for drawing the platforms. Anyway let's continue attempting to understand how the kernel is drawn.
II The Actual Pointer
Remember, this is how the kernel is drawn:
ldy Heights+.SEC ... lda (FrostyImagePtr+.SEC*2),y and (FrostyMaskPtr+.SEC*2),y sta GRP0 lda (FrostyColorPtr+.SEC*2),y sta COLUP0
We need not worry about the strange loading of 54 bytes of information into the memory location relative to Heights, because only the data in the first 5 bytes relative to Heights is loaded into the Y Register. Heights is only set out as 5 bytes large, and the first 5 bytes of ROM contain the section heights, and the .SEC pseudo-variable only increments Heights by 5 bytes of memory addresses.
A FrostyImagePtr
The first element of the kernel we need to trace is the Image Pointer. This pointer contains the 16 bit memory address that contains the main player graphics. How is this pointer formed?
LINE 1975: SET_POINTER FrostyImagePtr, FrostyGraphic
It is formed with the macro SET_POINTER. All of this code is executed in Vertical Blank. You can find the set of instructions which corresponds to SET_POINTER in MACRO.H:
MAC SET_POINTER.POINTER SET {1}.ADDRESS SET {2} LDA #<.ADDRESS ; Get Lowbyte of Address STA .POINTER ; Store in pointer LDA #>.ADDRESS ; Get Hibyte of Address STA .POINTER+1 ; Store in pointer+1 ENDM
There are two arguments for this macro. The first argument is the pointer variable in memory. The second argument is the memory address. What this macro does is store the 16 bit memory address location inside the 2 bytes of pointer memory. Simple enough to understand.
For your reference, this is what exists at the memory address we have now stored inside the FrostyImagePtr: FrostyGraphic is a label in ROM:
LINE 986: .byte zz__XXXX__ ; 0 .byte zz_XXXXXX_ ; 1 .byte zzXXXXXXXX ; 2 .byte zzXXXXXXXX ; 3 .byte zzXXXXXXXX ; 4 .byte zzXXXXXXXX ; 5 .byte zzXXXX_XXX ; 6 .byte zz_XXXXXX_ ; 7 .byte zz__XXXX__ ; 8 .byte zz_XXXXXX_ ; 9 .byte zz_XXX_XX_ ; 10 .byte zz_XXXXXX_ ; 11 .byte zz_XXXXXX_ ; 12 .byte zz_XXX_XX_ ; 13 .byte zz__XXXX__ ; 14 .byte zz___XX___ ; 15 .byte zz__X__X__ ; 16 .byte zz__XXXX__ ; 17 .byte zz__X_X___ ; 18 .byte zz__XXXX__ ; 19 .byte zz___XX___ ; 20 .byte zz__XXXX__ ; 21 .byte zz___XX___ ; 22 .byte zz___XX___ ; 23 .byte zz___XX___ ; 24 .byte zz___XX___ ; 25FrostyGraphic
There is some code directly underneath the SET_POINTER macro call:
LINE 1979: sec lda FrostyImagePtr sbc FrostyY sta FrostyImagePtr lda FrostyImagePtr+1 sbc #0 sta FrostyImagePtr+1
This adjusts the line of graphics we load into the kernel. The variable FrostyY stores the vertical position of the main player character on the screen. So what we are doing is adjusting the line of graphics we load into the kernel relative to what FrostyY contains. What happens in this game is that Frosty--the main player character--melts. As Frosty melts, we load less and less of the player graphics as Frosty sinks into the ground.
The above setting of the pointer is sufficient for a game that does not have a kernel which is repeated in bands down the screen. Because we have a game with bands, we need to adjust the graphics pointers. If we do not adjust the pointers for the main player character, it will only be visible on a single band. The main player character needs to (a) be visble in different bands; and (b) be visible moving between bands. I assume this can all be achieved by adjusting the pointers relative to the values of the heights of the different bands up and down the screen.
This is how SpiceWare prepared the graphics pointer for Sections 1-4. This means the main player character must appear at Section 0 at the beginning of the game, and is the origin for the Y position of the main player character.
LINE 2006: ldx #0 ldy #0pfLoop 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.
The final process of forming the main player's graphics pointers is tweaking the pointers so that the memory addresses they point to don't cross any page boundaries. When the kernel uses the LDA (ZP),Y operation in the kernel, it needs to take a precise amount of time. If the CPU has to cross a page boundary in memory in order to fetch the data at the pointer's address, it will add a cycle and disrupt the very precise timing of the kernel.LINE 2059:;+-------------------------+;| tweek Frosty's pointers |;| so no page breaks occur |;+-------------------------+ ldy #1MaskLoop lda FrostyMaskPtr,y ; Load higher byte of Mask Pointer cmp #>BlankGraphic ; CMP with higher byte of BlankGraphic beq MaskCheck2 ; If it is blank then branch lda #>BlankGraphic ; If not, load higher byte of BlankGraphic sta FrostyMaskPtr,y sta FrostyImagePtr,y sta FrostyColorPtr,y ; ...and store it into the higher byte of the pointers. dey ; This blanks them all out. lda #0 sta FrostyMaskPtr,y sta FrostyImagePtr,y sta FrostyColorPtr,y ; ...blank out all of the lower bytes of the pointers as well iny bne NextMask ; Always branchMaskCheck2 dey lda FrostyMaskPtr,y cmp #<FrostyMask bcc NextMask2 ; Branch if lower byte of Mask Pointer (less or) equal to lower byte of FrostyMask lda #0 sta FrostyMaskPtr,y sta FrostyImagePtr,y sta FrostyColorPtr,y ; ...if not (higher than) blank out lower byte of pointers iny lda #>BlankGraphic sta FrostyMaskPtr,y sta FrostyImagePtr,y sta FrostyColorPtr,y ; ...and blank out the higher byte as well bne NextMask ; Always branch?NextMask2 inyNextMask iny iny cpy #SECTIONS*2 ; Have we gone through the loop 10 times? (5 x 16 bits worth of memory addresses) bcc MaskLoop ; If we haven't, keep looping!
This is about all I can achieve for now...
0 Comments
Recommended Comments
There are no comments to display.