Saturday, November 16, 2019

Freedom of Speech

While the latest patch to WordPress seems to have fixed my issues on spelchan.ca, fighting with it is not something I want to spend time on so I am going to consider that a failed experiment and will instead post my longer-form posts here once a month with my Blazing Game Development blog linking here when appropriate.

This is a different post from the one I was originally going to post due to recent events in Canada. The fact that stating an opinion that could be construed as controversial can lead to a person being fired is terrifying but is something that just happened here in Canada. Recently, a popular but often controversial hockey commentator named Don Cherry had a speech about how people need to support our Veterans by buying poppies. Clearly this is a valid statement as walking down the street before Remembrance Day you could see lots of people without poppies. The problem was that he focused his statements at a particular group of people, immigrants. This made his statements “racist.”  This resulted in people who were offended by his statements insisting that he be fired, and he was.

The thing is, some of our social improvements, such as religious freedom, women’s rights, and the LGBTQ movement were all politically incorrect. Atheism is still somewhat politically incorrect, which is strange since it is impossible to have religious freedom without  the option to not believe. These improvement could never have come about without freedom of speech. If we lose freedom of speech, then the people who don’t think those things are improvements could take those freedoms away.

I do not believe that having a politically incorrect opinion should be grounds for firing someone.  Even if that opinion is offensive to some other people. Quite frankly, any opinion can be considered to be offensive by someone who holds the opposite opinion.  “But he’s a racist!” Maybe, but so are a lot of other people many of whom like to call other people racists. In fact, there are leader’s of countries that are clearly racist, and I am not just talking about Trump.  Apparently it is okay to be a racist and run a country but racists should not be allowed to comment on Hockey game strategy.

Freedom of speech means that everyone should be free to express their opinions without fear of repercussions. This means that some people are going to say things that you disagree with. But guess what? You also have freedom of speech which means that you can use your speech to explain why that person is wrong. Calling someone a “racist” does not explain why they are wrong! In fact, even though Don Cherry was fired over his opinion, nobody bothered to look and see if there was any validity to his statements, which to me is the bigger problem. Truth should be what we strive for, even if it offends people. But in today’s politically correct climate, truth is often the victim. Are immigrants less likely to support our veterans’? If so is it simply due to them being unfamiliar with our customs in which case explaining the significance of wearing a poppy to them would likely solve the problem.

If when someone says something that you strongly disagree with then dismissing them as a “racist”, “sexist”, or whatever-ist does not help the situation. If they have a valid point then that point is still valid no matter what type of human that person is. We should be focused on what is being said and whether it is valid, not on who is saying it. It is impossible to get to the root of a problem if we are forbidden from talking about the problem. In the long term, stifling opinions – even incorrect ones – is far more harmful to society than offending people.

Now with that off my chest, next month - assuming something doesn't happen to keep me on the sandbox - I will post the first half of the next chapter of my HTML5 Game Development using Animate eBook.

Saturday, October 12, 2019

I have concluded that having two separate blogs is just too big of a pain. I am also thinking that I am better off with longer posts to better cover whatever topic I am discussing. This means that I will not be posting the new blog every week but instead will be cutting down to a long post once a month. Chapters from my animate (and possibly create.js) eBook will be posted once every 3 to 4 months, with posts on other subjects in between the chapter posts. The in-between posts will be about Spelchan.com content, game-jam or home-brew content, and articles related to what I am researching with the odd miscellaneous soapbox post appearing related to whatever thing got me angry enough to write a post.

This leads to the next question as to where these posts will appear. While I have nothing against blogger, I would prefer that it was on a site that I control. For that reason I set up WordPress on my Spelchan.ca site so my blog posts will appear on Spelchan.ca while my games appear on Spelchan.com. For the next while, I will probably be posting TLDR summaries on this blog for posts.

The first post will be on Halloween Liche and will be posted next Saturday. I am really tempted to have my first soap-box post next week due to the election in Canada but it is probably best that I avoid politics in my post, unless it is computer related such as DRM or taxing automation.

Saturday, September 28, 2019

2600 Collisions

One of the nicer things about the 2600 is the hardware collision detection. Having written software sprite-based engines where collision detection had to be done with software this is a nice feature. As the TIA must know what to draw in the pixel, and there are only 6 things that can collide, the hardware simply needs to do a series of and operations with the information it has and store that information in the appropriate register. As each of the 6 thing that can collide can’t collide with themselves and are reversible (missile1 hitting player0 is the same as player0 hitting missile1) that means that there are only 15 possible collisions which I outline in the table below.

By assigning each collision with a bit we can put all the possible TIA collisions into a 15 bit word or split it into 2 bytes. This is the way a modern system would do this if the API designer didn’t add a hundred layers of abstraction to the system. For some reason, the TIA designers decided to have the collision bits divided into 8 registers as shown in table \ref{tbl:collisionregs}. This lets you use the BIT instruction which sets the Negative flag if bit seven is set so BPL/BMI can be used to check this bit as well as setting the overflow flag if bit 6 is set allowing for BVC/BVS branching.

Once a collision register has been set it stays set until the CXCLR is written to. As you generally want the collision for the whole frame this is not a big issue but something that needs to be kept in mind if you want to figure out where on the screen the collision occurred.

The TIA emulator handles collisions by tracking the flags as a single integer with bits set up as per the first table. This is a global collisonState variable which is cleared by CXCLR. Whenever a pixel is being written, we create a mask that will be logically ORed to the collision state. It assumes that all collisions are going to happen.

As we draw each sprite, we check to see if the element is drawn. If it is, the mask is unchanged, but if it is not then no collision with that component could have happened so we remove those bits using the collision mask as an indication of which bits should be ANDed out.

pixelColor = if (sprites[0].isPixelDrawn(column)) playfieldBallColor else {
pixelColor}
pixelColor = if (sprites[1].isPixelDrawn(column)) playerMissile1Color else {
pixelColor}
pixelColor = if (sprites[2].isPixelDrawn(column)) playerMissile1Color else {
pixelColor}
pixelColor = if (sprites[3].isPixelDrawn(column)) playerMissile0Color else {
pixelColor}
pixelColor = if (sprites[4].isPixelDrawn(column)) playerMissile0Color else {
pixelColor}

