Jump to content
IGNORED

Why is X positioning so weird?


MrTrust

Recommended Posts

I'm trying to force myself to learn this, and every learning resource I have seen does X positioning more or less like this:

 

setX
  LDA X_Pos  ; Duh

  SEC          ;  Set the carry otherwise it subtracts one from the accumulator after the SBC

  SBC #$0F ;   I kind of get this.  The shortest loop we can write is 5 cycles, 3 pixels to a cycle means each loop is 15 pixels.  

  BCS setX  ;   This I don't get.   We are trying to loop until we get a negative result, yes?  Why do we not use BPL?  Will the result not be the same and won't that be more intuitive?

  

Then, I see two methods employed.  You either TAX and use the result of the SBC as a pointer for a lookup table to get your fine adjustment, or you do a XOR with $07 and then a bunch of ASLs, I assume to get your result into the high nibble of the accumulator.  Either way, you then hit HMP0 and wait for the next WSYNC.

 

Thing I don't understand is this is described as dividing the X position by 15, but it isn't really, is it?  We're using the how-far-did-we-go-beyond-zero number for our fine adjustments, which means we have a range of 1 to 15.  So, somewhere in that range of, what $F1 to $FF, we have rolled over from didn't delay long enough to delayed too long, so we need to move it left.  I guess this is how you would structure your lookup table.  I assume the XOR method does essentially the same thing, but I don't really understand how the math works here.

The other thing I don't understand is why are we hitting RESP0 every frame?  Why not just do it once in vsync when a new enemy is spawned or missile is deployed and just use your HMP0 to move the sprite around after that?  That works perfectly fine for moving sprites that are already "initialized", so why does every tutorial go through this whole routine just to move a lousy sprite one pixel to the left?  I don't get it.  I assume there's a good reason for it or every tutorial wouldn't have this inside the kernel, but nothing about it is intuitive, and I've never seen any of these resources say anything about this obvious question.  

Why is it all so weird and unintuitive?
 

Link to comment
Share on other sites

The more I think about it, maybe that last question is dumb.  If you just used the HMP0 to move, you would still need to update the X position variable when you did that, in case you need to read it for some other reason, like triggering enemy fire or if you needed to flicker or multiplex the sprite.  Since you're going to need to do a RESP0 at some point anyway, you'd might as well have a sub that you can just pass that variable to and do placement and movement in one pass.

Link to comment
Share on other sites

2 hours ago, MrTrust said:

Why do we not use BPL?  Will the result not be the same and won't that be more intuitive?

X positions can be 0-159 (160 positions).  In binary, anything 128 or above is considered negative.  So you'd never be able to position anything right of 128 or above.

 

2 hours ago, MrTrust said:

Thing I don't understand is this is described as dividing the X position by 15, but it isn't really, is it?

It is, really.  What's left is the remainder, essentially.

 

2 hours ago, MrTrust said:

The other thing I don't understand is why are we hitting RESP0 every frame?  Why not just do it once in vsync when a new enemy is spawned or missile is deployed and just use your HMP0 to move the sprite around after that?  That works perfectly fine for moving sprites that are already "initialized", so why does every tutorial go through this whole routine just to move a lousy sprite one pixel to the left?

COMBAT does do RESP0 only at the start of game, and uses HMP during play, just as you say.  But some games may want to arbitrarily position something more than +/- 16 pixels each time.  And in re-using sprite resources, often positioning is done mid-frame, often many times per frame.

 

Imagine "flickering" a sprite every other frame to represent two objects, for example.  Each would have it's own independent X position.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

You can do it exactly as you said by just using the fine positioning to move the sprite around.  That's how the designers intended the hardware to be used and that's perfectly fine ...if you don't plan to reuse sprites. 

 

If you do want to reuse sprites, though, then you will need a function to do sprite repositioning.

 

Now why do the tutorials introduce the sprite repositioning function right off the bat?  I'm guessing it's to make things easier, because if you use that function, then all the details of rough and fine positioning are abstracted away - all you need to do is set an X value for your sprite and call the function.

 

But, as you've seen, if you actually delve into the function and try to understand the internals of how it works, it can make your brain hurt. :)

 

Hope that helps.

  • Like 1
Link to comment
Share on other sites

47 minutes ago, glurk said:

X positions can be 0-159 (160 positions).  In binary, anything 128 or above is considered negative.  So you'd never be able to position anything right of 128 or above.

 

Ah, thank you.  This was driving me absolutely nuts.

 

49 minutes ago, glurk said:

It is, really.  What's left is the remainder, essentially.

 

So the quotient is equal to the number of loops we iterate less the last one...  So the remainder maps onto the number of color clocks "between" the loops the thing is somehow.  If the remainder is F1, that means the X position was exactly divisible by 15, since we would have been at zero to start the last iteration...

 

This is giving me a headache.  So, if the remainder is F2, then we would have been at 1 at the last loop, so that means we're going to kick out of the loop after the true xPos, so we need to move left 7 pixels, and if it's F8, we kicked out too early and need to move right seven pixels, and so on.

 

Or do I have that inverted, or not right at all?

 

1 hour ago, glurk said:

Imagine "flickering" a sprite every other frame to represent two objects, for example.  Each would have it's own independent X position.

 

Yeah, like I said, it occurred to me shortly after I posted this that that was kind of a dumb question.

 

This routine is analogous to a function call in a high-level language, maybe?  Whatever you preload a register with before you hit the sub is like passing an argument to it.  Obviously, you can feed it whatever xPosition variable you want, and if you use x/y index addressing, you can hit any of the sprite registers with it.

 

Is that a rational way of thinking about it?

Link to comment
Share on other sites

21 hours ago, MrTrust said:

Why is it all so weird and unintuitive?

 

INVENTING THE ATARI 2600: The Atari Video Computer System gave game programmers room to be creative:

 

Quote

One critical MOS-dependent feature was the use of a special counter—called a polynomial counter, or pseudorandom shift register—instead of a true binary counter to determine object positions on the screen. A polynomial counter occupies one-fourth the silicon area of an equivalent binary counter, but, unlike a binary counter, it does not count in any simple order. Thus, a programmer cannot calculate a screen position for an object and load it into the position counter.

 

emphasis added

 

A polynomial counter does not increment by 1, but instead counts in what appears to be a random sequence of values similar to the LFSR that we use to generate random numbers. Linear Feedback Shift Register: Uses as counters

Quote

The repeating sequence of states of an LFSR allows it to be used as a clock divider or as a counter when a non-binary sequence is acceptable, as is often the case where computer index or framing locations need to be machine-readable.[12] LFSR counters have simpler feedback logic than natural binary counters or Gray-code counters, and therefore can operate at higher clock rates. However, it is necessary to ensure that the LFSR never enters an all-zeros state, for example by presetting it at start-up to any other state in the sequence. The table of primitive polynomials shows how LFSRs can be arranged in Fibonacci or Galois form to give maximal periods. One can obtain any other period by adding to an LFSR that has a longer period some logic that shortens the sequence by skipping some states.

 

From one of my Atari 2600 Homebrew presentations, this is a commonly used LFSR for 2600 games:

 

 

2015HAE_001.thumb.png.ffb769d696e1c41ac4e36d669f6390e8.png

and this is the sequence of values it returns:

2015HAE_002.thumb.png.1105497e94a0a5b7e1dc5038312df786.png

 

after returning $84 it goes back to the beginning and returns $42.

 

So the positioning is weird because it took significantly fewer hardware resources to implement it that way, which costs less. Costs are the same reason there's only 128 bytes of RAM, only 4K of cartridge space, etc. Those cost reduction choices made the Atari somewhat affordable ($189.95 at launch in 1977, equivalent to $964.76 in 2023).

  • Like 7
Link to comment
Share on other sites

2 hours ago, SpiceWare said:

So the positioning is weird because it took significantly fewer hardware resources to implement it that way, which costs less. Costs are the same reason there's only 128 bytes of RAM, only 4K of cartridge space, etc. Those cost reduction choices made the Atari somewhat affordable ($189.95 at launch in 1977, equivalent to $964.76 in 2023).

 

Yes, I understand that.

 

What I'm referring to is to the way programming the 2600 gets explained, of which this one thing just happens to be an example.  Every single resource I've consulted on doing this goes through this whole spiel about "the TIA has no X positioning, so (diagram) we need to account for the asynchronous operation of the CPU and then compensate for..."

