Jump to content
Andrew Davie

Session 14: Playfield Wierdness

Recommended Posts

The diagram below shows the bizarre way that bits in the TIA playfield registers (PF0, PF2) map to the onscreen pixels in reverse order. We have already seen how PF1 works - it is shown in this diagram, too.

 

This strange backwardness (not to mention inconsistency!) is probably a result of the fact that it was simpler (cheaper) to design the hardware to operate in this fashion. Among other things, this layout of pixels in our TIA registers makes scrolling horizontally a major pain in the neck!

 

The bits marked with a cross are not used by the '2600 (including the low bit in the colour registers), and you may write any value to these - it is ignored.

 

The diagram shows a shadowy "right-side" - where the 20 pixels of the left side are duplicated. Be aware that this right-side may also be mirrored, further complicating things.

post-214-1054466928_thumb.png

  • Thanks 1

Share this post


Link to post
Share on other sites
Any exercises ?

 

I'll be delivering this session in stages. I'll give some exercises once I've gotten through the explanations.

 

If you want to play with some things then....

 

1. Confirm that PF0 and PF2 have reverse pixel to bit position ordering (Hint: using binary for your values will assist you to see exactly what pixel corresponds to what bit (ie: lda #%01000000, sta PF0)

 

2. What happens if you write PF0, PF1 or PF2 in the middle of a scanline? What would you expect to happen? Can you see any use for this? (Hint: how do you think an asymmetric playfield - a different pattern on the left and right of the screen - is created?)

 

3. Write some solid shape(s) to PF0, PF1, PF2 (ie: lda #%01011110, sta PF0, sta PF1, sta PF2) and then play with changing the playfield colour several times during a scanline. How many colour changes (maximum) do you think you can get on any line? Why is there a limit?

 

4. How would a game do horizontal scrolling? This is a difficult question - but I'm trying to get you to think about the implications of the odd playfield->pixel correspondence, and the implications for game writing.

 

5. How would you make a "wall" which was 8 scanlines high, full screen width, followed by left and right walls just 1 pixel wide each, at extreme left/right edges of the screen, 176 scanlines high, followed by another horizontal "wall", full screen width and 8 scanlines high?

 

Note: this would form a "box" border around the entire playfield.

 

There we go, that should keep you busy!

 

I'll answer last sessions sessions with example code, shortly.

Share this post


Link to post
Share on other sites

1: write same value to both PF0 and PF1

               lda #%00100000

               sta PF1

               sta PF0

2352_1054473398.jpg

 

 

2: add during scanline

 

 

               sleep 40  

               lda #%00010000

               sta PF2

2352_1054473458.jpg

Share this post


Link to post
Share on other sites
1: write same value to both PF0 and PF1

               lda #%00100000

               sta PF1

               sta PF0

 

2: add during scanline

 

 

               sleep 40  

               lda #%00010000

               sta PF2

 

Happy, if you do these exercises and post your results... please explain in detail what you think you are doing/seeing. It will help others (rather than just pictures and code snippets), and also let me see if you're on the right track. The above samples... don't let me determine that.

 

In short, a bit more blurb, thanks.

 

Cheers

A

Share this post


Link to post
Share on other sites

Alrighty then :D

I fixed the code to be more obvious

1:

               lda #%10000000

               sta PF1

               sta PF0

This is showing the relation between PF1 and PF0. both in their 1st position. (now)

They appear as 1 line (because PF0's 1st position is really it's last )

 

2:

               sleep 40  

               lda #%00010000

               sta PF2

I really didn't think this one through. If you change halfway through the

scan, you'll have to change back at the begining or end of the scan :dunce:

               lda #%000100000

               sta PF1

               sta PF0

               sleep 12

               lda #%00011100

               sta PF0

               sta PF1

This code changes the playfield mid scan and does what I thought the first

bit of code was doing. Because its iterated 192 times it needs to load each

side of the playfield on every pass...

 

well hows that for a second attempt ?

Share this post


Link to post
Share on other sites

Oh yeah I got a "playfield" working :D

It's ugly but it works :)

 

     ;------------------------------------------------

     ;Top line

               lda #15                 ; A nice whitish colour

               sta COLUPF            

               lda #%11111111    ; Fill the whole playfield

               sta PF0                  ; left

               sta PF1                  ; mid

               sta PF2                  ; right

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC            ; make it 8 scanlines please

               lda #%00000000    ; Reset Playfield

               sta PF1                  

               sta PF2                  

    ;------------------------------------------------

    ;Side lines

     

Picture      lda #15                 ; load up the white

               sta COLUPF

               lda #%00010000    ; PF0's left

               sta PF0

               sleep 2                   ; wait for it to draw

               lda #$0                   

               sta COLUPF               ; then kill it

               sleep 25                  ; for 25 cycles 

               lda #15                     ; mmmm White

               sta COLUPF               ; Colour Ready to draw some more.

               lda #%10000000      ; PF2's right

               sta PF2                    ; Now draw the right side and

               sta WSYNC              ; Snooze for the rest of the scanline

               inx

               cpx #176

               bne Picture

 

 

;------------------------------------------------

;bottom line - same as top (GOTO TOP to read comments :P )

               lda #15

               sta COLUPF

               lda #%11111111

               sta PF0

               sta PF1

               sta PF2

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               lda #%00000000

               sta PF1

               sta PF2 

As for horizontal scrolling - I wish I knew :P :ponder:

Share this post


Link to post
Share on other sites

Finally got this to work on a real tv with no flipping :)

 

;Scrolling color border thingy





           processor 6502

           include "vcs.h"

           include "macro.h"





COUNTER1	= $80

COLOROFFSET     = $81

TIMETOCHANGE    = 2                   ; speed of "animation" 



           SEG

           ORG $F000



Reset
; Clear RAM and all TIA registers



               ldx #0

               lda #0

Clear           sta 0,x

               inx

               bne Clear



      ;------------------------------------------------

      ; Once-only initialisation...



               lda #0

              ;lda #$45

               sta COLUPF             ; set the playfield colour



StartOfFrame



  ; Start of vertical blank processing



           lda #0

           sta VBLANK



           lda #2

           sta VSYNC

          

              ; 3 scanlines of VSYNCH signal...



               sta WSYNC

               sta WSYNC

               sta WSYNC



           lda #0

           sta VSYNC           







              ; 37 scanlines of vertical blank...

          

               ldx #0

VerticalBlank   sta WSYNC

               inx

               cpx #36; Now waiting 36 lines

               bne VerticalBlank



;do some color offset calcs

               

 ldx #$02

 stx COLUBK;redundant as we never change COLUBK, but... :)

 ldx #0





 ldy COUNTER1

 cpy #TIMETOCHANGE

 bne notyet



 inc COLOROFFSET

 ldy COLOROFFSET

 lda #0

 sta COUNTER1



notyet

 inc COUNTER1

 ldy COLOROFFSET

 



 sta WSYNC ;  wait for last line of vblank

DrawField	

Top  sty COLUPF

 lda #$FF

 sta PF0

 sta PF1

 sta PF2

 lda #0

 inx

 iny

 sta WSYNC

 cpx #15

 bne Top



 SLEEP 3 ; sync up middle 



Middle  

 sty COLUPF

 lda #$F0

 sta PF0

 lda #0

 sta PF1

 sta PF2

 lda #0

 SLEEP 15

 sta PF0

 lda #$F0

 sta PF2

 lda #0

 sta WSYNC

 inx

 iny

 cpx #177

 bne Middle

Bottom  

 sty COLUPF

 lda #$FF

 sta PF0

 sta PF1

 sta PF2 

 sta WSYNC

 inx

 iny

 cpx #192

 bne Bottom	





           lda #%01000010

           sta VBLANK                     ; end of screen - enter blanking



               ldx #0

 stx COLUPF

Overscan        sta WSYNC

               inx

               cpx #30

               bne Overscan

 

               jmp StartOfFrame





           ORG $FFFA



           .word Reset          ; NMI

           .word Reset          ; RESET

           .word Reset          ; IRQ



      END

 

Now this has worked for a while on the emulators, but I came to discover that my vblank period was off because of the offset code which I assume put the rest of the frame out of sync slightly each pass which eventually caused the screen to hop or roll on a real tv (using cuttle cart).

 

I fixed this once but then noticed that my scanline count was 264 not 262 as the code above produces. This actually worked on my tv, so my question is: How far out of the range of 262 can you be for the game to work on most tvs (NTSC) or is it a matter of the atari used (jr vs 4 switcher for example)? Also, is there a way to reproduce the effects (flipping, etc) on an emulator that you would see on a tv other than counting scanlines?

Share this post


Link to post
Share on other sites

I'd like to know how that works with 384 in picture scanlines ?

I was under the impression stella only had 242 scanlines at the most (pal) :ponder:

Share this post


Link to post
Share on other sites
I'd like to know how that works with 384 in picture scanlines ?

I was under the impression stella only had 242 scanlines at the most (pal)  :ponder:

 

Where did you get 384? There's 3 vsych + 37 vblank + 192 picture + 30 overscan = 262 total scanlines of which only 192 are visible :)

Share this post


Link to post
Share on other sites
I'd like to know how that works with 384 in picture scanlines ?

I was under the impression stella only had 242 scanlines at the most (pal)  :ponder:

 

Where did you get 384? There's 3 vsych + 37 vblank + 192 picture + 30 overscan = 262 total scanlines of which only 192 are visible :)

Mabey I'm reading it wrong but ....

Top      sty COLUPF

     lda #$FF

     sta PF0

     sta PF1

     sta PF2

     lda #0

     inx

     iny

     sta WSYNC

     cpx #15

     bne Top



     SLEEP 3 ; sync up middle



Middle      

     sty COLUPF

     lda #$F0

     sta PF0

     lda #0

     sta PF1

     sta PF2

     lda #0

     SLEEP 15

     sta PF0

     lda #$F0

     sta PF2

     lda #0

     sta WSYNC

     inx

     iny

     cpx #177

     bne Middle

Bottom      

     sty COLUPF

     lda #$FF

     sta PF0

     sta PF1

     sta PF2

     sta WSYNC

     inx

     iny

     cpx #192

     bne Bottom  

15 lines for the top 177 scanlines for the middle and 192 for the bottom = 384 not including overscan and vblank. :?

Share this post


Link to post
Share on other sites

Ahh, I see how you got that number now.

 

The x index register is never reset so after the top is done x = 15, after middle x = 177 after bottom x = 192. Basically it is the counter for the entire visible portion, not individual portions of the visible screen. :wink:

Share this post


Link to post
Share on other sites

Galaga_Freak if COLUBK is never changed you should initalize it after Clear and before StartOfFrame.

No use doing it once every frame ;)

 

 

