Jump to content
IGNORED

Session 18: Asymmetrical Playfields - Part 2


Recommended Posts

I have been reading these lessons for a couple of days now. I'm starting to learn a few things here... a few things there :) It's so great you're doing this.

 

Why not eventually show how to move sprites, how to implement simple routines (enemies moving towards the player, collision checks) or more simply... show how to make a simple game and explain everything.Step by step :)

Link to comment
Share on other sites

The graphic certainly lives up to its namesake. It truly is an "Incredibly Good Timing Diagram."

 

The only thing I'd add to the diagram is an annotation or a footnote stating that a write to the WSYNC register halts the microprocessor until the TIA gets to time zero, the start of the Horizontal Blanking period. I was confused over this "obvious" fact for the longest time, thinking the microprocessor resumed execution immediately AFTER the 68 color clocks of Horizontal Blanking.

 

Ben

Link to comment
Share on other sites

The graphic certainly lives up to its namesake. It truly is an "Incredibly Good Timing Diagram."

 

The only thing I'd add to the diagram is an annotation or a footnote stating that a write to the WSYNC register halts the microprocessor until the TIA gets to time zero, the start of the Horizontal Blanking period.  I was confused over this "obvious" fact for the longest time, thinking the microprocessor resumed execution immediately AFTER the 68 color clocks of Horizontal Blanking.

 

Ben

Thanks :) I was just about to ask that one :D

Link to comment
Share on other sites

Excellent diagram. Now I have something to compare to my sketches! Keep up the great work, Andrew. I'm lovin' this tutorial and have been following it closely.

 

Right now I have an animated sprite that can run around the screen. When he gets above the "horizon" his sprite goes into jump mode, although you can still move him around like he were walking...sort of a Crouching Tiger, Hidden Dragon thing. :)

 

Thanks!

Jason Rein

Link to comment
Share on other sites

Why not eventually show how to move sprites, how to implement simple routines (enemies moving towards the player, collision checks) or more simply... show how to make a simple game and explain everything.Step by step :)

 

Patience, young Jedi master! All things will be revealed to those who seek the way.

Link to comment
Share on other sites

I shall wait master ;)

 

I'm thinking of converting a little game I wrote in BASIC to the Atari 2600. The game was called NEOBYKE and was based on the lightcycles in the movie TRON. So I'm trying to learn 6502(6507) assembly to get the job done :) In a way it's much more fun program for a console like the 2600. I mean, anyone can write a game for the PC but it'll just drown with 1000s of other similar games. And besides, the 2600 is quite a beast and being able to tame it... well then I suppose you COULD call yourself a jedi master ;)

 

Keep up the good work.

Link to comment
Share on other sites

Thomas Jentzsch has pointed out that there is a mimimum delay before a change to a TIA playfield register becomes visible. He suggests ~2 TIA clocks.

 

When one considers that a 6502 instruction may take anywhere from 2 cycles (=6 TIA colour clocks) to 7 cycles, it is apparent that any particular 6502 instruction "occupies" a fairly wide slice of TIA time during its execution. All instructions require a fetch of the opcode (=6502 instruction) from memory, the decoding and execution of that instruction, and sometimes a write of data back to memory.

 

These stages of 'execution' of an instruction happen at various times during the time taken to execute the entire instruction. For example, the first cycle of the total instruction time might be allocated to retrieving the opcode from ROM. The second might be allocated to decoding and executing some of the instruction. Truth be told, I'm not really sure what happens when - it will differ for each of the instructions and addressing (=access to memory) modes.

 

The point is, though, that when we write to the TIA playfield registers (or any other register for that matter), one may have to make allowances for the fact that although you may start an instruction on a particular TIA / 6502 clock cycle, the actual write to the TIA memory/register will most definitely not happen until 2 or more cycles later - and that depends on the addressing mode. We will cover addressing modes later - but basically they deal with ways of accessing memory (eg: directly, indirectly via pointers, via a list (indexec), etc).

 

The timing diagram above should be considered to indicate the time at which TIA playfield registers must be updated by, for correct playfield data to be displayed.

 

Another issue altogether - and one I simply don't know the answer to right now - is *EXACTLY* what happens when you write to a playfield register when that playfield register is currently being displayed. I am not sure exactly what timing constraints determine which pixel is displayed in which situation - the old or the new. Thomas has also indicated that there are some reports of consoles behaving differently when you get into this sort of extreme 'pushing the envelope' timing, too.

Link to comment
Share on other sites

On the Atari VCS you can't change the state of a playfield pixel while it is being processed. So, for example, you could write the data for the right copy of PF2 while the last pixel of the left copy PF2 is still being displayed. This pixel will stay on or off, even if the new value is the opposite of it's current state.

 

You can however change the colour of a playfield pixel while it is diplayed. The colour information is evaluated ion a TIA-pixel by TIA-pixel basis. So if you wrote to COLUPF in such a way that the write finishes halfway though a playfield pixel, then it's first two TIA-pixels will have the old colour and it's last two TIA-pixels will have the new colour.

 