With that done, we now have all the information we need to read the collision registers so we implement the readRegister method to return the requested pair of collision bits as outlined in our second table.

val bit7 = if ((collisonState and bit7mask) == bit7mask) 128 else 0
val bit6 = if ((collisonState and bit6mask) == bit6mask) 64 else 0
return bit7 or bit6

TIAPIARegs.CXBLPF -> buildCollisionByte(TIAPIARegs.ICOL_PFBL,0)
TIAPIARegs.CXM0FB -> buildCollisionByte(TIAPIARegs.ICOL_PFM0, TIAPIARegs.ICOL_BLM0)
TIAPIARegs.CXM1FB -> buildCollisionByte(TIAPIARegs.ICOL_PFM1, TIAPIARegs.ICOL_BLM1)
TIAPIARegs.CXM0P -> buildCollisionByte(TIAPIARegs.ICOL_P0M0, TIAPIARegs.ICOL_M0P1)
TIAPIARegs.CXM1P -> buildCollisionByte(TIAPIARegs.ICOL_P0M1, TIAPIARegs.ICOL_P1M1)
TIAPIARegs.CXP0FB -> buildCollisionByte(TIAPIARegs.ICOL_PFP0, TIAPIARegs.ICOL_BLP0)
TIAPIARegs.CXP1FB -> buildCollisionByte(TIAPIARegs.ICOL_PFP1, TIAPIARegs.ICOL_BLP1)
TIAPIARegs.CXPPMM -> buildCollisionByte(TIAPIARegs.ICOL_P0P1, TIAPIARegs.ICOL_M0M1)

