CSBouffard Posted May 7, 2012 Share Posted May 7, 2012 Sorry if this topic has been beaten like a dead horse, but ... I am new to programming the 2600, but have prior assembly experience with my C=64. Recently I wrote my first 2600 game. It required fine (HMOVE) horizontal sprite positioning, but didn't involve vertical movement. I am now trying to understand the various skipdraw routines out there. While I have answered some of my own questions, others have eluded me thus far. I really don't want to start designing a kernel when I know I wouldn't understand everything that I'm doing. Here's what I understand as of today: "Standard" skipdraw routines seem to involve variations on the following: TXA ; transfer scanline counter to A SEC ; set the carry flag SBC #P1YCOORDINATE ; subtract the sprite vertical position ADC #P1SPRITEHGHT ; add with carry the sprite height BCC SkipSprite ; if carry flag clear, skip sprite TAY ; transfer the result to Y index LDA (P1GraphicArray),y ; load image using indirect indexing STA GRP1 ; latch image to TIA register SkipSprite ... [The routine assumes the following: (1) the scanline counter is X, which starts at 192 and decreases; (2) P1YCOORDINATE is the vertical sprite position of the top of the sprite, measured from the bottom of the screen (just like the scanline counter); (3) P1SPRITEHGHT is the scanline height of the sprite (usually to include the final "zero byte"); and (4) the sprite is stored in ROM "upside down," starting with the "zero byte."] Having spent some time reverse engineering this routine, I'm convinced that whoever wrote it is an assembly genius. For the benefit of fellow newbies out there, it takes advantage of the overflow/underflow properties of 6502 unsigned arithmetic (meaning that numbers vary from 0 to 255). An "overflow" occurs if an addition results in the accumulator exceeding 255. The 6502 sets the carry flag to advise you of that. Similarly, an "underflow" occurs if a subtraction results in the accumulator dropping below 0 (e.g., #$FF). The 6502 clears the carry flag to advise you of that. The routine first sets the carry flag, as is required of 6502 subtraction. It then subtracts from the scanline counter the sprite Y/vertical position. It then adds to that the sprite's vertical height. The result: (1) When the scanline counter (X) is above (greater than) the sprite Y/vertical position, the subtraction will never result in an "underflow," and the carry flag will remain set. When the ADC (add with carry) is then executed, the carry flag will be cleared/consumed. A clear carry flag = don't draw the sprite. (2) When the scanline counter (x) equals the sprite Y/vertical position, we're getting close. The subtraction will equal 0, and the carry flag will remain set. When the addition is executed, the carry flag will clear. Clear carry flag = no sprite yet. (3) When the scanline counter is one less than the sprite Y/vertical position, things change. The subtraction will result in an "underflow," (A=#$FF) and the carry flag will clear. Now we add the sprite height (assume it's #10/$A, for the sake of this example). We get an "overflow," and the carry flag will set. Set carry flag = generate the sprite. (Note also that A = #9.) (4) As this continues, at some point the sprite height addition will no longer cause an "overflow." The initial subtraction will clear the carry, and the subsequent addition will not be large enough to set the flag by "overflow." Clear carry flag = don't draw the sprite. In other words, the sprite "window" has passed. In my example, during the period that the sprite is generated (carry flag set), the result of the calculations go from A=#$09 to A=#$00. A fringe benefit of this skipdraw routine is that the accumulator gives you the sprite index for the data array! Thus, the routine does a TAY and uses that index to indirectly load the sprite data. Pretty cool. With that understanding (or so I hope), here's where I'm confused: (1) In Andrew Davie's (completely awesome) tutorial (Session 23), it is suggested in the forum comments that P1GraphicArray should be manipulated such that you would use the scanline counter as the Y index for the the LDA (P1GraphicArray),y instruction, such that it would point to the applicable sprite data. As best as I understand, this would require something like a subtraction of the scanline counter from the array pointer address stored in zero page. If I understand the above routine, that would seem unnecessary. What am I missing? Is there an advantage to doing the indirect indexing that way? (2) Thomas Jentzch wrote well-known skipdraw routines that utilize illegal op codes. They have gone completely over my head thus far. For example, he references illegal op code BCX ... or is that a typo? (I haven't been able to find info on BCX.) Also, "If you like using illegal opcodes, you can use dcp (dec,cmp) here: lda #SPRITEHEIGHT ; 2 dcp SpriteEnd ; 5 initial value has to be adjusted bcx .skipDraw ; 2 = 9 ... Advantages: - state of carry flag doesn't matter anymore (may save 2 cycles)" I have not been able to understand how this code replicates what was discussed above. (To begin with, I'm unclear what DCP does, even with some online material available.) I don't see how the "state of the carry flag doesn't matter anymore." Maybe I'm misinterpreting what he means or how this works ... I could really use some guidance and/or clarification on that one. Thanks! Chris Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted May 7, 2012 Share Posted May 7, 2012 (2) Thomas Jentzch wrote well-known skipdraw routines that utilize illegal op codes. They have gone completely over my head thus far. For example, he references illegal op code BCX ... or is that a typo? (I haven't been able to find info on BCX.) Also, "If you like using illegal opcodes, you can use dcp (dec,cmp) here: lda #SPRITEHEIGHT ; 2 dcp SpriteEnd ; 5 initial value has to be adjusted bcx .skipDraw ; 2 = 9 ... Advantages: - state of carry flag doesn't matter anymore (may save 2 cycles)" I have not been able to understand how this code replicates what was discussed above. (To begin with, I'm unclear what DCP does, even with some online material available.) I don't see how the "state of the carry flag doesn't matter anymore." Maybe I'm misinterpreting what he means or how this works ... I could really use some guidance and/or clarification on that one. I didn't have time to read your whole post, but: BCS is what BCX represents in this case. Maybe Thomas was using a different compiler at the time when he wrote the code, but we are indeed using BCS here. DCP is an illegal opcode. It stands for decrement and compare. It subracts '1' from a memory location without borrow, and compares it to the value in the accumulator. So You don't have to worry about the state of the carry prior to using DCP, and this is what Thomas meant. However, once the DCP operation is finished the carry will be set or cleared just like a normal compare operation would do. Quote Link to comment Share on other sites More sharing options...
DEBRO Posted May 7, 2012 Share Posted May 7, 2012 Hi there, (2) Thomas Jentzch wrote well-known skipdraw routines that utilize illegal op codes. They have gone completely over my head thus far. For example, he references illegal op code BCX ... or is that a typo? (I haven't been able to find info on BCX.) Also, "If you like using illegal opcodes, you can use dcp (dec,cmp) here: lda #SPRITEHEIGHT ; 2 dcp SpriteEnd ; 5 initial value has to be adjusted bcx .skipDraw ; 2 = 9 ... Advantages: - state of carry flag doesn't matter anymore (may save 2 cycles)" I have not been able to understand how this code replicates what was discussed above. (To begin with, I'm unclear what DCP does, even with some online material available.) I don't see how the "state of the carry flag doesn't matter anymore." Maybe I'm misinterpreting what he means or how this works ... I could really use some guidance and/or clarification on that one. I didn't have time to read your whole post, but: BCS is what BCX represents in this case. Maybe Thomas was using a different compiler at the time when he wrote the code, but we are indeed using BCS here. DCP is an illegal opcode. It stands for decrement and compare. It subracts '1' from a memory location without borrow, and compares it to the value in the accumulator. So You don't have to worry about the state of the carry prior to using DCP, and this is what Thomas meant. However, once the DCP operation is finished the carry will be set or cleared just like a normal compare operation would do. Actually the X is a wildcard. It could be BCC or BCS based on your implementation. You would BCC to "skip" but you would BCS to "draw". 1 Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted May 8, 2012 Share Posted May 8, 2012 Thanks Dennis, that makes sense now. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.