Jump to content

vidak

Members
  • Posts

    499
  • Joined

  • Last visited

Blog Entries posted by vidak

  1. vidak
    Okay. I was off to the wrong start with my first kernel. On the WIP thread for this game in the forums we established what was really going on as Commando 2600 drew the screen. I will repeat the "discovery" here. Commando draws the screen in bands, what SpiceWare calls in Stay Frosty, "sections". So what we are able to do as we move down the screen is forget that we have drawn the playfield and Player1. This is one of the advantages of the 2600's "racing the beam". You are able to draw as many copies of the player graphics down the screen each line as you like, so long as you have enough memory to set everything up in the code. Perhaps an illustration would help:
    TOP OF THE SCREEN-------------------------------------------------- ENEMY GRAPHICS (GRP1)------------------------------------------------------------------- TREE GRAPHICS (GRP1)-------------------------------------------------------------------- ENEMY GRAPHICS (GRP1)--------------------------------------------------------------------(NUSIZ-ed GRP1): TREE GRAPHICS TREE GRAPHICSBOTTOM OF SCREEN----------------------------------------------------
    The great benefit of drawing the screen like this is that it multiplies the amount of objects you can draw on the screen. The player character, GRP0, can move all around all of these images on the screen. None of the rules of the 2600 hardware are broken.

    I immediately attempted to go about programming a kernel that could do this. Unfortunately, because I am a novice I ran into a big conceptual issue. I couldn't imagine how it would be possible to draw different repeated kernel bands down the screen that would (a) get the 2600 to forget about GRP1, and change its data; while (b) not forgetting about GRP0 (the player character). Luckily SpiceWare provided me with the answer. He had already solved this problem in commented source code in Stay Frosty.

    In the rest of this blog post I will explain how he achieves a free-moving GRP0 among bands of repeated GRP1s and playfield graphics. The simple answer for how SpiceWare achieves a GRP0 that can move between bands of repeated GRP1 is that he uses a mask to allow the GPR0 graphics to pass through on the correct lines. The kernel is programmed to calculate and draw the GRP0 graphics, but for the inclusion of the calculation of a mask which will either permit or block the graphics to the drawn. The big picture is easy to understand, but actually practically implementing this a little more complicated.

    I How Stay Frosty Does It

    The reason I am making this blog post is that I am having trouble understanding the complex process of setting up the pointers for GRP0 and GRP1 in SpiceWare's multi-band kernel.

    A Macros

    The kernel is implemented using DASM's ability to assemble code using macros.

    What is a macro? A macro is different from a "function", "method", or "subroutine". When you type a macro into a file for DASM to assemble, you are not writing code which the Atari will end up running. You are using a set of instructions which DASM interprets and then uses to produce code that will be assembled for the Atari. The exact definition of a macro is that it is a sequence of characters that DASM will take and them map onto another sequence of characters. The purpose of having macros is to help reduce the need to repeat code mindlessly, or to reduce the conceptual complexity of producing the final characters which then go on to form the final code which is executed.

    In this case the macro that SpiceWare has written is a sequence of characters which DASM is then told to repeat five times in order to construct an entire kernel to draw the five-banded screen. This can be seen in the source code here:
    Line 577: SECTION 4 SECTION 3 SECTION 2 SECTION 1 SECTION 0
    The macro "SECTION" is defined like this. You can find a fairly accurate description of the acceptable syntax for macros in DASM here: http://www.macs.hw.ac.uk/~pjbk/scholar/dasm.html
    MAC SECTION .SEC SET {1}<CODE> ENDM
    What happens in the above code is that we define the macro SECTION, and we define it so that it has one argument (SET {1}), and we use what I assume is ".SEC" as a pseudo-variable to pass this argument into the code. Basically what DASM does is replace .SEC in the code with the argument we pass to the macro. The manual I linked above says we cannot use these arguments to write recursive macro code, but we can call macros recursively. Anyway there is no such recursive concepts in Stay Frosty.

    B Kernel Basics

    This is the basic code that draws the main character:
    lda (FrostyImagePtr+.SEC*2),y and (FrostyMaskPtr+.SEC*2),y sta GRP0 lda (FrostyColorPtr+.SEC*2),y sta COLUP0
    As you can see, there are pointers for the image, mask, and colour. The pseudo-variable that is passed into the code from the macro is also present. It is also multiplied by 2 with macro code. This means that if the number 4 is passed as an argument into the macro, the number 8 will be added to the pointer variable. Bear mind mind that each of these pointer variables are being used by Indirect Indexed instructions.

    What happens when you type in LDA (Variable),Y is that the CPU fetches the data in the variable, and uses it as the location of 16 bit address in memory. The variable is in zero page memory. The variable is only 8 bits long, so it uses the next higher byte next to the variable in memory as the higher byte of the 16 bit address. Then, the number contained in the Y index register is added to this 16 bit address. That memory location is then fetched. Here is a graphical illustration of how this works:




    The Zero Page memory location that contains the 16 bit address is called a pointer. It POINTS to the 16 bit location in memory that contains the real data we want to load. Because of the use of macros in this code, there is an extra step. We are adding the value of (.SEC x 2) to the address contained in the pointer. So we have the POINTER + (.SEC x 2) + Y which contains the final memory location which contains the data we want to load.

    So, in order to understand how we are getting the data we want to load as graphics, we need to understand how this operation works.

    We need to understand how we obtain the values for three elements:

    The Y Index Register The actual pointer The (.SEC x 2) macro code


    C The Y Index Register

    The easiest element to trace inside the code is how the Y Register is set. This code at the beginning of the SECTION macro sets the Y Register:
    LINE 138: ldy Heights+.SEC ; load Y with height of current section
    So the Y Register holds the number of scanlines we need to draw in each section. It is decremented every time we finish each scan line. We form the value of the Y Register by loading the variable Heights and adding the argument we pass into the code from the macro. So we have 2 elements we need to trace in order to understand the Y register's value. We can obtain the value of .SEC easily: it is a number between 0 and 4. It is the band of the screen we are currently drawing. If we are on band 4, .SEC equals 4.

    Where does Heights come from? Heights is formed in the PrepLevel "subroutine" macro.
    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 initLINE 2200: .plLoop lda (LevelDataPtr),y sta Heights,y dey bpl .plLoop
    The level data pointer forms the variable Heights. We loop through this loop (5 x 11 - 4 - 1 + 2 + 2) times. Which is 54 times. The explanations for the minor adjustments are in the above code. We have unused platform bytes, and some game logic encoded in the ROM along with the level data pointer information.

    We now have one element we need to trace in order to understand how Heights is formed. That is the level data pointer variable. Where is this variable formed? It is formed just above the above code in the PrepLevel macro "subroutine":
    LINE 2187: lda CurrentLevel and #%00111111 asl tay lda LevelData,y sta LevelDataPtr lda LevelData+1,y sta LevelDataPtr+1
    We form the level data pointer from the CurrentLevel variable. After some masking and shifting using the AND and ASL operations (which I assume is used to make sense of the data), we turn it into an index register. We do not need to loop through this set of operations because we can infer that CurrentLevel can only be a small number of finite states--every single level in the game. This process above illustrates the process of forming a pointer quite well. A pointer is a 16 bit memory address, and we only have 8 bit variables through which to form this address. Therefore we need to set a pointer by setting two 8 bit variables. You can see this when we use STA LevelDataPtr and STA LevelDataPtr+1.

    CurrentLevel is used as an index in order to draw data from LevelData, which is a label for information in ROM. LevelData* is in ROM like this:
    LINE 2338: LevelData .word Level1 .word Level2 .word Level3 .word Level4 .word Level1 .word Level2 .word Level3 .word Level4
    There are therefore 8 levels in this game - levels 1 to 4 repeated once. You'll notice that under each ROM label there are 54 bytes of data. This agrees with the number of times .plLoop repeats.

    So where is CurrentLevel formed? In a few places.

    At the beginning of the game, CurrentLevel is set to zero:
    LINE 1295: lda #0 sta CurrentLevel ; set initial level
    But CurrentLevel can be incremented when a level is completed:
    ; level cleared, advance to next levelLINE 1325: ldx CurrentLevel inx cpx #8 bne SaveLevel ldx #0 SaveLevel stx CurrentLevel jsr PrepLevel
    This ends the tracing needed in order to understand how the Y Register in the kernel is obtained. To recap, this is how we obtain Y:
    CurrentLevel (Values 0--> LevelData (Repeats through Level1 to Level4 twice)-> LevelDataPtr (54 bytes of information)-> Heights
    D The Actual Pointer

    I don't have enough time today to pick through everything! I will return to this tomorrow.

    Please look forward to me finishing up the kernel tomorrow.

    ============================================================================


    * I am now in such a state of concentration that the word "Level" looks strange and doesn't make sense.
  2. vidak
    Hey. I do a lot of stuff. I do this:
     
    - Upload my thesis versions and write essays on my blog: bootlickers.party
     
    - Vlog on YouTube: www.YouTube.com/JollyCommunist
     
    - Run the Hip Hegelian Memes Facebook meme page: www.facebook.com/goodoldhiphegel
     
    - Develop Homebrew Communist Atari 2600 Games: http://atariage.com/forums/topic/269750-sierra-maestra-an-early-wip/
     
    - Run an Educational Blog on Atari 2600 Programming: http://atariage.com/forums/blog/703-vidaks-blog/
     
    - Run a Blog on Video Game Hardware and Aesthetics: https://jumpnshoot9000.wordpress.com
     
    - Full Time Unpaid Socialist Activist in Sydney, Australia
     
    - Full Time PhD student.
     
    - Write poetry: http://burninghotelsdown.tumblr.com/
     
    I only get 27 000 AUD a year from my PhD scholarship, and I live in one of the most expensive cities in the world. This is less than the minimum wage for a full time worker in Australia, and is only twice the unemployment benefit.
     
    I applied for 200-300 jobs last year, and was only hired to push trolleys at a supermarket chain. It would take be 4 hours of travelling by public transport every day to do that job.
     
    This is the reality of modern life for future academics under neoliberalism.
     
    If you'd like to buy me a coffee, go ahead. If you can't say something nice, keep quiet.
     
    At the very least, just realise that I am not an exception as a PhD student, let alone a philosophy PhD student.
     
    http://ko-fi.com/hiphegelianvidak
  3. vidak
    Just checking in here after a really exhausting 48 hours after arriving back in my home town of Perth, 4000km away from Sydney, where I live now.
     
    I think I can finally relax after driving 200km yesterday and then another 200km today. We were meant to be staying in Collie, 200km south of Perth, in the country. That's where my sister in law lives.
     
    It turns out their dogs hated my dog, and we couldn't stay there, so we had to drive another 3-4 hours back up to Perth. It was very stressful.
     
    Anyway I can get back to work on the Che Guevara game tomorrow.
     
    Just thought I'd check in, because I feel a great connection to this community.
  4. vidak
    Okay! So I think I have mastered how to draw the kernel that SpiceWare outlined in StayFrosty. If make sure you read all of the blog posts under the #5 heading so you understand what I am talking about.

    Remember this his how the main player character is drawn in the kernel:
    ldy Heights+.SEC ... lda (FrostyImagePtr+.SEC*2),y and (FrostyMaskPtr+.SEC*2),y sta GRP0 lda (FrostyColorPtr+.SEC*2),y sta COLUP0

    I The "Heights" Variable






    I asked SpiceWare for some help in understanding how the Heights variable worked. It turned out my analysis was more or less correct, despite some small errors. This is how the Heights variable is set:
    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 initLINE 2200: .plLoop lda (LevelDataPtr),y sta Heights,y dey bpl .plLoop
    There are 55 bytes of data in ROM, and the Y Register is loaded with the number 54. The BPL instruction for .plLoop causes the loop at line 2200 to loop through the number zero, so we go through the loop 55 times. This loop transfers 55 bytes of data into 55 bytes of ROM. It transfers the 5 bytes of the heights of the different bands into Heights, but then it also transfers the other 50 bytes of level data into the RAM variables after heights. My analysis in #5.2 was correct.


    II Preparing the Pointers for Sections 1 to 4






    It was easy enough to understand how the pointers for the graphics were set up for Section 0 of the screen. This is because the bottom of the screen is the designated origin of the Y position of the main player character. But we need to adjust the Y positions of all the copies of the main player character up the screen. We will mask out all but one image of the main player character, but we still need to make the kernel attempt to draw the image 5 times up the screen before we mask it out.

    So this is how we prepare the pointers for the above purpose:
    LINE 2006: ldx #0 ldy #0pfLoop clc lda FrostyImagePtr,y adc Heights,x iny ; 1 iny ; 2 sta FrostyImagePtr,y dey ; 1 lda FrostyImagePtr,y adc #0 iny ; 2 iny ; 3 sta FrostyImagePtr,y <Repeated Process for the Mask and Colours here> dey ; 2 inx cpx #SECTIONS-1 bcc pfLoop
    What is happening in the above code? This is what is happening:
    We are adding a 1 byte number to a 2 byte number. The process for doing this requires you to add the 1 byte number to the lower byte of the 2 byte number, and add any overflow to the higher byte. I did not realise this was happening! So if Heights (1 bytes) + ImagePtr(Lower byte) = 256, we add the carry put in the Program Counter to ImagePtr (Highter Byte). So, what the code achieves overall is this:
     
    FrostyImagePtr1 = FrostyImagePtr0 + Height0FrostyImagePtr2 = FrostyImagePtr1 + Height1FrostyImagePtr3 = FrostyImagePtr2 + Height2FrostyImagePtr4 = FrostyImagePtr3 + Height3

    This makes perfect sense. We are setting the minimum Y position of each image pointer as the top height of the band of the screen below it.




    III The Final Component of the Kernel: (.SEC*2)






    The final element of drawing the main player character in the kernel is understanding what .SEC*2 does in the following code:
    lda (FrostyImagePtr+.SEC*2),y
    This is what is happening:
    .SEC is the argument passed into the kernel macro. This argument is converted into a pseudo-variable in the code. It is not a real variable because .SEC is not a piece of memory that contains data. .SEC is a set of characters that are mapped into the code depending on the value of the argument passed into the macro. .SEC therefore specifies a number between 0 and 4 - which tells the kernel to draw a specific band of graphics up and down the screen.

    What does " *2 " achieve though? This is simple. The image pointer is a 2 byte variable, so we need to tell the macro to specify in the code that the variable we want to specify in the final code is a 2 byte memory location. Simple.


    IV But What About the Mask and the Colour?






    The colour pointer works in exactly the same way as the image pointer. It is like a second part of the image pointer that needs to be set up separately with its own code. The process of setting a colour pointer for a multi-band screen is perfectly isomorphic with setting the graphics pointer.

    This is the data in ROM which sets out the mask used to let the main player character graphics through onto the correct band of the screen:
    LINE 865: align 256BlankGraphic repeat 50 ; 148-26 .byte 0 repend repeat 26 .byte $ff rependFrostyMask repeat 50 ; 148-26 .byte 0 repend
    The data is set out in macro code, but it is easy to interpret. It is 50 bytes (scanlines) of zeroes, followed by 26 bytes (scanlines) of $FF, followed by 50 bytes (scanlines) of zeroes. The origin of the pointer is the label FrostyMask, so we need to count down 26 bytes of memory address in order to draw the main player character, and count up 50 bytes in order to blank out the main player character. Once the kernel has counted down 26 bytes and drawn the character, there are then 50 bytes of zeroes, which will blank out drawing the graphics.

    The idea is to have the 26 bytes of $FF set to the correct scanline of the screen, so that when we bitwise AND the player graphics on the first line for them to show, the first like of the $FF of the mask is loaded.


    V The End






    That's it! Now I can get to writing my own kernel!

  5. vidak
    I looked up the specifications of the 7800, the NES, and the SMS.
     
    The 7800 is amazing! It can draw many more sprites on screen than the SMS and NES can. It also has a much larger palette of colours than the SMS and NES.
     
    What amazing hardware! I suppose the only drawback is the sound chip, which is virtually the same as the 2600 - but honestly I couldn't care, I love the 2600 sound the way it is.
  6. vidak
    I figure posting on here is better than clogging up the forums.

    I'm learning a lot by constructing my own kernel. I suppose the first thing to mention is that if you are drawing an asymmetric playfield, you MUST consciously draw the playfield every line. You are forced to update it every line, otherwise the right half will be displayed on the left by the time the beam comes around on the next line.

    You can have a 2-line resolution, as in a playfield that has its graphics changed every two lines, but you must still must actively update the left and right sides of the screen every scanline.

    Please find attached the newest version of the kernel I am writing. I dropped the missile for player0, and it still aims to have multi-coloured Player0 and Player1. The code should be well numbered and virtually complete, however erroneous it is.

    The main issue I have run into now is that I wasn't updating the playfield every scanline. I was loading the left side on scanline 1,
    and loading the right side on scanline 2. This would look horrible. It would have a shearing effect on the playfield graphics.

    What would happen is that you would have the left side drawn three times, and then the right side drawn once, I think. It would look like this:
    |===================================|=======================================||<-LEFT SIDE WRITTEN | LEFT SIDE COPIED ||===================================|=======================================|| LEFT SIDE NOT WRITTEN SO COPIED | RIGHT SIDE WRITTEN ||===================================|=======================================|
    Fortunately you can write the Playfield Registers before the scanline has finished.

    These are the timings for the left side:

    PF0 -- @55-22 PF1 -- @66-28 PF2 -- @0-38

    So I think I will have to update the playfields like this:
    |=======================================| PRELOAD LINE | UPDATE LEFT PF0 + PF1 (16 CYCLES) ||===================================|=======================================||UPDATE LEFT PF2, RIGHT PF0+PF1 (24)|UPDATE RIGHT PF2, LEFT PF0+PF1 (24 CYC)||===================================|=======================================|
    Thinking out loud like this helps me understand what I need to do.

    I have enough space in the kernel for 24 cycles per line to update the playfield, but I don't think I have enough time to use 48/76 cycles to draw an asymmetrical playfield.

    I may have to write more than a 2 Line Kernel. Either that or I have to give up an asymmetric playfield. I think using Player Graphics in order to draw the environment is looking more and more likely. Either that, or I need to find a faster way to draw an asymmetric playfield.

    What I am currently using to draw the playfield is a 5 cycle Indirect Indexed opcode, followed by the write to the register:
    LDA (LeftPF0Ptr),y ; 5 STA PF0 ; 3
    The whole thing takes 8 cycles.

    I could save a cycle by using LDA ZP,X. That would make an entire updating of one side of the playfield take 21 cycles. That would mean an entire scanline of updating the playfield would have 42 instead of 48 cycles.

    That would mean I'd only be spending 55% of the scanline updating the playfield instead of 63%, with 48 cycles. That's still an enormous amount of time, though.

    If the asymmetric playfield took 48 cycles, I would have 28 cycles left. I would have 34 left if it took 42 cycles.

    This is how long each other operation takes in the kernel:

    DoDraw for the player with no colour: 15 cycles DoDraw for the missile: 14 cycles Calculating the playfield scanline colour: 8 cycles DoDraw for the player with colour: 18 cycles Drawing the player with no colour: 3 cycles Drawing the player with colour: Loading the colour from pointer (5 cycles) Writing to COLUPX (3 cycles) Extra cycles to handle Indirect Indexing for the player graphics (3 cycles) STA GRP0 (3 cycles) 14 cycles all up. I think I could shave some cycles off this. I think instead of doing this: LDA (Player0Ptr),y - 5 STA Player0GFX - 3 LDA Player0GFX - 3 STA GRP0 - 3 (Total: 14 cycles)

    [*]I could do this:
    LDA (Player0Ptr),y - 5 TAX - 2 STX GRP0 - 3 (10 cycles)

    [*]Which saves 4 cycles


    So assuming I MUST change the colour of the playfield graphics every line, I use up 56 cycles every scanline drawing the asymmetric playfield using Indirect Indexed addressing.

    That means I have 20 cycles left every line to do other things. My immediate thoughts right now are:
    I can sacrifice missiles for the NPCs, but now for Che Sacrifice the asymmetric playfield Sacrifice colour for the players Make a 3 or 4 line kernel (I have no idea how to do this) Draw the environment with Player Graphics (Not sure how to do this) Find a faster draw routine (is SkipDraw faster?) I know there is SkipDraw, FlipDraw, DoDraw, and SwitchDraw. Apparently DoDraw is the slowest.

    I think I'll have to give up on anymore work today and have a rest. I'm at the stage now where I can't remember how to add up numbers.

  7. vidak
    I have just been told about the website 8bitworkshop.com! It's amazing!
     
    I feel like writing a kernel for an Atari 2600 game is a lot easier than I originally thought.
     
    Everything seems so cryptic when you first get into 2600 programming. As soon as you grasp that everything is about drawing the screen line by line, it becomes a little clearer.
     
    Looking at other people's code is a must. I don't think I would have learned as much as I have without reading the code of past masters.
  8. vidak
    After a day working on Commando, it's proving very confusing.
     
    The disassembler has obviously scrambled all the variable names, labels and routine titles.
     
    Maybe tomorrow I will start posting snippets of code on the forums and ask for help.
     
    I know what some of the most important variables are, I just can't seem to work out what any of them are doing.
  9. vidak
    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.


    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.

    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...


  10. vidak
    Introduction



    Despite the fact that I have always been obsessed with computers and programming, I have always been a novice at it. I only ever learned to program the simple programs you could copy out of magazines. The programming language I know best is 6502 assembly, but I started learning it 6 months ago. The next language I am most proficient in is Python. My level of skill is finishing the Code Academy guided learning tutorials.

    The subject area I am most proficient in is philosophy. I am equally good at metaphysics and political philosophy. Philosophy is not like programming at all. At least the philosophy I have been trained in. It is possible to use mathematics, statistics, and computers to do philosophy, but that kind of research is closer to the sciences than the humanities. The type of philosophy I know best is a subject area of the humanities. This kind of philosophy is very discursive and involves reading lots of words and learning a lot of qualitative information. Everything you do in the humanities is about writing essays and linking discursive concepts together into a logical argument. The word "argument" exists in programming, but it has a very different meaning. In programming, an "argument" is a value that is passed between functions, programs or subroutines. In philosophy, an argument is a collection of reasons to support a claim. If the reasons are good enough, you are entitled to "prove" your claim.

    The process of making arguments (the main activity in philosophy) is very different to programming. Programming is a mechanical process, where you have to build up your entire program or game out of discrete interlocking parts. The whole program is the entire sum of the parts that go into making it up. In philosophy, the whole is more than the sum of the parts. Even though you can have a "big picture" aim in programming, the "big picture" of a philosophy book, article, or thesis is qualitatively different, and in some ways independent of the reasoning you undertake to make up its "overall message".


    How to Start From the Absolute Beginning






    It's Okay to Do Unfamiliar Things

    Anyway, despite programming and philosophy sometimes being different, there is nothing stopping you from learning both skills. I want to put forward a theory about how it is possible to go about learning programming for the Atari 2600 if you're a humanities student (like me). The first step you take in learning something new is dedicating time and effort to starting. Through every phase of coming to master a subject area you will have to put effort in. I suppose the point I am trying to make here is that at the very beginning of learning to program for the Atari you're going to have to get used to doing something familiar. For example I've had to improve my mental maths. I frequently found myself going to google or a calculator in order to do simple arithmetic, because I had forgotten a lot of my mental maths since I had done calculus in high school. But now I'm getting better. It was an unpleasant feeling, looking at two two-digit numbers and feeling helpless to add them together. But I got there. I pushed myself, and I forced myself to keep working despite the fact I was unfamiliar with this kind of activity.

    Accept Your Current Skill

    I feel like a lot of people who go about starting off in philosophy give up when they first fail at writing a good essay. A lot of people say "I will never succeed at this goal I have to be a good XYZ". This is only true if you never try. However if you put even a minimal amount of effort in every day, say, a hour's practice at something every day, it is completely false that you will never master a specific skill. The only thing that is stopping you from achieving some specific goal -- say learning how to argue for a specific premise in an argument, or learnign how Indirect Indexed addressing works for the 6502 -- is the frustration you are feeling with the lack of a habit you have at completing that task. The whole point of learning is to form habits, and when you first start out learning something, you don't have that habit. You are frustrated when you first start off learning something because you expect to have a habit that is ready-to-hand in order to help you succeed at completing that task.

    The best strategy to diminish the level of frustration you have at learning something new is to try and accept that you don't yet have a habit for solving that task.


    Voluntarism






    So in a way, the mythology that asserts that if you spend ten thousand (10 000) hours practicing a skill, you will perfect it, is half-true. You do need to keep practicing an activity for a long time in order to become good at it.

    The mythology is in reality completely misleading and very false. This is because not just any practice will lead to you mastering a subject area. You need the correct kinds of habit-forming practice in order to eventually becoming an expert at something.

    This article (https://www.daedtech.com/how-developers-stop-learning-rise-of-the-expert-beginner/), by Erik Dietrich, demonstrates what happens if you learn the wrong kinds of habits when you first go about learning how to do something new. The article says you will definitely become an expert if you learn the right habits. It adopts a model from Dreyfus and Dreyfus to show what stages you go through if you learn all the right habits for a subject area:

    Novice; Advanced Beginner; Competent; Proficient; and Expert.

    But Dietrich says things can go wrong. If you learn bad habits learn your new subject area, you risk becoming derailed from this pathway to becoming an expert. You may advance beyond being a Novice and Advanced Beginner with bad habits in a subject area, but you will never become Competent.


    Being an Expert Beginner



    You will become an Expert Beginner instead. Dietrich helps describe what the bad state of being an Expert Beginner is:


    When you consider the Dreyfus model, you’ll notice that there is a trend over time from being heavily rules-oriented and having no understanding of the big picture to being extremely intuitive and fully grasping the big picture. The Advanced Beginner stage is the last one in which the skill acquirer has no understanding of the big picture. As such, it’s the last phase in which the acquirer might confuse himself with an Expert. A Competent has too much of a handle on the big picture to confuse himself with an Expert: he knows what he doesn’t know. This isn’t true during the Advanced Beginner phase, since Advanced Beginners are on the “unskilled” end of the Dunning Kruger Effect and tend to epitomize the notion that, “if I don’t understand it, it must be easy.”

    The Expert Beginner does not know what they do not know. They don't understand the bigger picture of the subject area they are learning. The point Dietrich is trying to make here is you will only ever learn the correct habits to finally becoming competent if you understand the bigger picture of the discipline you are becoming initiated into.

    Dietrich illustrates this argument by relaying a story about how they once used to enjoy bowling. Dietrich says when they started off as a novice at bowling, they adopted a very bad method of throwing the ball down the lane. Such a method was very bad for eventually becoming an expert and bowling, but it was adequate in order to progress right up to being an Advanced Beginner. Eventually Dietrich's bowling game hit 160, and he could never seem to improve beyond that point. So he asked for help. A mentor said he would have to completely unlearn the terrible habit he had at throwing the ball in order to eventually become competent. This meant that he would immediately become much worse at bowling before he ever become better. The frustration Dietrich went through unlearning his bad habits was so irritating he gave up bowling.

    From Humanities to Programming



    Dietrich did not understand the overall point of bowling, so he was happy to at first adopt some terrible habits about playing the game. I think the exact same lessons applies to people like me who have originally been trained in the humanities, and are trying to learn programming as a new skill.

    Don't immediately assume that just because you have mastered a couple of interesting tricks for the Atari 2600 that you have mastered everything you need to know about programming for the system. You will never truly understand how to become an expert at the Atari unless you are humble and pay attention to the advice of others. Learning the Atari is not an individual but a collective process. Make sure you socialise with the community adequately and pay attention to the work other people are doing.

    Having a box of tricks that you have memorised to deploy at certain instances is not an adequate mastery of the system.

    Maybe I am being too demanding, but I think a very deep understanding of a subject area is the only way anyone can go about becoming an expert at something.
  11. vidak
    So I'm running out of time in my current draft of my kernel. This is what I'm trying to achieve by using SpiceWare's Collect tutorial as a framework:

    Preload:
    Blank Playfield and load scanline index Y (13 cycles) Load playfield scanline colour (8 cycles) Load Player0 graphics (18 cycles) SLEEP 15 for timing of left side of playfield (15 cycles) Draw left side of asymmetric playfield (PF0, PF1) (16 cycles) (70 cycles total) WSYNC =========NEW SCANLINE===== Draw COLUP0 Draw GRP0 Draw Left PF2 Draw right PF0,PF1,PF2 Decrement scanline index Y (52 cycles total)

    Continuation of Kernel Line 2:
    Calculate Missile1 (14 cycles) (66 cycles running total) Calculate Player1 Graphics (18 cycles) (84 cycles total)

    Falling into the "stub" of Line 2 takes too long. I originally thought that I could not draw the playfield for the first scanline in the Preload section, but what I think I am going to do is get rid of Missile1 entirely. The NPCs will not have missiles to fire. They will just chase the player.

    I checked the original plan I had for the game and I didn't want the NPCs to shoot anything anyway! So if I remove calculating Missile1, I will end up with 70 cycles before the end of the second line of the kernel. The issue is there is the "first part" of Line 2 down the bottom after Line 1. Remember this is how the kernel is structured:

    Preload Second "stub" of Line 2 Line 1 First "stub" of Line 2 Loop back to second "stub" of Line 2

    So I have to make sure the second "stub" doesn't take longer than 76 minus 18 cycles (58 cycles).

    Line 1 of the kernel is taking too long, but I think by removing the need to enable Missile1 at the beginning of Line 1, we will free up enough time to draw Left PF0.

    There is more to talk about, but I think I have used this blog post sufficiently to clarify my thoughts.

  12. vidak
    I am in the process of making an Atari 2600 game. I have been given a lot of support and shown a lot of kindness, even at this very early stage. So I wanted to give back in a small way. In this blog post, I will make five arguments for why you, someone who has never done programming before, should get started in making homebrew games for the Atari 2600. This also applies to the 5200, and the 7800 -- and perhaps even to the Atari 8-bit home computers.





    Development is Easier Than It Has Ever Been



    You should get involved in making games for the 2600 because it is actually quite easy. Before 2005, you were forced to learn assembly language, the machine language of the Atari 2600 in order to make games. This is not the case anymore. There is now a high level language for programming 2600 games called batari BASIC, and it takes care of most of the difficult issues you will come across if you were forced to learn assembly.

    Many people who had never learned a computer language before picked up batari BASIC and made their own games. You can find many of these games in the Atariage store. BASIC is one of the simplest programming languages that exist. It is designed to teach beginners how to program. Don't let the simplicity of the language fool you, however. batari BASIC possesses a suite of very advanced features which many programmers back in the 70s and 80s would have paid a great deal of money to use.

    There also exist new kernels for batari BASIC that enable you to go beyond the limits of what the Atari 2600 can do alone. There exist the DPC+ kernel, which enhances the graphics of your games and allows you to make games which are more complex.

    A long-time member of this forum, Random Terrain, also keeps an enormous encyclopedia for both batari BASIC and assembly language which you can use and reference free of charge. This is an invaluable source of information for beginners and experienced programmers alike.


    It's Cheap to Get Started






    Another reason you should strongly consider starting to make homebrew Atari 2600 games is that it is very cheap to do so. Your entire development environment for making 2600 games is most likely to be composed of freeware. This means all the components you need in order to develop, test, and finish the software version your game can all be found for free. Take my development environment for instance. I use the standard 6502 assembler program that everyone else on Atariage uses: DASM. DASM is free to download. I use the powerful linux text editor Kate for all my coding. Kate is a part of linux, so it too is free. Kate is, by definition, "Free Software". Finally there is the 2600 emulator everyone on Atariage uses, Stella. Stella is donationware. This means you may download it for free, but the project survives on donations from users in order to stay active. Stella is an invaluable tool for testing and developing games because it has a "debugger" mode, which allows you to step through your code line by line and watch exactly what is being executed.

    That is the sum total of my development environment right now, and all of it was obtained free of charge. There is no need to "rent" the tools you need to develop 2600 games. You do not need to pay for any licences for software packages at all.

    There also exists the batari BASIC compiler, which will transform high level BASIC into binary ROMs Ataris and the Stella emulator can run. This is likewise also free. There is also an Integrated Development Environment program called Visual batari BASIC, which is a powerful tool for simplifying a great many repetitive tasks which crop up while developing Atari games. This is also free. However, it only runs on Windows.

    Aside from the actual tools you need to make Atari games, there is an entire encyclopedia of educational information all available free of charge online for your to peruse. All of this information on the internet will form the backbone of teaching you how to program for the Atari. Online there are:
    MOS 6502 assembly language tutorials on YouTube Many books teaching the fundamental concepts of BASIC on the AtariArchives The "live" development environment of 8bitworkshop.com Kirk Israel's Atari Programming 101 Andrew Davie's Atari Programming for Newbies (can be found in the forums and on Random Terrain's website) SpiceWare's Collect tutorial on his blog As mentioned above, Random Terrain's batari BASIC encyclopedia



    The History of 2600 Homebrew is Well Established






    People have been developing homebrew games for the Atari 2600 for a long time. Many people who developed new games for the Atari in the 1990s are still with us in the Atariage forums today. I'll leave it up to you to go and find out who they are! It won't be very difficult to complete such a task, because all you would have to do is read the Stella Mailing List. This mailing list is archived as far back as 1996. I have read and documented almost all of it. You can find my "Dig" of it on my blog. Many people developed homebrew games (some as complete beginners!) on the Stella Mailing List, resulting in titles such as Thrust!, Oystron, INV, Gunfight!, and so on. The Stella Mailing List is the bible of the homebrew community because it contains all of the solutions you will ever need to the biggest problems in coding for the Atari. Some of the biggest programming tricks in the community were developed during the years of the mailing list. Like the giant interleaved image trick, the 48 pixel sprite trick, a whole suite of sprite drawing routines (like FlipDraw, SkipDraw, SwitchDraw...), and multi-sprite tricks. 8-bitworkshop.com has transformed many of these tricks into programs which can be viewed "live" through its javascript interface.

    As I just described, there are well-established solutions to many of the biggest challenges for creating a 2600 game. Most of the time you will not need to come up with your own solutions for, say, the horizontal positioning of objects, because there is aleady an establised "community standard" for how to solve such a problem.

    Finally, there are a whole suite of disassemblies of famous original Atari games, as well as the source code to many of the most popular homebrew games. Learning to read through these disassemblies will teach you skills and ways of thinking which will improve your games.


    The Community is Large and Very Active






    The Atariage forums is very large and very active! You can always always ask for help when you are stuck on programming your game. I know I do, and someone usually always replies to a request for help within 24 hours. This is the best thing about learning to program Atari games. There are many many experienced people on the forum who dedicate their time to helping others finish their games. These truly are wonderful people, because they do it for absolutely nothing. It is for these people that I wanted to write this blog post and bring attention to the fact that the Atari homebrew community is so fantastic.

    There is no lack of people who are able to play test and debug your games! Simply posting the latest source code and binary to your games on a blog post or forum thread is enough to have someone potentially download your game and give you feedback. It's a wonderful feeling to have someone else check your work and appreciate it.

    You are also able to turn your finished games into real Atari cartridges which you can play on real Atari 2600s. They can even be sold through the Atariage store! If that isn't a goal worth striving for, I don't know what is!
  13. vidak
    The new Star Wars movie is amazing. It has inspired me so much. I felt like I was getting tired of programming, but seeing Luke Skywalker really picked me up and made me feel so energised.
     
    It is such a good movie. I enjoyed it IMMENSELY.
  14. vidak
    I decided to go into the city today and work from a Starbucks. It's a very different experience than the library. Homeless people frequently come in asking for money. I feel better about coming here than the posh Sydney University library because that way I can give my change to people who need it.

    I remember reading an article on why you should give money unconditionally to homeless people, so I'm happy I've made the change.

    Anyway today I'm gonna try and get a nice big bulk of the work needed to be done to get the mountain climbing kernel of the Sierra Maestra game working. I think I'll just code-name this Cuban Revolution game Sierra Maestra because I think I want to make the final name something more provocative.

    Anyway SpiceWare's Collect tutorial is amazing, and I'm learning a lot by following it. I think I'll be able to get 2 players, the playfield and 2 missiles working in this kernel. The main issue I have with this kernel is the mountain climbing part of the game is meant to feature 3 different types of characters - Che, the player, and 2 NPCs: peasants and Batista soldiers. I think what I'll do is I'll show each NPC on different screens. When you find a peasant and they agree to be in your party, I think I'll create an icon on the bottom of the playfield, that way you'll be able to have a peasant with you when you encounter a soldier.

    There won't be scrolling in this game, but there will be multiple screens. I'll learn scrolling later.
  15. vidak
    Well, happy new year.
     
    2017 was a real bastard of a year, but I'm ready to get stuck into 2018.
     
    Time to get back into coding the Guerrilla Game!
  16. vidak
    This is the game I am developing where the main game mechanic is like "snakes with guns".
     
    So I have learned how to write to the Playfield register, and how to count scanlines.
     
    So now I know how to do that as well as load player graphics from tables. I also learned how to load graphics using a pointer. By using LDA (ZP),Y I can point to whatever table of graphics I want to.
     
    I have also learned how to position graphics on the Y and X axis.
     
    One thing I still need how to learn how to do is change the colour of graphics line by line. I think I can also change the colour of graphics using a pointer.
     
    I think I may need to implement more than a one line kernel. Doing all these things (positioning player0, player1, the ball, the missile... Colouring everything...) will take a lot of cycles.
     
    But things are looking good. This kernel will be for the overworld, so it's a little more simple than the actual game mechanic.
     
    After I manage to master the overworld kernel, I need to invent a kernel for the "snake" part of the game. That part will involve trying to work out how to draw 2 or 3 "dots" behind a soldier icon.
     
    Because the main mechanic of the game is a game of snake where you shoot the last soldier in the line of soldiers.
     
    The snake will be easy to draw in a vertical line:
     
    o
    o
    o
     
    But not in these forms:
     
    oo
    o
     
    or
     
    oo
    ..o
     
    or
     
    ooo.
     
    So I'll need to learn how to make a simple multi-sprite kernel perhaps... Or I could use playfield graphics... Like in Surround. I could make a table of shapes to draw using playfield graphics... Surround draws playfield graphics but leaves them on the screen - it just doesn't erase the end of the line.
     
    I could ask for help on that one, or I could just have a think... I think the table of shapes to load in as the soldiers move around the screen would be a good idea.
×
×
  • Create New...