Jump to content
IGNORED

understanding fine motion from the book


SavedByZero

Recommended Posts

Hey all,

I'm having more trouble understanding "Making Games for Atari 2600".  This time, chapter 9, page 53, the section below, for anyone who knows it:

A now contains (the remainder - 15). ; We’ll convert that into a fine adjustment, which has ; the range -7 to +8. eor #7; this calculates (23-A)% 16 asl asl asl; HMOVE only uses the top 4 bits, asl; so shift left by 4 sta HMP0; set fine position That tricky calculation with EOR and ASL converts the remainder into a value appropriate for the horizontal motion register:

Hugg, Steven. Making Games For The Atari 2600 (The 8bitworkshop Series Book 1) (Page 53). Puzzling Plans LLC. Kindle Edition. 

First off, the comment "A now contains (the remainder - 15)" sounds nonsensical.   A contains the remainder, period, right?  Start with 70, keep subtracting in that loop by 15 each time, the loop breaks when the carry flag is set and you go below zero.  The loop would go 70, 55, 40, 25, 10, -5, and stop there, right?  Thus, wouldn't the remainder be -5?  Is this some errata that was never reported on the main page?

second, I have no idea how "eor #7"  (which calls an XOR operation between -5 -- the remainder and value inside the A register -- and -7....right?)

translates to (23-A)%16.  Where did the numbers 23 and 16 come from?

 

Sorry for my density.  Thanks.

 

 

Link to comment
Share on other sites

Good questions; for me it helped to write out the list of possible remainders (and their binary representation) to fully understand this.

I also own a copy of "Making Games for Atari 2600", so I pasted the code below:

; A contains the X coordinate.
; First divide A by 15, as de DivideLoop takes 5 CPU cycles for each iteration
; 5 CPU cycles = 15 color clock cycles

    sec             ; set carry
DivideLoop
    sbc #15         ; substract 15
    bcs DivideLoop  ; branch while Carry still ste


; A now contains (the remainder - 15). 
; We’ll convert that into a fine adjustment, which has 
; the range -7 to +8. 

    eor #7         ; this calculates (23-A) % 16 
    asl 
    asl 
    asl            ; HMOVE only uses the top 4 bits, 
    asl            ; so shift left by 4 
    sta HMP0       ; set fine position 
  
; That tricky calculation with EOR and ASL converts the remainder into a value 
; appropriate for the horizontal motion register.
;
; Hugg, Steven. Making Games For The Atari 2600 
; (The 8bitworkshop Series Book 1) (Page 53). Puzzling Plans LLC. Kindle Edition. 
  • So right after the DivideLoop code, A contains the mathematical remainder minus 15; you could write that as (the remainder - 15) I guess.
    E.g. when you start with 30 as X coordinate, the mathematical remainder would be 0, but A would contain -15.
    And when you start with 44 as X coordinate, the mathematical remainder would be 14, but A would contain -1.
    So, the remainder in A can go from -1 (%11111111) to -15 (%11110001)
     
  • I guess you could say that 'eor #7' is calculating (16 + 7 - A) % 16, but you would have to clear the top 4 bits of the result to make this statement true, so it's confusing.
    Anyway, by doing that 'eor #7' (or eor %00000111) and then shifting left 4 bits, this basically maps the remainder in A to values in the range $60, $50, $40, $30, $20, $10, $00, $F0, $E0, $D0, $C0, $B0, $A0, $90 and $80. When put in the HMP0 motion register, this will result in a vertical positioning of -6 to +8 "pixels".

    remainder in A: -15 | #11110001 -> #01100000 | $60 -> -6 pixels
    remainder in A: -14 | #11110010 -> #01010000 | $50 -> -5 pixels
    remainder in A: -13 | #11110011 -> #01000000 | $40 -> -5 pixels
    ...
    remainder in A: -10 | #11110110 -> #00010000 | $10 -> -1 pixels
    remainder in A:   -9 | #11110111 -> #00000000 | $00 -> +0 pixels
    remainder in A:   -8 | #11111000 -> #11110000 | $F0 -> +1 pixels
    ...
    remainder in A:   -2 | #11111110 -> #10010000 | $90 -> +7 pixels
    remainder in A:   -1 | #11111111 -> #10000000 | $80 -> +8 pixels

    So the value $70 (= -7 pixels) is actually never being mapped to, but this is no problem as using the range of -6 to +8 pixels will cover what we need to do fine positioning.

 