I cleaned my code up a bit. There was a lot of weirdness going on.

 

     ;------------------------------------------------

     ;Top line



               lda #%11111111         ;fill playfield

               sta PF0

               sta PF1

               sta PF2

               lda #15                ; It looks like white

               sta WSYNC              ; all thats visable so wait

               sta COLUPF             ; before writing the line  

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC              ; Give me 7

               lda #%00000000         ; Clear the 2 PF's that arn't PF0

               sta PF1

               sta PF2

    ;------------------------------------------------

    ;Side lines

     

Picture         lda #15                ; Re-initalize whiteness

               sta COLUPF

               lda #%00010000         ; And PF0

               sta PF0

               sleep 2                ; let it write

               lda #$0                ; now get rid of the damn thing

               sta COLUPF              

               sleep 25               ; and wait until the reflected bit has been written black

               lda #15                ; and Re-initalize the colour (again)

               sta COLUPF

               lda #%10000000         ; Line up PF2

               sta PF2

               sta WSYNC              ; wait till end of scanline

               inx

               cpx #176

               bne Picture            ; do it till it's done

 

 

;------------------------------------------------

;bottom line (yadda yadda yadda...)



               lda #15

               sta COLUPF

               lda #%11111111

               sta PF0

               sta PF1

               sta PF2

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

               sta WSYNC

 lda #$0               ; Clear to return

 sta COLUPF