I have heard reports that the Coleco expansion module behaves a bit differently when rewriting playfield registers, but I don't know the exact timing either.

 

 

Ciao, Eckhard Stolberg

Link to comment
Share on other sites

So, from what Eckhard is saying, it sounds as if the TIA latches the value of the PFx register when it starts drawing, and uses that value, even if the original value changes. Does that sound right?

 

BTW, I've been working off the assumption that as long as the instruction finishes on or before the cycle on the chart, that should be fine. For example, PF0 finishes at 6502 cycle 28, so as long as my STA PF0 finishes at cycle 28 or later, it should be fine. But, I've been waiting for the TIA to finish drawing a particular portion of the playfield (i.e, I'm stuffing the values into the registers as early as I possibly can, and I'm just waiting for the register to be free again), so my assumptions may be completely wrong. And considering what Eckard said, it might not matter anyway :)

 

So, does anyone know for certain how the timing works for these fringe cases? If I reverse my example above, if I have a STA PF1 that takes 3 cycles, and I start it on 6502 cycle 25, since it finished on cycle 28, will that display properly? Logically, it should work since the instruction is finished at cycle 28, and the TIA shouldn't be drawing until when cycle 29 begins...

Link to comment
Share on other sites

As our knowledge of playfield manipulation increases, so does the desire to create more complex backgrounds. What are the best tools to design playfields? I think someone mentioned not so long ago the use of an Excel spreadsheet as a huge grid map. If there is any experienced homebrewer out there reading this: what have worked best for you?

Link to comment
Share on other sites

As our knowledge of playfield manipulation increases, so does the desire to create more complex backgrounds. What are the best tools to design playfields? I think someone mentioned not so long ago the use of an Excel spreadsheet as a huge grid map. If there is any experienced homebrewer out there reading this: what have worked best for you?

 

 

I draw my stuff in a paint program, save it out as a 40 x 192 pixel file (or whatever depth you like) and run it through a tool written for the purpose. This tool creates tables of data which can be simply copied to the screen as required. I'm happy to share it - but it's not that tricky to write your own.

Link to comment
Share on other sites

Dang it -- I just re-read Eckards post... the TIA appears to be latching a single pixel, not the entire value of PFx, right?  That actually makes more sense when I look at it :)

 

This is correct. If you change a playfield register while the third playfield pixel is currently being displayed, then the first three playfield pixels will be taken from the old value in the playfield register, and the last five playfield pixels will be taken from the new value.

 

 

Ciao, Eckhard Stolberg

Link to comment
Share on other sites

This is correct. If you change a playfield register while the third playfield pixel is currently being displayed, then the first three playfield pixels will be taken from the old value in the playfield register, and the last five playfield pixels will be taken from the new value.

 

Which TIA cycle does the 6507 write cycle "complete" on? The last third, I think, right?

Link to comment
Share on other sites

I'm not quite sure if I understand your question correctly. Is it about the relation between the TIA clock and the processor clock?

 

There are three TIA cycles for every CPU cycle. The CPU dosn't do anything between the cycles it gets clocked with, so CPU write cycles (like all other CPU cycles too) happen on every third TIA cycle. If you'd do something like:


sta WSYNC

nop

nop

nop

nop

nop

nop

nop

nop

nop

nop

sta COLUBK

then the background colour would change after the first TIA pixel has already been drawn, because the STA COLUBK would finisch it's write cycle at CPU cycle 23 (which is TIA cycle 69, which is one TIA cycle after the horizontal blank period).

 

 

Ciao, Eckhard Stolberg

Link to comment
Share on other sites

then the background colour would change after the first TIA pixel has already been drawn, because the STA COLUBK would finisch it's write cycle at CPU cycle 23 (which is TIA cycle 69, which is one TIA cycle after the horizontal blank period).

Thanks, that was the info I was looking for.

Link to comment
Share on other sites

The assymmetric chart has been extremely helpful to me. I first read about the timing quirk in "How to draw an assymetrical playfield" source sample at The Dig but never fully understood it until now. Here is the stuff I did: Sample1.zip is a mockup of the Donkey Kong stage. Sample2.zip is the Atari Age banner, 2600 style :)

 

For the last one I changed the PF color once per frame right before the text logo for a simple color cycling effect. While doing this I found myself thinking where to "put it". I needed 9 free cpu cycles that wouldn't interfere with the PF writing code. After some trial and error I put it somewhere at beginning of the scan loop.

 

1) Is it possible to create the data tables in a separate text file and have it inserted into our program as an 'include "mypfdata.h"'?

2) I used PF data tables to draw my assymetrical playfields. The data is for every scanline in the visible kernel. The drawings I did have empty spaces at the top and bottom. This translates into a lot of zero byte entries, that takes valuable ROM space. I could shave off those data lines and adjust the kernel code accordingly. Is there is another way?

post-3560-1057600741_thumb.jpg

post-3560-1057600742_thumb.jpg

sample2.zip

sample1.zip