// Unknown or unimplemented registers print warning
else -> { println("TIA register address not implemented!"); 0} } } This means that we now have collisions. Next fortnight things are changing as I make up my mind about the future of this blog. Saturday, September 14, 2019 TIA Sprite Implementation Now that we have a sprite class for the internal processing of the TIA chip Player Missile Graphics (which I will refer to as sprites), we need to implement the TIA registers that handle the various sprite activity and adjust our sprites to reflect the register settings. First, we create the 5 sprites that the TIA uses. This is done in an array class variable with constants set up in the TIAPIARegs singleton class that hold the index values of each sprite. val sprites = arrayOf(PlayerMissileGraphic(1, 30570), // Ball PlayerMissileGraphic(8, 15423), // Player 1 PlayerMissileGraphic(1, 1023), // Missile 1 PlayerMissileGraphic(8, 28377), // Player 0 PlayerMissileGraphic(1, 24007) ) // Missile 0 The most important thing for a sprite is to be able to be shown. The ball and missiles have an enable register which when bit 1 is set will show the sprite otherwise will hide the sprite. For the player sprites, set the byte that represents the image to 0 for the sprite not to be drawn and fill it with the display data to have the sprite draw something. TIAPIARegs.ENABL -> sprites[TIAPIARegs.ISPRITE_BALL].drawingBits = if ((value and 2) == 2) 1 else 0 TIAPIARegs.ENAM0 -> sprites[TIAPIARegs.ISPRITE_MISSILE0].drawingBits = if ((value and 2) == 2) 1 else 0 TIAPIARegs.ENAM1 -> sprites[TIAPIARegs.ISPRITE_MISSILE1].drawingBits = if ((value and 2) == 2) 1 else 0 TIAPIARegs.GRP0 -> sprites[TIAPIARegs.ISPRITE_PLAYER0].drawingBits = value and 255 TIAPIARegs.GRP1 -> sprites[TIAPIARegs.ISPRITE_PLAYER1].drawingBits = value and 255 Being able to see the sprite is nice, but we need to control where on the scan-line the sprite will be displayed. It would be nice if we just had a register that held the position information of the sprites, but for some reason that is beyond me this is not how sprite positioning works on the TIA. To position a sprite you simply write anything into the RESXX register and it will position the sprite at the current color clock position or position 0 if called in the horizontal blanking interval. TIAPIARegs.RESBL -> sprites[TIAPIARegs.ISPRITE_BALL].x = column TIAPIARegs.RESP0 -> sprites[TIAPIARegs.ISPRITE_PLAYER0].x = column TIAPIARegs.RESP1 -> sprites[TIAPIARegs.ISPRITE_PLAYER1].x = column TIAPIARegs.RESM0 -> sprites[TIAPIARegs.ISPRITE_MISSILE0].x = column TIAPIARegs.RESM1 -> sprites[TIAPIARegs.ISPRITE_MISSILE1].x = column TIAPIARegs.RESMP0 -> sprites[TIAPIARegs.ISPRITE_MISSILE0].x = sprites[TIAPIARegs.ISPRITE_PLAYER0].x TIAPIARegs.RESMP1 -> sprites[TIAPIARegs.ISPRITE_MISSILE1].x = sprites[TIAPIARegs.ISPRITE_PLAYER1].x The problem with this is that each CPU cycle is 3 color clock cycles and a STA instruction takes at least 3 cycles to complete. This means that we need to adjust the horizontal position using the move delta. This is done at the start of the scanline by writing to HMOVE with any value. The amount each sprite moves is a signed nibble set by HMxx registers with a HMCLR register that sets all the HMxx registers to zero. We have a utility method to convert a nibble into a signed integer as follows private fun convertNibbleToSignedInt(nibble:Int):Int { val nib = nibble and 15 return if (nib > 7) nib - 16 else nib } And within the writeRegister when statement we have the following registers implemented: TIAPIARegs.HMP0 -> sprites[TIAPIARegs.ISPRITE_PLAYER0].deltaX = convertNibbleToSignedInt(value shr 4) TIAPIARegs.HMP1 -> sprites[TIAPIARegs.ISPRITE_PLAYER1].deltaX = convertNibbleToSignedInt(value shr 4) TIAPIARegs.HMM0 -> sprites[TIAPIARegs.ISPRITE_MISSILE0].deltaX = convertNibbleToSignedInt(value shr 4) TIAPIARegs.HMM1 -> sprites[TIAPIARegs.ISPRITE_MISSILE1].deltaX = convertNibbleToSignedInt(value shr 4) TIAPIARegs.HMBL -> sprites[TIAPIARegs.ISPRITE_BALL].deltaX = convertNibbleToSignedInt(value shr 4) TIAPIARegs.HMOVE -> { for (cntr in 0..4) sprites[cntr].hmove() } TIAPIARegs.HMCLR -> { for (cntr in 0..4) sprites[cntr].deltaX = 0 } Sprite scaling and mirroring is done through the NUSIZx register with the copy/scale mode of the player sprites being the lower 3 bits and the scaling of missile for that player being two to the power of the two bit number occupying bits 4 and 5. In other words, we take the last three bits to form a number between 0 and 7 which is used to set the player sprite scaling and copies via the setePlayerScaleCopy method that we wrote in the previous article. The missile scaling is bits 4 and 5 which when shifted right become a number between 0 and 3, this is used as a power of two which can be done by left-shifting a 1 by the given number which would result in 1, 2, 4, or 8. TIAPIARegs.NUSIZ0 -> { sprites[TIAPIARegs.ISPRITE_PLAYER0].setPlayerScaleCopy(value and 7) sprites[TIAPIARegs.ISPRITE_MISSILE0].scale = 1 shl ((value shr 4) and 3) } TIAPIARegs.NUSIZ1 -> { sprites[TIAPIARegs.ISPRITE_PLAYER1].setPlayerScaleCopy(value and 7) sprites[TIAPIARegs.ISPRITE_MISSILE1].scale = 1 shl ((value shr 4) and 3) } Finally, mirroring is simply setting the sprite mirror variable based on the state of bit 3 of the value passed to the REFx register. TIAPIARegs.REFP0 -> sprites[TIAPIARegs.ISPRITE_PLAYER0].mirror = (value and 8) == 8 TIAPIARegs.REFP1 -> sprites[TIAPIARegs.ISPRITE_PLAYER1].mirror = (value and 8) == 8 That just about covers all the write registers for manipulating sprites, but there are also a set of eight readable registers that are used for handling collision detection. This is a bit complex of a subject, though is a lot easier to use than one would expect and will be covered next time. Saturday, August 31, 2019 Sprites are Player Missile Graphics To plan out the classes that I would use for creating the player and missile sprites I put together the following table which shows the various registers used for the different player and missiles as well as how the different objects compare to each other. Table 1 is a handy chart to have for programming the 2600 and wish I would have created it when I first started playing around with the 2600! The original plan was to have a base class that held the common functionality and have the player class and missile/ball classes override this class. This is probably the “proper” way to do this but the classes are so similar that having them combined into a single class seemed like the easiest solution. A missile or ball is essentially a sprite that is only 1 pixel so by simply tracking the number of pixels as size, all the different player missile graphics can be incorporated. Sprites have a collision mask that will be used for determining which collision flags they are part of. This will be explained later when we implement collisions but for now it can be thought of as simply a magic number with each bit in the number representing a particular pair of objects that can collide with each other. Scale is how many pixels to draw for each pixel in the sprite. The 2600 supports scales of 1, 2, 4, and 8 with player sprites limited to a maximum scale of 4. The number of copies is how many times to draw the sprite while the gap is the distance (in multiples of the size) between the copies. The 2600 supports distances of 2, 4 and 8. The sprite is placed on the screen at the x location with the deltaX variable being how much to alter the x position when an HMOVE command is issued. Normally we do not want the sprite to move so the default is 0. The drawing bits are the byte used to represent the image in the sprite at that horizontal scanline. It can be considered a bit of a kludge when using missiles and balls as only the one bit is used. Mirroring is used to indicate which order the pixels should be drawn. For balls and missiles it will mirror the single pixel but as the result of that is the original image there is not much use for size 1 sprites. class PlayerMissileGraphic(var size:Int, var collisionMask:Int) { var scale = 1 var copies = 1 var distanceBetweenCopies = 0 // close = 2, med = 4 far = 8 var x = 0 var deltaX = 0 var drawingBits = 0 var delayedDraw = false var mirror = false The heart of the sprite class is the isPixelDrawn method which simply determines if the current state of the sprite would result in a pixel being drawn at the indicated location. This is simply a matter of looping through the number of copies of the sprite and for each copy being drawn figure out where it will be drawn. We know the number of pixels that a copy will occupy as it is simply the size times the scale so if the column being drawn is within that range we figure out which pixel in the image is at that location and if that bit is 1 then we are drawing otherwise we are not. Calculating which bit in the drawing data to draw depends on whether we are mirroring the sprite and the scale of the pixels. fun isPixelDrawn(col:Int):Boolean { if (col < x) return false if (delayedDraw) return false var pixelAtCol = false for (copyCount in 1..copies) { val copyStart = x + (copyCount - 1) * size * scale * distanceBetweenCopies val copyEnd = copyStart + size * scale if ((col >= copyStart) and (col < copyEnd)) { val bitPos = (col - copyStart) / scale val bit = if (mirror) 1 shl bitPos else (1 shl (size-1)) ushr bitPos if ((bit and drawingBits) > 0) pixelAtCol = true } } return pixelAtCol } The HMOVE command is processed simply by adjusting the x variable by the movement delta with a bit of clamping code to make sure it is within the range of the display. My clamping is done by nesting a max function within a min function by having the highest column be the minimum to compare against and 0 and the target column being the max to pick from. As a result the max will clip away negative values making them 0 while the min will clip away off-right-edge values making them the right edge. fun hmove() { x = min(159, max(0, x + deltaX)) } The TIA player sprites have 8 different modes as shown by the chart below. While this could be handled by the TIA register calls, there would be duplication of code so I wrote a simple utility method that will set the sprite scale, copies, and distance based on a player sprite mode. This method is just a simple when statement which sets the parameters appropriately based on the indicated mode. The different play modes that the TIA recognizes is in table 2. fun setPlayerScaleCopy(scmode:Int) { when (scmode) { TIAPIARegs.PMG_NORMAL -> {scale = 1; copies = 1; distanceBetweenCopies = 1} TIAPIARegs.PMG_TWO_CLOSE -> {scale = 1; copies = 2; distanceBetweenCopies = 2} TIAPIARegs.PMG_TWO_MEDIUM -> {scale = 1; copies = 2; distanceBetweenCopies = 4} TIAPIARegs.PMG_THREE_CLOSE -> {scale = 1; copies = 3; distanceBetweenCopies = 2} TIAPIARegs.PMG_TWO_WIDE -> {scale = 1; copies = 2; distanceBetweenCopies = 8} TIAPIARegs.PMG_DOUBLE_SIZE -> {scale = 2; copies = 1; distanceBetweenCopies = 1} TIAPIARegs.PMG_THREE_MEDIUM -> {scale = 1; copies = 3; distanceBetweenCopies = 4} TIAPIARegs.PMG_QUAD_PLAYER -> {scale = 4; copies = 1; distanceBetweenCopies = 1} } } We now have a class that can handle the sprites in the TIA but we still have to handle the TIA registers related to drawing the sprites and perform the drawing of the sprites when the color clock reaches them. This will be what we do next. Saturday, August 17, 2019 The 2600 Play-field The play-field is a 40-bit image that is stretched across the 160 pixels that make up a scan-line. This means that each play-field bit occupies 4 pixels. The original purpose behind the play-field is to create the map or maze that the player occupies. It is often used for other things such as drawing scores, background images or title screens. Anything that can be represented using crude graphics is fine. The play-field is managed using the CTRLPF, PF0, PF1 and PF2 registers. The CTRLPF register controls how the play-field will be displayed controlling the display order of the play-field, whether the bits should be repeated or mirrored, and if the play-field should be drawn using the play-field color or drawn using player 0 color on the left and player 1 color on the right. This register also handles scaling of the ball which will be covered later. The PF0, PF1 and PF2 registers hold the 20 bits that are to be drawn. These bits will either be repeated or mirrored based on the settings in CTRLPF. As with color, you can change the play-field bits in the middle of drawing so that the right side of the screen is different from the left side of the screen. The order of the bits is weird which is likely a by-product of keeping the transistor count of the chip down. The image below shows the order of the pixels that make up the display. Essentially the play-field drawing starts with the 4 bits in the PF0 register drawn in low to high order. This is followed by the eight bits from PF1 in high to low order. Finally the PF2 bits are drawn in low to high order. This is repeated or mirrored for the right half of the screen. As many games used symmetrical maps this made it easy to set up the play-field bits and simply mirror them. Asymmetric maps are a bit trickier as you need to set up the PF0, PF1, and PF2 registers for the left half of the screen then wait until the beam is past PF0 to change PF0 to the right side PF0, wait until the beam is past PF1 to change to the right side PF1 and wait until the beam is past PF2 to change to the right side PF2. While you could theoretically do this while mirroring, asymmetrical images are easier to do with repeated mode unless you don’t need to change PF2. The hard part about implementing the play-field is dealing with the different orientations of the data. My internal representation of the play-field data is simply a 20 bit integer with the bits being the play-field bits from left to right. When one of the PF registers is called, the appropriate bits of this play-field value are set. This requires that we flip the bits, but this is a simple process of using and to see if a bit is set or to build the reversed byte and shifts to get the bits aligned properly. fun reversePFBits(value:Int):Int { var reversed = 0 var testBit = 1 for (cntr in 0..7) { reversed *= 2 reversed += if ((testBit and value) > 0) 1 else 0 testBit *= 2 } return reversed } The calls to the playfield register simply take the value that is to be written to the register and, if necessary, reverse the bits. It then masks out the bits in the playfield that the register represents, shifts the bits to the appropriate part of the playfield then uses a logical or to put the new value in place. The playfield control register CTRLPF simply sets flags to indicate how the playfield should be rendered. The flags are mirror to indicate if mirroring should be taking place, scoreMode to determine if we are drawing using the playfield color or the player 0 color on the left and the player 1 color on the right. The priorityPlayfield flag determines if the playfield should be drawn first or last. TIAPIARegs.PF0 -> { var bits = reversePFBits(value) playfieldBits = (playfieldBits and 0xFFFF) or (bits shl(16)) } TIAPIARegs.PF1 -> { var bits = value playfieldBits = (playfieldBits and 0xF00FF) or (bits shl(8)) } TIAPIARegs.PF2 -> { var bits = reversePFBits(value) playfieldBits = (playfieldBits and 0xFFF00) or bits } TIAPIARegs.CTRLPF -> { mirror = (value and 1) == 1 // bit 0 handles mirror mode scoreMode = (value and 2) == 2 // bit 1 handles score mode priorityPlayfield = (value and 4) == 4 // bit 2 Places playfield above sprites } To draw the play-field we simply determine which play-field pixel should be processed. The left is simply the column being drawn divided by 4. The right is the same (less 80) if repeated and the inverse if mirrored. Once we know the bit to be checked we can determine if there is a pixel there and if so set the color based on the scoreMode. We then adjust our collision mask which will be explained in the collisions section later but is essentially a set of bits indicating which collisions have happened. val pfCol = if (column < 80) column / 4 else if (mirror) (159 - column) / 4 else (column - 80) / 4 var pfPixelMask = 0x80000 shr pfCol val shouldDrawPlayfield =(playfieldBits and pfPixelMask) > 0 val pfColor = if (scoreMode) {if (column < 80) playerMissile0Color else playerMissile1Color } else playfieldBallColor if ( ! shouldDrawPlayfield) collisionMask = collisionMask and 31668 At the start of the drawing of the pixel, we check to see if the playfield is normal priority and if so draw it. if ((shouldDrawPlayfield) and ( ! priorityPlayfield )) pixelColor = pfColor // TODO sprite drawing code here if ((shouldDrawPlayfield) and (priorityPlayfield)) pixelColor = pfColor Finally, at the end of the drawing of a pixel, we see if the playfield is priority and if so draw it. Between these two checks is the sprite drawing which is the next thing to be implemented. Saturday, August 3, 2019 Drawing Pixels on 2600 TIA In the 2600, the TIA is drawing the display in real time by sending signals to the television. There was no frame buffer in the TIA. With an emulator, our situation is slightly different. As I want the TIA emulator to work in several different platforms, a cross-platform way of generating the display is needed. My solution is to simply have a 160 “pixel” raster line that the TIA emulator writes to. Whenever a scan-line is finished, or part way through drawing the scan-line if you are doing some type of stepping through the program, the class managing the display will grab this information and use the TIAColors information to build a display appropriate to the platform that is being rendered. The figure below shows the logic that the TIA uses to determine which of the four colors to place on a pixel. We start with the background color. This would be the background color at the time that the pixel was drawn. As the color registers can be changed in the middle of rendering a line this can be a distinct color at different points along the scan-line. The order that things are drawn depends on flags in the CTRLPF register. Normally the play-field is drawn right after the background but if bit 1 of the CTRLPF is set then the play-field and ball will be drawn after the players and missiles. Player 1 and the missile associated with that player is drawn before player 0. So if CTRLPF bit 1 is set the drawing order is background, player and missile 1, player and missile 0, play-field and ball. Otherwise the drawing order is background, play-field and ball , player and missile 1, player and missile 0. This drawing order determines what the final pixel color will be in cases where multiple display objects overlap a given pixel. The 2600 goes even further by tracking the overlaps and setting a series of collision flags that can be tested to see if there were any collision between display objects when the line was being drawn. This effectively means that we have six things to potentially draw in any given pixel. Three of these -- the ball, missile 0, and missile 1 – are simply a line so they can be grouped into a common class. The two player sprites are not that much more complicated so while they could be a separate class from the ball and missile, making a more complicated player missile graphics class to handle all five of these display objects makes sense. This leaves us with the play-field. This is my favorite part of the 2600 rasterizer as it is the closest thing you have to traditional bitmap graphics. Saturday, July 20, 2019 Color Me TIA One of the big, if not the biggest, challenge of programming the 2600 was simply getting the scan-lines to be rendered properly. You are drawing the display as it is being shown and only have 76 cycles per scan line to set up the registers. As with many graphics processors, the TIA can be thought of as a state machine with the values contained in the registers at the end of a scan line being retained at the start of the next scan-line. This fact must be considered when writing your renderer, known as a kernel in 2600 parlance. A well-designed kernel will take advantage of this fact while graphical artifacts will result from a poorly designed kernel. Quick math shows that 160 pixels with only 76 cycles means that more than a single pixel is being drawn per CPU cycle. The TIA has its own clock which runs three times the speed of the CPU. This means that there are 228 TIA cycles per scan-line. To distinguish TIA cycles from CPU cycles, the TIA cycles have been given the name color clocks. The first 68 of these color clock ticks are used for horizontal blanking so do not draw anything to the screen but does give the kernel time to set up some of the TIA registers. The remaining 160 color clocks draw the pixels of the display based on the register settings at the time a pixel is being drawn. Pixels can be one of four colors based on what is being drawn in that location. The background color is what is used if there is nothing to draw in that location. The play-field color is the color of the play-field and the ball. Finally each player and their missile have a color. The colors are selected from a palette of 128 colors by simply setting the appropriate color register to the desired color. The color byte is in the format HHHHLLLX with the 4 H bits being the Hue, the L bits being the Luminosity, and X being unused. The hue is the base color of the pixel while the luminosity is the brightness of the pixel. While the color could be calculated, with only 128 colors it is much quicker just to have a table of colors. class TIAColors() { val colorTable:Array<Int> = arrayOf( // Hue 0 0xFF000000.toInt(),0xFF000000.toInt(),0xFF404040.toInt(), 0xFF404040.toInt(),0xFF6c6c6c.toInt(), 0xFF6c6c6c.toInt(),0xFF909090.toInt(), 0xFF909090.toInt(), 0xFFb0b0b0.toInt(),0xFFb0b0b0.toInt(),0xFFc8c8c8.toInt(),0xFFc8c8c8.toInt(), 0xFFdcdcdc.toInt(),0xFFdcdcdc.toInt(),0xFFececec.toInt(),0xFFececec.toInt(), // Hue 1 0xFF444400.toInt(),0xFF444400.toInt(),0xFF646410.toInt(),0xFF646410.toInt(), 0xFF848424.toInt(),0xFF848424.toInt(),0xFFa0a034.toInt(),0xFFa0a034.toInt(), 0xFFb8b840.toInt(),0xFFb8b840.toInt(),0xFFd0d050.toInt(),0xFFd0d050.toInt(), 0xFFe8e85c.toInt(),0xFFe8e85c.toInt(),0xFFfcfc68.toInt(),0xFFfcfc68.toInt(), //Hue 2 0xFF702800.toInt(),0xFF702800.toInt(),0xFF844414.toInt(),0xFF844414.toInt(), 0xFF985c28.toInt(),0xFF985c28.toInt(),0xFFac783c.toInt(),0xFFac783c.toInt(), 0xFFbc8c4c.toInt(),0xFFbc8c4c.toInt(),0xFFcca05c.toInt(),0xFFcca05c.toInt(), 0xFFdcb468.toInt(),0xFFdcb468.toInt(),0xFFecc878.toInt(),0xFFecc878.toInt(), // Hue 3 0xFF841800.toInt(),0xFF841800.toInt(),0xFF983418.toInt(),0xFF983418.toInt(), 0xFFac5030.toInt(),0xFFac5030.toInt(),0xFFc06848.toInt(),0xFFc06848.toInt(), 0xFFd0805c.toInt(),0xFFd0805c.toInt(),0xFFe09470.toInt(),0xFFe09470.toInt(), 0xFFeca880.toInt(),0xFFeca880.toInt(),0xFFfcbc94.toInt(),0xFFfcbc94.toInt(), // Hue 4 0xFF880000.toInt(),0xFF880000.toInt(),0xFF9c2020.toInt(),0xFF9c2020.toInt(), 0xFFb03c3c.toInt(),0xFFb03c3c.toInt(),0xFFc05858.toInt(),0xFFc05858.toInt(), 0xFFd07070.toInt(),0xFFd07070.toInt(),0xFFe08888.toInt(),0xFFe08888.toInt(), 0xFFeca0a0.toInt(),0xFFeca0a0.toInt(),0xFFfcb4b4.toInt(),0xFFfcb4b4.toInt(), // Hue 5 0xFF78005c.toInt(),0xFF78005c.toInt(),0xFF8c2074.toInt(),0xFF8c2074.toInt(), 0xFFa03c88.toInt(),0xFFa03c88.toInt(),0xFFb0589c.toInt(),0xFFb0589c.toInt(), 0xFFc070b0.toInt(),0xFFc070b0.toInt(),0xFFd084c0.toInt(),0xFFd084c0.toInt(), 0xFFdc9cd0.toInt(),0xFFdc9cd0.toInt(),0xFFecb0e0.toInt(),0xFFecb0e0.toInt(), // Hue 6 0xFF480078.toInt(),0xFF480078.toInt(),0xFF602090.toInt(),0xFF602090.toInt(), 0xFF783ca4.toInt(),0xFF783ca4.toInt(),0xFF8c58b8.toInt(),0xFF8c58b8.toInt(), 0xFFa070cc.toInt(),0xFFa070cc.toInt(),0xFFb484dc.toInt(),0xFFb484dc.toInt(), 0xFFc49cec.toInt(),0xFFc49cec.toInt(),0xFFd4b0fc.toInt(),0xFFd4b0fc.toInt(), // Hue 7 0xFF140084.toInt(),0xFF140084.toInt(),0xFF302098.toInt(),0xFF302098.toInt(), 0xFF4c3cac.toInt(),0xFF4c3cac.toInt(),0xFF6858c0.toInt(),0xFF6858c0.toInt(), 0xFF7c70d0.toInt(),0xFF7c70d0.toInt(),0xFF9488e0.toInt(),0xFF9488e0.toInt(), 0xFFa8a0ec.toInt(),0xFFa8a0ec.toInt(),0xFFbcb4fc.toInt(),0xFFbcb4fc.toInt(), // Hue 8 0xFF000088.toInt(),0xFF000088.toInt(),0xFF1c209c.toInt(),0xFF1c209c.toInt(), 0xFF3840b0.toInt(),0xFF3840b0.toInt(),0xFF505cc0.toInt(),0xFF505cc0.toInt(), 0xFF6874d0.toInt(),0xFF6874d0.toInt(),0xFF7c8ce0.toInt(),0xFF7c8ce0.toInt(), 0xFF90a4ec.toInt(),0xFF90a4ec.toInt(),0xFFa4b8fc.toInt(),0xFFa4b8fc.toInt(), // Hue 9 0xFF00187c.toInt(),0xFF00187c.toInt(),0xFF1c3890.toInt(),0xFF1c3890.toInt(), 0xFF3854a8.toInt(),0xFF3854a8.toInt(),0xFF5070bc.toInt(),0xFF5070bc.toInt(), 0xFF6888cc.toInt(),0xFF6888cc.toInt(),0xFF7c9cdc.toInt(),0xFF7c9cdc.toInt(), 0xFF90b4ec.toInt(),0xFF90b4ec.toInt(),0xFFa4c8fc.toInt(),0xFFa4c8fc.toInt(), // Hue 10 0xFF002c5c.toInt(),0xFF002c5c.toInt(),0xFF1c4c78.toInt(),0xFF1c4c78.toInt(), 0xFF386890.toInt(),0xFF386890.toInt(),0xFF5084ac.toInt(),0xFF5084ac.toInt(), 0xFF689cc0.toInt(),0xFF689cc0.toInt(),0xFF7cb4d4.toInt(),0xFF7cb4d4.toInt(), 0xFF90cce8.toInt(),0xFF90cce8.toInt(),0xFFa4e0fc.toInt(),0xFFa4e0fc.toInt(), // Hue 11 0xFF003c2c.toInt(),0xFF003c2c.toInt(),0xFF1c5c48.toInt(),0xFF1c5c48.toInt(), 0xFF387c64.toInt(),0xFF387c64.toInt(),0xFF509c80.toInt(),0xFF509c80.toInt(), 0xFF68b494.toInt(),0xFF68b494.toInt(),0xFF7cd0ac.toInt(),0xFF7cd0ac.toInt(), 0xFF90e4c0.toInt(),0xFF90e4c0.toInt(),0xFFa4fcd4.toInt(),0xFFa4fcd4.toInt(), // Hue 12 0xFF003c00.toInt(),0xFF003c00.toInt(),0xFF205c20.toInt(),0xFF205c20.toInt(), 0xFF407c40.toInt(),0xFF407c40.toInt(),0xFF5c9c5c.toInt(),0xFF5c9c5c.toInt(), 0xFF74b474.toInt(),0xFF74b474.toInt(),0xFF8cd08c.toInt(),0xFF8cd08c.toInt(), 0xFFa4e4a4.toInt(),0xFFa4e4a4.toInt(),0xFFb8fcb8.toInt(),0xFFb8fcb8.toInt(), // Hue 13 0xFF143800.toInt(),0xFF143800.toInt(),0xFF345c1c.toInt(),0xFF345c1c.toInt(), 0xFF507c38.toInt(),0xFF507c38.toInt(),0xFF6c9850.toInt(),0xFF6c9850.toInt(), 0xFF84b468.toInt(),0xFF84b468.toInt(),0xFF9ccc7c.toInt(),0xFF9ccc7c.toInt(), 0xFFb4e490.toInt(),0xFFb4e490.toInt(),0xFFc8fca4.toInt(),0xFFc8fca4.toInt(), // Hue 14 0xFF2c3000.toInt(),0xFF2c3000.toInt(),0xFF4c501c.toInt(),0xFF4c501c.toInt(), 0xFF687034.toInt(),0xFF687034.toInt(),0xFF848c4c.toInt(),0xFF848c4c.toInt(), 0xFF9ca864.toInt(),0xFF9ca864.toInt(),0xFFb4c078.toInt(),0xFFb4c078.toInt(), 0xFFccd488.toInt(),0xFFccd488.toInt(),0xFFe0ec9c.toInt(),0xFFe0ec9c.toInt(), // Hue 15 0xFF442800.toInt(),0xFF442800.toInt(),0xFF644818.toInt(),0xFF644818.toInt(), 0xFF846830.toInt(),0xFF846830.toInt(),0xFFa08444.toInt(),0xFFa08444.toInt(), 0xFFb89c58.toInt(),0xFFb89c58.toInt(),0xFFd0b46c.toInt(),0xFFd0b46c.toInt(), 0xFFe8cc7c.toInt(),0xFFe8cc7c.toInt(),0xFFfce08c.toInt(),0xFFfce08c.toInt() ) fun getARGB(indx:Int):Int { if ((indx < 0) or (indx > 255)) return 0 else return colorTable[indx] } fun getHTMLColor(indx:Int):String { val noAlphaColor = getARGB(indx) and 0xFFFFFF return "#{noAlphaColor.toString(16)}"
}
}