Share this post


Link to post
Share on other sites
This actually worked on my tv, so my question is:  How far out of the range of 262 can you be for the game to work on most tvs (NTSC) or is it a matter of the atari used (jr vs 4 switcher for example)?  

That's a question hard to answer. The scanline range of NTSC games I have seen so far is somewhere between 241 (Artillery Duel, a few people have reported problems here!) and ~280 scanlines (e.g. Desert Falcon).

 

And many TV (especially with a vertical hold) can even display PAL games (standard: 312 scanlines, but there are some with up to 342 scanlines like Acid Drop).

 

So I assume +/- 15..20 scanlines should be no problem.

Share this post


Link to post
Share on other sites
That's a question hard to answer. The scanline range of NTSC games I have seen so far is somewhere between 241 (Artillery Duel, a few people have reported problems here!) and ~280 scanlines (e.g. Desert Falcon).  

 

And many TV (especially with a vertical hold) can even display PAL games (standard: 312 scanlines, but there are some with up to 342 scanlines like Acid Drop).

 

So I assume +/- 15..20 scanlines should be no problem.

 

This might be a stupid question, but does altering the number of scanlines affect the number of FPS displayed? Seems to me that if you went with, say, 280 scanlines (keeping 192 for the picture), that the FPS would drop below 60. Or is there something else that comes into play that I'm not aware of? (I'm just talking NTSC numbers here).

 