Link to comment
Share on other sites

1) Is it possible to create the data tables in a separate text file and have it inserted into out program as an 'include "mypfdata.h"'?

 

Yes. That's really all the line:

 

include vcs.h

 

does.

 

2) I used PF data tables to draw my assymetrical playfields. The data is for every scanline in the visible kernel. The drawings I did have empty spaces at the top and bottom. This translates into a lot of zero byte entries, that takes valuable ROM space. I could shave off those data lines and adjust the kernel code accordingly. Is there is another way?

 

Yes, there's always another way.

 

Here are a couple suggestions...

 

If the blank lines are a known constant, then at the top and bottom of the screen (ie: Before and after you actually start drawing) you can add something like:

 


  lda #10

nextblank:

  sta WSYNC

  dex

  bpl nextblank

 

where #10 is the number of blank lines you need.

 

If the number of blank lines is not constant (ie: You're writing a general purpose screen display routine that could display any of several different "bitmaps") then you can turn taht #10 into a variable.

 

Or, you could include it all in your graphics data like:

 


  ldy GraphicsData ;Get number of blank lines at top

nextblankline1:

  sta WSYNC

  dey

  bne nextblankline1



  ldx #0;Index of graphics data



  ldy GraphicsData+1

nextdrawline:

  sta WSYNC

  lda GraphicsData1,x

  sta PF0

  lda GraphicsData2,x

  sta PF1

  lda GraphicsData3,x

  sta PF2

  nop

 ;Some NOPs in here, I'm not going to cycle count

  nop

  lda GraphicsData4,x

  sta PF0

  lda GraphicsData5,x

  sta PF1

  lda GraphicsData6,x

  sta PF2

  inx

  dey

  bne nextdrawline



  ldy GraphicsData+2 ;Get number of blank lines at bottom

nextblankline2:

  sta WSYNC

  dey

  bne nextblankline2



GraphicsData: .byte 20;10 blank lines at top

                     .byte 150;150 lines of graphics

                     .byte 22;22 blank lines at bottom

GraphicsData1: .byte ;150 bytes of data for left PF0

GraphicsData2: .byte ;150 bytes of data for left PF1

GraphicsData3: .byte ;150 bytes of data for left PF2

GraphicsData4: .byte ;150 bytes of data for right PF0

GraphicsData5: .byte ;150 bytes of data for right PF1

GraphicsData6: .byte ;150 bytes of data for right PF2

 

 

You can further abstract that by using indexed indirect addressing: lda (),y but that should wait until Andrew explains the more advanced addressing modes...

 

 

Chris...

Link to comment
Share on other sites

When I place the include command (for PF data) at the beginning of the asm file, right after the macro.h and vcs.h includes, dasm gives me errors. If I put it where the data tables would usually be (before the org $fffa) it works fine though. Why doesn't dasm allows this?

 

Here is a quote of the dasm results:

 

9 references to unknown symbols.

7 events requiring another assembler pass.

- Expression in mnemonic not resolved.

- Obscure reason - to be documented :)

 

--- Unresolved Symbol List

NO_ILLEGAL_OPCODES       0000 ????         (R )

PFData0A                 10000 ????         (R )

PFData0B                 100a7 ????         (R )

PFData1A                 10037 ????         (R )

PFData1B                 100df ????         (R )

PFData2A                 1006f ????         (R )

PFData2B                 10117 ????         (R )

--- 7 Unresolved Symbols

Link to comment
Share on other sites

When I place the include command (for PF data) at the beginning of the asm file, right after the macro.h and vcs.h includes, dasm gives me errors.

 

Yes, because the assembler tries put the included file right where you have the INCLUDE command which in this case is before all the rest of your code. And given it's probably before any ORG statements this is a real problem! (The assembler doesn't know where to put the PFData tables). An include file consisting entirely of label definitions and/or macros is the only include file you're going to put at the very top with VCS.H and MACROS.H. Labels and macros don't really care because they aren't "anchored" to any specific place in ROM. However any code or data must be put in a specific ROM location.

 

In other words,

 


  include File1

  include File2

  include File3

 

Will give you a completely different result than:

 


  include File3

  include File2

  include File1

 

Just like:


 lda #$10

 sta $80

Is completely different from:


 sta $80

 lda #$10

 

 

All the INCLUDE command does is drop the included file directly in that spot. If you put everything back into one file like it was originally, would it make a difference if you moved all those PFData tables to the very start of your code? That's what you did by putting the include up there.

 

 

If you have to, think of an include file as a super label. So just as:


  TMP EQU $80

  LDA TEMP

is really just:


  LDA $80

because the label TEMP gets replaced by the $80. So too your INCLUDE statement gets replaced, right there, with the contents of the included file.

 

 

Chris...

Link to comment
Share on other sites

Hi there!

 

Very interesting. I was thinking about it in C terms, where the includes are always declared at the start of your code, but this is obviously not the case. Thanks for the help! :)

 

Actually in C it works exactly the same. :)

 

Greetings,

Manuel

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

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