Hope this helps!

Edited by Dionoid
Link to comment
Share on other sites

First, I don't know the book – and, admittedly, the text seems to be a bit terse. So...

 

1) "the remainder - 15", read AC (I personally prefer "AC" for the accumulator to avoid semantic ambiguity) contains "the remainder minus 15".

E.g., in your example, the remainder (70 % 15) is 10, with negative 5 remaining in AC, which is 10 - 15 = -5

 

So, what we're getting is the expected result, -5 as represented in two's complement (binary 11111011), which is equivalent to 5 ^ $ff +1.

 

2) "eor #7" effectively flips the lowest (least significant) 3 bits.

so we get %11111100 from %11111011 (251 = -5)

 

%11111011

eor #7 => %11111100
asl => %11111000
asl => %11110000
asl => %11100000

 

by applying asl 3 times, we get %11100000. However, we're interested only in the higher nibble, which is %1110.

%1110 happens to be -2 in two's complement.

Adding another asl ("so shift left by 4") gives %1100 or -4 (a fine adjustment of 4 color clocks to the right).

Mind that (23 - 251) % 16 = -4.

 

Edit:

Mind that what is true for the higher nibble, must have been also true for the lower nibble before the 4 ASLs.

Mind also that the fine adjustment actually effects a delay of up to 15 color clocks as expressed in -8 ... 0 ... +7, with positive delays shifting a sprite to the left and negative delays shifting it to the right. A negative delay meaning that the rest of the display effectively overtakes the sprite and is drawn first and vice versa a positive delay has to delay the display in order to draw the sprite first. Since the TIA doesn't do effective time travel, it has to adjust for this, and we have to add another 8 color clocks (compare the infamous HMOVE combs) to get positive delays only [or negatives only, depending on from which side you look at it], resulting in the base of 23. At least, this is my interpretation of it. As @Dionoid already mentioned, (23 - AC) % 16 actually maps/hashes the remainder to this value domain.

 

 

Edited by NoLand
Link to comment
Share on other sites

Thanks all.  The remainder thing seems much more obvious now, it was the book phrasing that I think confused me.  I thought there was an agreement that the word "remainder" in this context meant whatever was left in A (-5), so I was scratching my head wondering why we needed to subtract 15 from it again.

 

Two questions about the final step, though -- aren't $70, $60, etc. addresses and not values?  And how do we know what those addresses map to -- how do they end up mapping to 7, 6, and so forth other than that chart in the book telling us they do?  Sorry if I'm missing something obvious, the way I approach things is more like an interpreter than a compiler (I digest a little at a time, then stop right there if I have a problem figuring it out, until I get it). 

Edited by SavedByZero
Link to comment
Share on other sites

4 hours ago, SavedByZero said:

aren't $70, $60, etc. addresses and not values

"$" is just an indicator that the number is hexadecimal ($70 = 112 = %0111 0000), but not for its use.

In the particular case the only address is the HMP0 register of the TIA. (If you have a look at "vcs.h" for DASM, you'll see that this happens to be $20.)

 

How to know about the TIA

Generally, in terms of mental sanity, it may be better not to question any of the TIA related procedures too deeply. E.g., internally, the TIA uses linear feedback shift registers (LFSRs) to count pixels or color clocks in a nonlinear fashion in order to save a few transistors. Meaning, while it is perfectly able to compare values which occur in a deterministic sequence for identity, it doesn't "know" anything about offsets, etc., nor may we communicate with it in a sensible manner. Which is, why we may just say "now" in order to communicate a position and the TIA will latch to the current value in its counters. Similarly, the arrangement of the bits for the playfield pixels is rather odd, again to save a few components. (The linear offset values in HMP0 and HMP1 are quite an unexpected luxury in comparison.) Generally, there are just very few registers in the TIA, which will take all 8 bits of an entire byte (PF1, PF2, GRP0, GRP1 – that's it, have a look at "vcs.h"). Again, this is implementing just the bare minimum. It's an exercise in simplicity and cost reduction.