Well, apparently we don't need to do that.  Someone has already figured out how to abstract this in what is essentially a function call, so as far as we, the programmers, need to be concerned, the TIA effectively does have X positioning."  Maybe this is obvious to the people who write these things or who use them.  It certainly wasn't to me.  Why not just explain this in a way that's at least somewhat analogous to modern computing?  Here's a function.  Don't worry about getting into the weeds of how it operates the CPU; here's where to put it in your program's structure, here's how to call it, and here's what you need to pass to it to make it work correctly.

 

But nobody does explain it that way.  I buy Nanochess' book, for example, and it says we need to do a detailed analysis of this routine and really dig down into how it works.  First bullet in the explanation after the WSYNC:

"- Set the Carry flag (because the SBC instruction behavior takes Carry reversed, so Carry set means no borrow)."

Okay, now try to imagine you're an average person with no formal training in programming or mathematics.  What information does this convey to someone so situated?  It's not like there's any real explanation of the P registers or the SBC instruction prior to this point in the book, so what in the world does this mean?  So, you go looking around to try and find out, and, well, in two's complement, 8-bit binary arithmetic, if you have a singed integer more than 128...  Okay, what?  Again, all this stuff gets written as if it's something people would just know.  I know now that if the carry is not set prior to a SBC, it will subtract an additional $01 from the accumulator after the fact.  Why it does that or what that's supposed to mean wrt to doing calculations is beyond me (obviously it would mess up our comparison in this context, but generally).  This does not map onto conventional decimal subtraction in any way that is appreciable to me, or at least not at this point.

 

Everything that I see is half-abstracted.  I either need the full-on-remedial-math-class version, or the high level, here's some flowcharts for program structure and a library of functions with a reference, go write stuff version.  Now, most of this stuff is put out there for free, so I can't complain too much about it, but there does seem to be a gulf between 6502 knowers and learners that doesn't get communicated across very well.    
 

Link to comment
Share on other sites

On 10/25/2023 at 2:16 PM, MrTrust said:

Why is it all so weird and unintuitive?

 

2600 development in a nutshell! :)  Frustrating as hell, but you get bursts of euphoria once you've figured out an arcane trick or two.

 

16 minutes ago, MrTrust said:

What I'm referring to is to the way programming the 2600 gets explained, of which this one thing just happens to be an example.  Every single resource I've consulted on doing this goes through this whole spiel about "the TIA has no X positioning, so (diagram) we need to account for the asynchronous operation of the CPU and then compensate for..."

Well, apparently we don't need to do that.  Someone has already figured out how to abstract this in what is essentially a function call, so as far as we, the programmers, need to be concerned, the TIA effectively does have X positioning."  Maybe this is obvious to the people who write these things or who use them.  It certainly wasn't to me.  Why not just explain this in a way that's at least somewhat analogous to modern computing?  Here's a function.  Don't worry about getting into the weeds of how it operates the CPU; here's where to put it in your program's structure, here's how to call it, and here's what you need to pass to it to make it work correctly.