To aid in the selection of color values, my TIA testing program has a palette picker as seen in figure below.

This leaves the color clock. A simple function for handling a clock tick is simple enough to implement. I simply determine the pixel being rendered by subtracting the horizontal sync time from the color clock tick. If the column is negative, we don’t do anything otherwise we process the pixel at that column. The processing of the pixel will be implemented over the next several articles. Once the pixel is processed, we increment the color clock. If the color clock has reached it’s end we reset the color clock to zero and return true to indicate to the caller that the scanline is finished.

fun nextClockTick():Boolean {
// run current pixel
val column = colorClock - 68
if (column >= 0) {
var pixelColor = backgroundColor

// TODO render playfield
// TODO render player-missile graphics and set collisions

rasterLine[column] = pixelColor
}
++colorClock
return if (colorClock >= 228) {
colorClock = 0
true
} else false
}

Setting the colors is very simple as you simply set the appropriate color register to the desired value. These registers are COLUBK for the background color, COLUPF for the play-field and ball color, COLUP0 for player 0 and missile 0, and COLUP1 for player 1 and missile 1.

TIAPIARegs.COLUBK -> backgroundColor = value
TIAPIARegs.COLUPF -> playfieldBallColor = value
TIAPIARegs.COLUP0 -> playerMissile0Color = value
TIAPIARegs.COLUP1 -> playerMissile1Color = value
else -> println("TIA register \$address not implemented!")
}
}