The idea was that a few engineers will establish ways to deal with this early on and that this was much cheaper than implementing luxurious components in each of the units shipped. So, why bother with ease of handling for programmers? Why implement the entire counter logic twice for an additional vertical axis, when a programmer can do the job as well? This is an extremely cost optimized consumer product! – Do not expect too much of an obvious logic, apart from the one on a transistor level design optimized for part count. However, dealing with this is a major part of the fun.

 

If you are generally interested in what the TIA is all about, you may want to have a look at https://www.masswerk.at/rc2017/04/02.html#basics

This is a cursory description of the logic in Computer Space, the very first coin-op video game by Nolan Bushnell and Ted Dabney, which came before Atari. The TIA is pretty much about the same, but in color and implementing just the horizontal counter chains – and it takes some nifty shortcuts, which are responsible for its oddities.

Edited by NoLand
Link to comment
Share on other sites

6 hours ago, Dionoid said:

By the way, if you like books on 2600 assembly game development, Atari 2600 Programming for Newbies by @Andrew Davie is highly recommended!

This is, of course, where I started as well (when it was still just part of the archives.)

BTW, here's the blog of my first VCS project (done for RetroChallenge 2018), which also attempts to explain the general concepts:

https://www.masswerk.at/rc2018/04/

  • Like 1
Link to comment
Share on other sites

6 hours ago, SavedByZero said:

Thanks all.  The remainder thing seems much more obvious now, it was the book phrasing that I think confused me.  I thought there was an agreement that the word "remainder" in this context meant whatever was left in A (-5), so I was scratching my head wondering why we needed to subtract 15 from it again.

 

Two questions about the final step, though -- aren't $70, $60, etc. addresses and not values?  And how do we know what those addresses map to -- how do they end up mapping to 7, 6, and so forth other than that chart in the book telling us they do?  Sorry if I'm missing something obvious, the way I approach things is more like an interpreter than a compiler (I digest a little at a time, then stop right there if I have a problem figuring it out, until I get it). 

 

In this case $70 and $60 are values, which after you store them into a HMPx register will instruct the TIA the horizontal pixel offset.

 

Do you have a copy of the "Stella Programmer's Guide"? This guide is essential for learning (and lookup) of the Atari 2600 specs:

This is the information from the Stella Programmer's Guide that you are looking for:

image.thumb.png.0016233c804d098fbf879e8f12f61f28.png

 

 

 

Link to comment
Share on other sites

Ahh...okay...er...I'll check all that out.  Thanks all. I picked up "Making Games For the Atari 2600" before knowing about either of those other books.  I'm not new to programming itself, but I'm definitely new to this level of assembly (my intel assembly course in college did not prepare me for any of this, and it was also over 2 decades ago) and I'm starting to get the feeling some prior knowledge was assumed with this book.

Edited by SavedByZero
Link to comment
Share on other sites

13 hours ago, SavedByZero said:

I'm not new to programming itself, but I'm definitely new to this level of assembly (my intel assembly course in college did not prepare me for any of this, and it was also over 2 decades ago) and I'm starting to get the feeling some prior knowledge was assumed with this book.

 

In my Collect tutorial I recommend checking out the Easy 6502 tutorial for those who aren't familiar with 6502 Assembly. 

 

I need to update my tutorial, all the code snippets ended up on 1 line after the forum upgrade. Should still be able to download the source to follow along.

Link to comment
Share on other sites

While we're on this subject, I'm trying to really understand how this sort of horizontal positioning method works. I get the basic idea, but when I crunch the numbers, it doesn't seem like it should work!

 

Here's the version used in Collect, as an example (the version Steven Hugg uses in his book and on 8bitworkshop is essentially the same, differing only in the placement of a couple of instructions).

 