Depending on how you use it, it may make sense to roll your own X positioning functions (such as, say, you need to position an individual sprite/missile/ball and don't want to spend the clock cycles jumping and returning from a subroutine).  Or just use ones that others have developed.  I do (mostly, with some exceptions) the latter.  I also don't think there is one golden resource to learn everything there is to know about 6502/6507 development for the 2600, unless you can somehow absorb the knowledge from David Crane's head.   2600 games during their heyday were also an exercise in optimizing code to a suitable ROM size.  There were lots of shortcuts done to get to the commercially-viable 4K limit that would not pass muster in today's "modern development" practices.  In terms of 6502 knowers and learners (still being in the 2nd group myself) I had to view multiple sources of information (sometimes it duplicates, I consider it a sign of learning when you figure out what is redundant and what is new) and fill in the gaps on my own through experimentation.  This is my "learning list":

 

Particular places I learned 6502/6507 assembly (some of which you already know):
https://www.udemy.com/course/programming-games-for-the-atari-2600/  (Gustavo Pezzi has migrated this to a different site, so I'd recommend the migrated version instead:  https://pikuma.com/courses/learn-assembly-language-programming-atari-2600-games)
http://www.lulu.com/shop/andrew-davie/atari-2600-programming-for-newbies-revised-edition/paperback/product-23644281.html
https://www.randomterrain.com/atari-2600-lets-make-a-game-spiceware-00.html
https://8bitworkshop.com/v3.5.2/?platform=vcs

Programming Games For The Atari 2600 - Oscar Toledo G

Dennis Debro's disassemblies for original Atari 2600 games
Stella Programmer's Guide by Steve Wright 12/03/79
Countless topic posts on Atari 2600 programming on Atariage.com (really good one on bank switching here)

 

Web sites I used to help design various components of games:
https://alienbill.com/2600/playerpalnext.html - Playerpal 2600 for sprite design/animation
https://alienbill.com/2600/splash-o-matic - Splash-o-matic (now Atari-background-builder) for 48 pixel sprite design
https://alienbill.com/2600/atari-riff-machine/ - Atari riff machine (music/sounds)
https://www.masswerk.at/vcs-tools/TinyPlayfieldEditor/ - Tiny playfield editor 

 

Places I used to test out code:
Stella (The developer access interface "~" in this emulator is fantastic!)
https://8bitworkshop.com/v3.5.2/?platform=vcs  (I could modify code in real time to see its effects!)
https://skilldrick.github.io/easy6502/  (For testing basic 6502 functions)
https://javatari.org/  (8bitworkshop uses this too but was also handy to test the ROMs on its own).

 

  • Like 2
Link to comment
Share on other sites

12 minutes ago, littaum said:

Depending on how you use it, it may make sense to roll your own X positioning functions (such as, say, you need to position an individual sprite/missile/ball and don't want to spend the clock cycles jumping and returning from a subroutine).

To elaborate on this: the SetXPosition routine is not a panacea.  Imagine, for example you have a score display that is always in the same position.  It rarely makes sense to set it's X position and have the overhead (in bytes AND is used cycles) of calling the routine.  Typically what is done is to DIRECTLY hit RESP0/1 and manually set HMP0/1.  Same thing when having objects that don't move, like logo displays and such.

 

At times like these, you will need to understand the positioning more deeply.  Depending on the game and the display you are trying to achieve, sometimes you MUST find creative ways of positioning.  More advanced stuff uses indirect jump tables and multiple kernels for given lines / sections.

Link to comment
Share on other sites

When you're learning I wouldn't get too wrapped up in understanding how the internals of ready-to-use function like setX works, just treat them like a black box. When I wrote Medieval Mayhem I used an existing 48 pixel graphic routine for the menu and for the longest time I had no idea how it worked, just that it produced the results I needed. 

 

For my Collect Tutorial this is how I explained the PosObject subroutine I used that can set the X position for any of the 5 TIA objects:

 

;===============================================================================
; PosObject
;----------
; subroutine for setting the X position of any TIA object
; when called, set the following registers:
;   A - holds the X position of the object
;   X - holds which object to position
;       0 = player0
;       1 = player1
;       2 = missile0
;       3 = missile1
;       4 = ball
; the routine will set the coarse X position of the object, as well as the
; fine-tune register that will be used when HMOVE is used.
;
; Note: The X position differs based on the object, for player0 and player1
;       0 is the leftmost pixel while for missile0, missile1 and ball 1 is
;       the leftmost pixel:
;           players     - X range is 0-159
;           missiles    - X range is 1-160
;           ball        - X range is 1-160
; Note: Setting players to double or quad size will affect the position of
;       the players.
;===============================================================================
PosObject:
        sec
        sta WSYNC
DivideLoop
        sbc #15        ; 2  2 - each time thru this loop takes 5 cycles, which is 
        bcs DivideLoop ; 2  4 - the same amount of time it takes to draw 15 pixels
        eor #7         ; 2  6 - The EOR & ASL statements convert the remainder
        asl           ; 2  8 - of position/15 to the value needed to fine tune
        asl           ; 2 10 - the X position
        asl           ; 2 12
        asl           ; 2 14
        sta.wx HMP0,X  ; 5 19 - store fine tuning of X
        sta RESP0,X    ; 4 23 - set coarse X position of object
        rts            ; 6 29

 

 

Likewise it can take a bit to understand how negative numbers work (ie: why the 8-bit pattern for the value 255 is the same as the value -1).

  • Like 2
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...