Sunday, July 14, 2019

Accessing the TIA

This was suppose to be posted last Saturday but I got messed up and posted an entry to my Blazing Games Development Blog instead.  Then I had an anniversary party to attend last night and forgot to post this before leaving. To get things back on the proper schedule, I will post the next installment next week instead of next fortnight. It should be noted that once the TIA rendering material is posted I may be slowing down to monthly as I really don't have much time for work on my emulation project.

We are at the point in the project where we want to display graphics on the screen. We also want to write code so that it will work on the JVM as well as on the browser and possibly native code. The Kotlin language supports exactly this by using the “expect” and “actually” keywords. This works by letting you set up classes and functions with the expect keyword that will be implemented in a platform dependent way. Each platform that you are supporting would then have an implementation of the classes and functions using the actual keyword. This allows you to have generic code be able to call platform specific code and makes it easy to add new platforms by simply writing the expect classes and functions for that platform.

Unfortunately, I started using the IntelliJ IDE for my Kotlin development and set up the project before they added support for multiplatform projects. While I probably could create a new project as a multi-platform project and then try to pull my repository into that project, I suspect that would lead to a huge mess which would waste a lot of my time. Instead my plan going forward is to just do a JavaFX version and later port to JavaScript once I have everything working.

One thing that happens when you are working on a project is that your initial ideas about how things will work will often have missing or incorrect assumptions. This is one reason why the waterfall method of development fails so often. As you work on a project you gain a better understanding of the problems involved. One assumption that I had was that the TIA chip could simply read the memory to perform its rendering. Unfortunately, after reviewing the TIA documentation, it seems that several registers work by being written to, even if the value written is the same value in memory.