Another semi-related question - what if I decided to 'use' all my overscan time under VBlank? Why do the non-picture scanlines get split before/after the picture? (Or is that just a best practices standard - if so, what game(s) have done this out of curiousity?)

Share this post


Link to post
Share on other sites
This might be a stupid question, but does altering the number of scanlines affect the number of FPS displayed?  Seems to me that if you went with, say, 280 scanlines (keeping 192 for the picture), that the FPS would drop below 60.

Yup, exactly!

 

You can calculate that quite easily:

Artillery Duel: 60/241*262 = ~65Hz

Demon Falcon: 60/280*262 = ~56Hz

PAL games: 60/312*262 = ~50Hz

 

Another semi-related question - what if I decided to 'use' all my overscan time under VBlank?  Why do the non-picture scanlines get split before/after the picture?

This is done to center the visible part on the screen. After OverScan we are doing a Vertical Sync to bring the beam back to the top of the screen. So both parts (called Overscan and VBLank) below and above the visible area are about the same size.

Share this post


Link to post
Share on other sites
This is done to center the visible part on the screen. After OverScan we are doing a Vertical Sync to bring the beam back to the top of the screen. So both parts (called Overscan and VBLank) below and above the visible area are about the same size.

 

I understand what you're saying, but I've played with this a bit (I had a simple playfield with only one scanline of overscan, the rest under VBLANK) and the image was still centered as far as I can tell. Was that 'cause I was using an emulator?

Share this post


Link to post
Share on other sites
I understand what you're saying, but I've played with this a bit (I had a simple playfield with only one scanline of overscan, the rest under VBLANK) and the image was still centered as far as I can tell.  

Did you increase the VBlank time then and still had 262 scanlines?

 

If you only cut some blank lines at the bottom you won't notice that. ;)

Share this post


Link to post
Share on other sites
Did you increase the VBlank time then and still had 262 scanlines?

 