PosObject
        sec
        sta WSYNC
DivideLoop
        sbc #15        ; 2  2
        bcs DivideLoop ; 2  4
        eor #7         ; 2  6
        asl            ; 2  8
        asl            ; 2 10
        asl            ; 2 12
        asl            ; 2 14
        sta.wx HMP0,X  ; 5 19
        sta RESP0,X    ; 4 23 
        rts            ; 6 29

 

Okay, so let's say you want your object to be at horizontal position 40. Here's how it seems to me this would work. I'm repeating the loop code so we can keep track of cycles and accumulator state better:

 

                    ;   A    A binary    Cyc  Tot   Clocks   Pixels 
    sta #40         ;   40   0010 1000               
    stx #4                      
    sec                     
    sta WSYNC       
                    
DivideLoop                          
    sbc #15         ;   25   0001 1001     2    2        6        -
    bcs DivideLoop  ;                      3    5       15        -
    
DivideLoop  sbc #15 ;   10   0000 1010     2    7       21        -
    bcs DivideLoop  ;                      3   10       30        -
    
DivideLoop  sbc #15 ;   -5   1111 1011     2   12       36        -
    bcs DivideLoop  ;                      2   14       42        -   Only two cycles when dropping through

    eor #7          ;        1111 1100     2   16       48        -   7 dec = 0000 0111 bin (flip last 3 bits)
    asl             ;        1111 1000     2   18       54        -
    asl             ;        1111 0000     2   20       60        -
    asl             ;        1110 0000     2   22       66        -
    asl             ;        1100 0000     2   24       72        4   $C0 = shift right -4 (i.e., move left 4)
    sta.wx HMP0,X   ;                      5   29       87       19   .wx only there to add an extra cycle?
    sta RESP0,X     ;                      4   33       99       31
    rts             ;                      6   39      117       49

 

By the time the write to RESPx happens, we should be at coarse position 31, shouldn't we? It's 33 cycles since the WSYNC, which is 99 color clocks, which puts us 31 clocks from the left (99-68), correct?

 

Then we're shifting that 31 to the left by 4? Wouldn't that put us at position 27? Getting to our desired position of 40 would require a shift right by 9 (which is impossible).

 

What am I getting wrong here?

 

Thanks!
 

 - Jeff
 

 

 

Link to comment
Share on other sites

Hi Jeff,

 

2 hours ago, JeffJetton said:

What am I getting wrong here?

First the value $C0 is a +4 pixel adjustment.

 

Looking at the routine, I could be wrong, but I don't think the extra cycle is needed to position the players.

 

If you were to use a value of 0, the routine in its current state would coarse position the player to pixel 69 with a fine adjustment of $60 or -6. This places the player at pixel 63. Adjusting for HMOVE (i.e. +8 pixels) would place the player at pixel 71.

 

If the extra cycle were removed from the HMP0,x hit then the player would coarse position to pixel 66 with a fine adjustment of the same -6 and place the player at pixel 60. Adjusting for HMOVE would then place the player at pixel 68.

 

 

  • Like 1
Link to comment
Share on other sites

Regarding the extra cycle introduced by the word-size instruction:

Mind that the r/w line goes to low (indicating write) at the beginning of the 4th processor cycle. So, while the entire instruction is 4 cycles (until the next instruction is decoded), the write happens right from the beginning of the last cycle.

 