To solve this issue, I am going to have to rework the way memory is handled. What I am planning on doing is having both the A2600 class and the cartridge be memory managers with the A2600 class being the one that the m6502 instance uses. That way when memory is written or read from within the TIA register range, the TIA gets called so it can perform register operations.

Internally, I could represent the registers with a block of memory, but we still need to track trigger writes. As we are having methods to handle reading and writing it is probably easier to have a different internal representation to allow for easier rendering by having classes for display objects which will greatly simplify the emulation.

Having raw numbers is not useful, so I created an enumeration of the registers and their address. The problem here is that when I am calling the functions, the register is specified as an address so to use the enumeration you need to access the address component of the enumeration. A better approach would be to have static class constants. The problem is that Kotlin doesn’t support them. Companion objects could be used to perform a similar function but they are very inefficient.

This got me thinking that as the enum is just a class, why not use a singleton class in it’s place. Kotlin has a built-in singleton class type which is declared using object instead of class as there is only one instance.  This means that any const val declared in the object will be static constants. So I set up a TIAPIARegs object that holds all the TIA as well as the PIA registers. I have not yet decided if I will combine the TIA and PIA emulators together or create a separate class. I am using the constants from the Stella documentation which I found on the internet archives (\url{https://archive.org/details/StellaProgrammersGuide}).

@Suppress("unused")
object TIAPIARegs {
const val VSYNC  = 0x00     // vertical sync - set to 2 to start sync and to 0 to stop
const val VBLANK = 0x01     // vertical blank
const val WSYNC  = 0x02     // wait for sync
const val RSYNC  = 0x03     // Reset sync - for testing
const val NUSIZ0 = 0x04     // number-size player-missile 0  -> xxMMxPPP =Missile size, Player size/copies)
const val NUSIZ1 = 0x05     // number-size player-missile 1  -> xxMMxPPP =Missile size, Player size/copies)
const val COLUP0 = 0x06     // color-luminosity player 0
const val COLUP1 = 0x07     // color-luminosity player 2
const val COLUPF = 0x08     // color-luminosity playfield
const val COLUBK = 0x09     // color-luminosity background
const val CTRLPF = 0x0A     // control playfield ball size & collisions
const val REFP0  = 0x0B     // reflect player 0
const val REFP1  = 0x0C     // reflect player 1
const val PF0    = 0x0D     // Playfield register 0 =upper nibble display order 4..7)
const val PF1    = 0x0E     // Playfield register 1 =display order 7..0)
const val PF2    = 0x0F     // Playfield register 2 =display order 0..7)
const val RESP0  = 0x10     // reset player 0
const val RESP1  = 0x11     // reset player 1
const val RESM0  = 0x12     // reset missile 0
const val RESM1  = 0x13     // reset missile 1
const val RESBL  = 0x14     // reset Ball
const val AUDC0  = 0x15     // audio control 0
const val AUDC1  = 0x16     // audio control 1
const val AUDF0  = 0x17     // audio frequency 0
const val AUDF1  = 0x18     // audio frequency 1
const val AUDV0  = 0x19     // audio volume 0
const val AUDV1  = 0x1A     // audio volume 1
const val GRP0   = 0x1B     // graphics player 0 =bit pattern)
const val GRP1   = 0x1C     // graphics player 1
const val ENAM0  = 0x1D     // graphics =enable) missile 0
const val ENAM1  = 0x1E     // graphics =enable) missile 1
const val ENABL  = 0x1F     // graphics =enable) ball
const val HMP0   = 0x20     // horizontal motion player 0
const val HMP1   = 0x21     // horizontal motion player 1
const val HMM0   = 0x22     // horizontal motion missile 0
const val HMM1   = 0x23     // horizontal motion missile 1
const val HMBL   = 0x24     // horizontal motion ball
const val VDELP0 = 0x25     // vertical delay player 0
const val VDELP1 = 0x26     // vertical delay player 1
const val VDELBL = 0x27     // vertical delay ball
const val RESMP0 = 0x28     // Reset missile 0 to player 0
const val RESMP1 = 0x29     // Reset missile 1 to player 1
const val HMOVE  = 0x2A     // Apply horizontal motion
const val HMCLR  = 0x2B     // clear horizontal motion registers
const val CXCLR  = 0x2C     // clear collision latches

const val CXM0P  = 0x30     // read collision =bit 7) MO with P1 =Bit 6) M0 with P0
const val CXM1P  = 0x31     // read collision =bit 7) M1 with P0 =Bit 6) M1 with P1
const val CXP0FB = 0x32     // read collision =bit 7) PO with PF =Bit 6) P0 with BL
const val CXP1FB = 0x33     // read collision =bit 7) P1 with PF =Bit 6) P1 with BL
const val CXM0FB = 0x34     // read collision =bit 7) MO with PF =Bit 6) M0 with BL
const val CXM1FB = 0x35     // read collision =bit 7) M1 with PF =Bit 6) M1 with BL
const val CXBLPF = 0x36     // read collision =bit 7) BL with PF =Bit 6) unused
const val CXPPMM = 0x37     // read collision =bit 7) P0 with P1 =Bit 6) M0 with M1
const val INPT0  = 0x38     // Dumped =Paddle) input port 0
const val INPT1  = 0x39     // Dumped =Paddle) input port 1
const val INPT2  = 0x3A     // Dumped =Paddle) input port 2
const val INPT3  = 0x3B     // Dumped =Paddle) input port 3
const val INPT4  = 0x3C     // Latched =Joystick) input port 4
const val INPT5  = 0x3D     // Latched =Joystick) input port 5

const val SWCHA  = 0x280
const val SWACNT = 0x281
const val SWCHB  = 0x282
const val SWBCNT = 0x283
const val INTIM  = 0x284
const val TIMINT = 0x285

const val TIM1T  = 0x294
const val TIM8T  = 0x295
const val TIM64T = 0x296
const val T1024T = 0x297

const val TIM1I  = 0x29c
const val TIM8I  = 0x29d
const val TIM64I = 0x29e
const val T1024I = 0x29f

// Player sprite scale/copy modes
const val PMG_NORMAL = 0
const val PMG_TWO_CLOSE = 1
const val PMG_TWO_MEDIUM = 2
const val PMG_THREE_CLOSE = 3
const val PMG_TWO_WIDE = 4
const val PMG_DOUBLE_SIZE = 5
const val PMG_THREE_MEDIUM = 6

// Internal collision bits
const val ICOL_PFBL = 1
const val ICOL_PFP0 = 2
const val ICOL_PFM0 = 8
const val ICOL_PFP1 = 64
const val ICOL_PFM1 = 1024
const val ICOL_BLP0 = 4
const val ICOL_BLM0 = 16
const val ICOL_BLP1 = 128
const val ICOL_BLM1 = 2048
const val ICOL_P0M0 = 32
const val ICOL_P0P1 = 256
const val ICOL_P0M1 = 4096
const val ICOL_M0P1 = 512
const val ICOL_M0M1 = 8192
const val ICOL_P1M1 = 16384

// internal sprite indexes
const val ISPRITE_BALL = 0
const val ISPRITE_PLAYER1 = 1
const val ISPRITE_MISSILE1 = 2
const val ISPRITE_PLAYER0 = 3
const val ISPRITE_MISSILE0 = 4
}

The registers are assigned 5 to 6 character labels which are condensed descriptions of what the register does. After working with the 2600, the labels do start to make sense. The reason for the condensed names was likely due to limitations of the assemblers of the time. It was not unheard of for labels to be very limited in length, with eight characters being a common limit.

We will be covering the registers in large groups as the respective functionality of the TIA is implemented. Our first task, however, is to get the color clock working.