If you only cut some blank lines at the bottom you won't notice that. ;)

 

Yup - I made sure my total scanlines were still 262 (using z26 to verify my math was correct).

 

Seems like it would be simpler to use the 67 scanlines all together in one place instead of splitting code up into the 37 VBlank and 30 Overscan portions. Generally speaking of course.

Share this post


Link to post
Share on other sites

Looks like the emulator did some centering then. Definitely won't work that way on the real thing.

Share this post


Link to post
Share on other sites
Any exercises ?

4. How would a game do horizontal scrolling? This is a difficult question - but I'm trying to get you to think about the implications of the odd playfield->pixel correspondence, and the implications for game writing.

 

I modified the kernel code to use ASL commands to shift the bits in my playfield variable to the left. Since I didn't want the playfield to repeat itself twice on the screen, I added a second variable to the original kernel lesson sample.

 

The code only uses PF0 to draw the screen. The result is that the playfield scrolls horizontally from left to right, jumping the center and edges of the screen (everything else not drawned by PF0). The scrolling feels a lot like Vanguard. It's not smoothy like say, Chopper Command. I think for that you would need to use the HMOVE register with something other than the playfield, but that's ok for now.

 

On PCAEWIN it works fine, but on Z26 it draws some garbage at the top of the screen. Take a look at the code:

 

; 2600 for Newbies
; lesson 14 - playfield
; Question 4 - One way to do horizontal scrolling with the playfield.



processor 6502

include "vcs.h"

include "macro.h"


; OBJECTIVE:  This variation of the lesson 14 kernel does horizontal scrolling with the playfield.
;  Only PF0 is used. The program uses to variables to store the left and right side values
;  of the playfield. These are declared below:



PATTERN_LEFT	= $80

PATTERN_RIGHT	= $82



TIMETOCHANGE	= 20



SEG

ORG $F000



RESET

; Clear RAM and all TIA regs



LDX #0

LDA #0



CLEAR



STA 0,X

INX

BNE CLEAR



LDA #$45

STA COLUPF



LDY #0


; Here we assign the pattern values to our left and right side of the playfield. 
; Try using different values.



LDA #$0

STA PATTERN_LEFT 



LDA #$01

STA PATTERN_RIGHT



STARTOFFRAME



LDA #0

STA VBLANK



LDA #2

STA VSYNC



STA WSYNC

STA WSYNC

STA WSYNC



LDA #0

STA VSYNC


; 37 scanlines of vertical blank



LDX #0



VERTICALBLANK



STA WSYNC

INX

CPX #37

BNE VERTICALBLANK


; HANDLE A CHANGE IN THE PATTERN ONCE EVERY 20 FRAMES
; WRITE THE PATTERN TO PF1



INY  	

CPY #TIMETOCHANGE	

BNE SCROLLCOMPLETE  



LDY #0  	


; The following code uses arithmetic left shift commands (ASL) to make the scrolling happen.
; We shift the bits of the playfield variables to the left. Original bit 7 is shifted to the Carry.
; The program uses BCC to check if a bit was dropped. Dropped bits are inserted to the
; opposite pattern variable by the INC command. The INC command can mess up the pattern if
; the original bit 0 is turned on. To avoid this we shift everything to the left before incrementing. 



SHIFTRIGHT



CLC

ASL PATTERN_RIGHT

BCC SHIFTLEFT



ASL PATTERN_LEFT

INC PATTERN_LEFT

BCC SCROLLCOMPLETE



INC PATTERN_RIGHT

JMP SCROLLCOMPLETE



SHIFTLEFT



ASL PATTERN_LEFT

BCC SCROLLCOMPLETE

INC PATTERN_RIGHT





SCROLLCOMPLETE




; DO 192 SCANLINES OF COLOR CHANGING (OUR PICTURE)



LDX #0  	



PICTURE 



STX COLUPF  


; Load the left pattern into PF1 register.