(So there's a bit of an uncertainty here, regarding signals and cycle counts.)

 

Extra bits (and fun), from the 6500 Family Hardware Manual, Appendix A,

what's happening during an absolute write instruction:

Cycle    Address Bus   Data Bus   R/W   Comments
-----    -----------   --------   ---   --------
 T0      PC            OP CODE     1    Fetch OP CODE
 T1      PC + 1        ADL         1    Fetch low order byte of Effective Address
 T2      PC + 2        ADH         1    Fetch high order byte of Effective Adddr.
 T3      ADH, ADL      Data        0    Write internal register to memory

 

Edited by NoLand
Link to comment
Share on other sites

22 hours ago, DEBRO said:

First the value $C0 is a +4 pixel adjustment.

 

 

Ah, well that makes more sense. I guess I found the chart in the Stella Programmer's Guide confusing... you move right "the indicated number of clocks", but they must mean by the absolute value of the indicated number. Or put another way, I suppose the high nibble of the movement register always represents the magnitude of movement to the left, such that a negative number is a positive movement the other way.

Link to comment
Share on other sites

22 hours ago, DEBRO said:

Adjusting for HMOVE (i.e. +8 pixels) would place the player at pixel 71.

 

HMOVE adds another 8 pixels? I don't think I've ever read that fact before (is that what NoLand is talking about above?). That would certainly explain the math.

 

Edited by JeffJetton
Link to comment
Share on other sites

Hi Jeff,

 

12 hours ago, JeffJetton said:

HMOVE adds another 8 pixels? I don't think I've ever read that fact before (is that what NoLand is talking about above?). That would certainly explain the math.

This is an excerpt from the Atari 2600 TIA Hardware Notes by Andrew Towers...

Quote

In principle the operation of HMOVE is quite straight-forward;
if a HMOVE is initiated immediately after HBlank starts, which
is the case when HMOVE is used as documented, the [HMOVE] signal
is latched and used to delay the end of the HBlank by exactly
8 CLK, or two counts of the HSync Counter. This is achieved in
the TIA by resetting the HB (HBlank) latch on the [LRHB] (Late
Reset H-Blank) counter decode rather than the normal [RHB] (Reset
H-Blank) decode.

The extra HBlank time shifts everything except the Playfield
right by 8 pixels, because the position counters will now
resume counting 8 CLK later than they would have without the
HMOVE. This is also the source of the HMOVE 'comb' effect;
the extended HBlank hides the normal playfield output for the
first 8 pixels of the line.

 

  • Like 1
Link to comment
Share on other sites

13 hours ago, DEBRO said:

Hi Jeff,

 

This is an excerpt from the Atari 2600 TIA Hardware Notes by Andrew Towers...

 

Thanks, DEBRO. Interesting stuff.

 

I still haven't wrapped by brain around how that delay of the end of HBlank for one scanline winds up affecting the object display timers for all subsequent scanlines. But it looks like I've got more reading and experimenting (and study of old-skool video game display techniques) to do! ?

Link to comment
Share on other sites

And in general, this whole horizontal movement thing might be yet another area where I'm being tripped up by my "modern computer" way of thinking.

 

To me, a game would always store position as a state in memory, then just draw the thing at that position. That's essentially what the method above is doing (and probably any VCS game of any complexity would do). Keep track of the x coordinate for each object and have the TIA specifically reposition the objects at each x-coord during every frame.

 

But I wonder if the original intent (and maybe the way the early/simpler games like Tank and Pong worked) was to use the reset strobe just for the initial positioning at the beginning of the game, and rely solely on HMOVE-style relative repositioning to move the object horizontally from then on during the game? So the software would neither know nor care (due to hardware collision detection) about the exact x-position of things.

 

Is that what they actually did, or am I simplifying a bit too much?

Edited by JeffJetton
Link to comment
Share on other sites

18 hours ago, JeffJetton said:

But I wonder if the original intent (and maybe the way the early/simpler games like Tank and Pong worked) was to use the reset strobe just for the initial positioning at the beginning of the game, and rely solely on HMOVE-style relative repositioning to move the object horizontally from then on during the game? So the software would neither know nor care (due to hardware collision detection) about the exact x-position of things.

I think, to answer this, you have to go back to the very first arcade video games, like Computer Space and Pong. These used counter chains (TTL counter chips chained up sequentially, also called a sync chain) for generating the video image and implementing the positional logic. There were two major counter chains, a horizontal one counting the horizontal pixels (like in the TIA) and a vertical one counting the lines (which we have to do in software). Objects used a pair of counter chains of their own, but with a variable preset to achieve movement. Say, the horizontal resolution is 160 clock signals (like on the TIA) and you want to accomplish movements of up to 8 pixels to the left or right. So let's add 8 pixels to the horizontal 160 pixels for a total of 168 pixels. If we start counting on the horizontal reset signal at 8 (the so-called stop code), our object will be fixed on the screen. If we preset the counter chain by one less (7), it will take another clock signal to reach the reset state, therefore our object will "fall behind" the rest of the display and the respective object will visually move to the right as its display enable signal triggers late as compared to the rest of the display. If we were to start at 9, the counter chain would finish early and the object would slip a pixel to the left. (And, if we want to move at twice the speed, we'd use presets of 6 and 10 respectively.)

 