LDA PATTERN_LEFT

STA PF1


; Wait enough cycles before drawing right pattern.



SLEEP 20


; Load right pattern to PF1.



LDA PATTERN_RIGHT

STA PF1



STA WSYNC  



INX  	

CPX #192  

BNE PICTURE  



LDA #%01000010

STA VBLANK


; 30 LINES OF OVERSCAN



LDX #0



OVERSCAN



STA WSYNC

INX

CPX #30

BNE OVERSCAN



JMP STARTOFFRAME



ORG $FFFA



INTERRUPTVECTORS



.WORD RESET

.WORD RESET

.WORD RESET



END



 

 

The tricky code is the bit shifting. Here is the pseudocode:

 

 

[color=blue]; Pseudocode:
;
; Clear Carry
; Shift bits in variable PATTERN_RIGHT to the left. Original bit 7 is shifted into the Carry.
;
; if no bit was dropped from PATTERN_RIGHT (Carry Clear) then
;
;	Shift the bits in PATTERN_LEFT
;	if no bit was dropped from PATTERN_LEFT then
;
;  Scroll is complete. Continue program flow.
;
;	else
;
;  Insert dropped bit from left pattern into PATTERN_RIGHT (Using the INC command). 
;
;	end
;
; else
;
;	Shift bits in PATTERN_LEFT
;	Insert dropped bit from right pattern into PATTERN_LEFT.
;	If no bit was dropped from PATTERN_LEFT then
;
;  Scroll is complete.
;
;	else
;
;  Insert dropped bit from left pattern into PATTERN_RIGHT.
;
;	end
;
; end[/color]

 

 

This translates to:

 

SHIFTRIGHT



CLC

ASL PATTERN_RIGHT

BCC SHIFTLEFT



ASL PATTERN_LEFT

INC PATTERN_LEFT

BCC SCROLLCOMPLETE



INC PATTERN_RIGHT

JMP SCROLLCOMPLETE



SHIFTLEFT



ASL PATTERN_LEFT

BCC SCROLLCOMPLETE

INC PATTERN_RIGHT





SCROLLCOMPLETE

 

I am sure there is a better way of doing this without so many BCC's and ASL's (perhaps getting rid of the JMP altogether).

question4.zip

Share this post


Link to post
Share on other sites
I am sure there is a better way of doing this without so many BCC's and ASL's (perhaps getting rid of the JMP altogether).

:idea: Hint: Have a look at ROL.

 

This will make your code much simplier.

Share this post


Link to post
Share on other sites

Thanks Andrew,

 

It took me a lot of frustrating trial and error before I concluded that the bits in PF2 were displayed in reverse. I'm glad someone is making the TIA's quirks clear.

 

Zach

Share this post


Link to post
Share on other sites
I am sure there is a better way of doing this without so many BCC's and ASL's (perhaps getting rid of the JMP altogether).

:idea: Hint: Have a look at ROL.

 

This will make your code much simplier.

 

Thanks Thomas. With your help I was able to take this:

 

 CLC

ASL PATTERN_RIGHT

BCC SHIFTLEFT



ASL PATTERN_LEFT

INC PATTERN_LEFT

BCC SCROLLCOMPLETE



INC PATTERN_RIGHT

JMP SCROLLCOMPLETE



SHIFTLEFT



ASL PATTERN_LEFT

BCC SCROLLCOMPLETE

INC PATTERN_RIGHT



SCROLLCOMPLETE

 

Into this:

 

 CLC

ROL PATTERN_RIGHT

ROL PATTERN_LEFT

BCC SCROLLCOMPLETE



INC PATTERN_RIGHT 



SCROLLCOMPLETE

 

Wow, what a difference ROL makes! I am glad I did it the hard way first, otherwise I wouldn't had appreciated the difference. I have a newbie assembler question: How many machine cycles does the ROL commands above take? Is it 5 for zero page?

Share this post


Link to post
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.

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