e02-hsync-chain.png

 

(Image: "The Textbook of Video Game Logic; Volume 1" by Arkush, William; Kush N' Stuff Amusement Electronics, Inc., Campbell, CA, 1976.

Here, two 8-bit counters and a simple flip-flop for an additional bit are used to count and an AND gate is used to determine the reset state, when signals 256H & 128H & 64H & 4H are present at the same time, indicating a total of 452 clock pulses, triggering the horizontal reset signal. If we use a counter chip with variable preset values as the first in the line, we get a variable counter chain as used for object motion.)

 

In theory, this is all you need to move an object around on the screen. (Collision detection is easily accomplished, just AND the display enabled signals for any two objects occurring at the same time and latch it for the frame.) However, even for Computer Space you probably need some positional awareness to detect, if the player rocket is inside a targeting channel of the saucer(s). Quite the same, you probably want to know in Pong, when the ball is off-field (which could be done easily in TTL logic by comparing the display enable signal to signals of the main, static counter chain).

 

The TIA seems to be a bit of a hybrid solution. The HMOVE circuitry clearly implements variable counter chains for on-screen objects just like in the early arcade games, but it is also absolutely required for positioning any objects, due to the difference in the respective clock frequencies of the CPU and the TIA and the granularity of delays loops implemented in software. I guess, if games had been expected to use relative movement only, the TIA had been designed for linear counters, allowing direct access to the screen position (like in the early arcade games). However, there are the nonlinear shift registers, for cost efficiency, clearly expecting the software to perform some arithmetics for fine positioning. Moreover, the initial specs were about running games like Pong and Tank and I don't think you could do them without referring to absolute positions, just relying on relative motion and collision detection. Then, you just have to figure out how to do this in software once (and reuse the routine for any other games, since all are done in-house at Atari ;-) ) and you can reduce the transistor count (and cost) of the chip shipped with every individual console sold to the customer.

 

(Edit: An alternative solution, using HMOVE only, may be to first position your objects at the beginning of a game in 15-pixel granularity and then fine adjust to the effective positions by HMOVE in the next frame. Then just keep track with the offset and the frame count while you move the objects by HMOVE. This way, you should know as well, were your objects are at any given time. However, this takes you just so far, as you run inevitably into problems as you introduce additional objects during the game. At least, these would be bound to starting positions at 15 bit intervals, which is probably not what you want.)

Edited by NoLand
  • Like 1
Link to comment
Share on other sites

On 8/15/2019 at 9:38 AM, JeffJetton said:

But I wonder if the original intent (and maybe the way the early/simpler games like Tank and Pong worked) was to use the reset strobe just for the initial positioning at the beginning of the game, and rely solely on HMOVE-style relative repositioning to move the object horizontally from then on during the game? So the software would neither know nor care (due to hardware collision detection) about the exact x-position of things.

 

Is that what they actually did, or am I simplifying a bit too much?

 

In Stella load up Combat and in the debugger enter the command trapwrite RESP0.  You'll find that the reset is only used at the start of a game to set initial positions.  After that HMP0 is used to move the tank left/right.  Same for RESP1 and HMP1 for the other tank.

 

For the player's shots the RESMP0 and RESMP1 registers are used to center the shot within the tank, then the HMM0 and HMM1 registers are used to move the shots left/right.

 

You can also reference the source for Combat, it's available at MiniDig.

  • Like 1
Link to comment
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.
Note: Your post will require moderator approval before it will be visible.

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