-
Posts
65 -
Joined
-
Last visited
Content Type
Profiles
Forums
Blogs
Gallery
Events
Store
Community Map
Everything posted by JeffJetton
-
I wound up putting my reply back on the previous thread rather than here (since this isn't in programming). (Short answer? I'm stumped too!)
-
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
Okay, DigitalAnthony pointed out that, while using RESP0 alone won't get you right on the left edge, the difference isn't the 5 pixels as I described above, but rather only 3. Well heck, he's right. At least in Stella and Javatari. I've attached a slightly-improved version of my test program. I'm putting in an HMOVE, just to be more complete, although it's set up for "zero" and shouldn't change the coarse positioning. I also changed the way the player is drawn, to make it easier to see where it sits. Anyway, here are my recent findings: Sleep Cycles After RESP0 Color Clocks Trigger Pos Est. Draw Pos Emu Actual 0 3 9 0 5 3 5 8 24 0 5 3 10 13 39 0 5 3 15 18 54 0 5 3 17 20 60 0 5 3 18 21 63 0 5 3 19 22 66 0 5 3 20 23 69 1 6 6 21 24 72 4 9 9 22 25 75 7 12 12 So if you do 20 cycles of SLEEP or more, the player lands just where the math says it would. For example, at 20 cycles, you're at cycle 23 after the RESP0. That's 69 color clocks, or at pixel 1 (the 2nd pixel from the left). Take the 5-pixel draw delay into account, and you should be at x-position 6 (7th pixel over), which is what actually happens. (Yeah, the HMOVE bumps it to the right by 8 pixels, but then the clock stuffing of 8 clocks cancels it out exactly, so you're right back where you started.) But a SLEEP of 19, which I'm thinking should put you at position 5 really puts you at 3. And every value lower than that also puts you at 3. So why is there a delay of only 3 color clocks when RESP0 is hit during HBLANK, but a delay of 5 color clocks when it's not? Is this the way the real hardware would work? Or is this a case of the emulator not getting it quite right (and no one really caring because, really, what game would actually rely only on coarse positioning to put an object on the far left anyway? It's an admittedly weird example.) resptest2.asm -
Interesting. Heck, maybe I'm the one who's missing something here. ? Are you sure you're not using HMOVE or anything? That your positioning is merely with RESP0 alone? Could you post a code sample?
-
DigitalAnthony, We were actually just having a rollicking discussion of horizontal position just the other week. You might want to check it out: But to answer your question, yes, you can strobe RESP0 when the TIA is still in horizontal blank because the counter that controls horizontal positioning for P0 is a different one from the "main" horizontal counter. What's interesting is that the P0 counter isn't (normally) counting during horizontal blank. The clock that feeds it is only (normally) connected during the visible part of the horizontal line. The counter is designed to have 160 "ticks" before cycling around*, so it will hit zero at the same time on every scanline unless you mess with it. And RESP0 is one way to mess with it. All it does is reset the P0 counter to zero at the time you strobe it. It doesn't store the current horizontal position or anything like that. It doesn't have too, because player drawing isn't triggered by referencing an actual stored position. It's only triggered by that cycling counter hitting zero. Anyway, if you do the reset during HBLANK, that's no big deal. It's like setting a clock to midnight while it's unplugged. Once the counter starts up again, the beam will be at the left of the screen and (since the counter is now zero) the drawing of player 0 will be instantly triggered. There is a five-cycle delay** between the triggering of the draw and actual drawing, so you can't actually get to the extreme left using RESP0 alone. - Jeff * Okay, technically it only counts 40 times in a cycle, but the counter (when it's running!) only ticks every four color clocks. So it winds up being a 160-clock cycle anyway. Not really an important distinction from the programmer's perspective. ** Only four cycles for missile and ball objects
-
Wow, yeah. This is like, QWOP-level difficult for me. My high score is... 3. ? I do like the wide variety of obstacles. Maybe a "beginner" game variation where they are introduced one-at-a-time, gradually ramping up in difficulty? So you could get used to handling one before having to learn the next? Cool parallax cityscape!
-
Has anyone ever seen this Star Wars game for the 2600?
JeffJetton replied to BennyBean's topic in Atari 2600
Love how generic that ad copy is. It reads as if it were written before the game had even been designed. You could pretty much use it, Mad-Lib style, for any tie-in game... For example: -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
Huh. Yeah, that is a bit confusing. -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
If I understand your question correctly... When the tutorial says that "a zero page address is given, and then the value of the X register is added", it doesn't mean added as in "tacked on to the end". It means added mathematically. The address value is summed with the contents of X. So STA $02, X would be equivalent to STA ($02 + $01) or STA $03. Which, in turn, is equivalent to STA $0003. -
Amen. ? (Some people are weirded out by the whole "whitespace has significance" thing, but to me that just enforces the sort of stylistically-correct formatting readable code should have anyway, even if Python did require curly braces.)
-
Wow. All of those tricks are masterclass-level stuff, but this particular one is genius!
-
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
I promised (threatened?) more about HMOVE. Here we go... HMOVE really does delay the end of horizontal blanking by eight color clocks, which delays the point at which the clock driving the position counters is "plugged back in". So wouldn't that mean that all the objects would indeed be shifted to the right by eight pixels? Why can we get away with ignoring that when calculating the horizontal position? As it turns out, before HMOVE delays the start of the clock, it goes through a process that essentially bumps the counters up by anywhere from zero to 15 cycles/pixels, depending on the values found in the horizontal motion registers (HMxx). To continue the clock analogy, while HMOVE really does keep the clock unplugged another eight ticks, it also potentially "manually" moves the hands forward by some amount while they're still dormant. Those two things work together to determine the amount of time by which the clock winds up being "off" once it's plugged back in. The position counters are never "subtracted from" by moving them backwards. In fact, due to the way the counters are designed, I don't think there even is an efficient way to reverse them. They're strictly forward-only circuits. It's the combination of a variable forward shift (variable left movement between 0-15 clocks) with a non-variable start delay (fixed move right by 8 clocks) that results in a final move capability of up to 7 left or 8 right. Which means that the fine-positioning chart in the Stella Programmer's Guide is a dirty lie! Or at least a bit misleading. ? Per Andrew Towers: So... Stella Prog Guide Chart As Actually Seen by the TIA ------------------------------ ------------------------------- D7 D6 D5 D4 Clocks D7 D6 D5 D4 Clocks 0 1 1 1 +7 Move left 1 1 1 1 +15 0 1 1 0 +6 1 1 1 0 +14 0 1 0 1 +5 1 1 0 1 +13 0 1 0 0 +4 1 1 0 0 +12 0 0 1 1 +3 1 0 1 1 +11 0 0 1 0 +2 1 0 1 0 +10 0 0 0 1 +1 1 0 0 1 +9 0 0 0 0 0 No Motion 1 0 0 0 +8 1 1 1 1 -1 Move right 0 1 1 1 +7 1 1 1 0 -2 0 1 1 0 +6 1 1 0 1 -3 0 1 0 1 +5 1 1 0 0 -4 0 1 0 0 +4 1 0 1 1 -5 0 0 1 1 +3 1 0 1 0 -6 0 0 1 0 +2 1 0 0 1 -7 0 0 0 1 +1 1 0 0 0 -8 0 0 0 0 0 Notice how the "real" value for "no motion" is +8. That cancels out exactly the 8 color clock delay, thus no motion! - Jeff P.S. I've attached a portion of the TIA schematic that shows the HMxx circuit detail. I've annotated it and boxed in the area that is different between bits 4-6 and bit 7. Even if you're not a hardware engineer (I sure as heck ain't), it's pretty obvious that bit 7 is indeed wired "the other way around". -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
So I've done a wee bit of research, including studying Andrew Towers' amazing TIA Hardware Notes, writing some experimentation programs, checking out the TIA schematics, poking around the Stella source code, and even simulating the TIA polynomial counters in Python to get a better understanding of how they work (I put it up on GitHub if anyone's interested). What I found out is probably not news to most of you, but I figured I'd report back anyway for any future fellow newbies who stumble onto this thread. Here's why my math was off in my earlier post: As DEBRO pointed out, I had my fine adjustment backwards. $C0, which can be thought of as -4 in the high nibble, is a shift of 4 pixels to the right, not left. But as it turns out, HMOVE does not add another 8 pixels. More on that interesting fact later... That gets us to position 35. How do we get the rest of the way over to 40? Well, Karl G is indeed correct that you have to allow another 5 pixels for internal TIA timing reasons (only 4 pixels for missles/ball), which gets us to the desired 40-pixel position. Problem solved! But... apparently those 5 pixels are not due to a delay in setting the position with RESPx, but rather a delay in displaying the player on each line. Per Andrew Towers: "Each START decode [of the position counter value] is delayed by 4 CLK in decoding." In other words, there's an inherent four-TIA-cycle delay between the horizontal movement counters hitting the "draw" trigger and that drawing actually taking place. Player objects (due to the added complexity of their bitmaps, I assume?) take another cycle on top of that, for a total delay of five cycles/pixels for them. So the RESPx really does reset the position counter to zero at the exact spot on the scanline it finishes executing. There's no delay going on there. Left to its own devices, the counter will continue to hit zero at that same time (and thus same position) on every line. This explains, by the way, why hitting RESPx during the horizontal blanking period--and not using HMOVE at all--still results in the player displaying five pixels from the left edge, even if you hit RESPx immediately after the WSYNC. (It's true! I've attached some source code... try it and see.) If the delay was happening on the RESPx end, you should still have plenty of time for it to set the counter to zero. The position counter acts essentially like an unplugged clock during HBLANK, and you have 68 cycles to reset the hands of that clock back to midnight before it gets plugged back in for the visible scan line. So an early enough RESPx during HBLANK would give you a player smack-dab on the left-most pixel if display were immediate. The fact that that doesn't happen is a clue that the delay is in the display, not the reset itself. - Jeff P.S. Oh, and I realized that, if there really was a inherent shift of 4/5 pixels going on in the TIA, I should be able to find evidence of it in the Stella source code. Sure enough, if you look in Player.cxx (lines 364-391) you can see that the functions that get and set player position take a "renderCounterOffset" into account. Its value is defined in the header file: private: enum Count: Int8 { renderCounterOffset = -5, }; Same thing in the Missle.cxx/hxx and Ball.cxx/hxx files, although the renderCounterOffset is defined there as -4. BTW, these values wind up getting subtracted in the calculations. Since they're defined as negative, they effectively add to the horizontal position (i.e., move it to the right). resptest.asm -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
[Edited a bit since I originally misread Nukey Shay's point...] I initially figured there was something that kept track of the constantly-moving horizontal position, and that strobing RESPx caused that current position value to be stored somewhere. Subsequent scanlines would then just refer to the "frozen" stored position to know whether or not to start drawing. RESMPx would just read in the value from the player and copy it over to the missile. But, as I'm learning, horizontal positions aren't actually kept track of anywhere in a traditional sense. Strobing the reset registers merely resets the counters that control when an object's display begins (not where it begins, technically... although "when" and "where" are basically the same thing from the TIA's perspective). These object position counters just cycle through a period of time equal to 160 horizontal pixels, over and over again. Whenever the beginning of the cycle comes around again, it triggers the start of object display, similar to how a clock striking midnight triggers the chimes to go off. The counters are independent of each other, of course, so each can have their own local time and thus their own personal midnight. The strobing any of the object position reset registers (with RESPx, RESMx, or RESBL) doesn't change the cycle state at which the trigger happens. That's always "midnight" as far as the counter/clock is concerned. The strobe just adjusts the hands to make them immediately "point to midnight" right there and then. That's how I've been thinking about it, anyway. Which makes sense, I guess. If you wanted a grandfather clock to remind you to take a roast out of the oven at 6:15, you wouldn't modify the internal workings to get the chimes to go off at 6:15. Much easier to just adjust the clock hands to a time that, while incorrect compared to what the actual time is, will nonetheless make it strike midnight when your roast is ready. Anyway, as I understand it, RESMPx doesn't access the state of the player position counters or directly set the missile position counter. All it does it set some flag that effectively causes a RESMx to occur in the middle of the next player x drawing routine. So yeah, the RESMPx does sort of "collect" the position in a sense, but at no point does any of the logic genuinely "know" where any of the objects are, does it? -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
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? -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
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! ? -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
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. -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
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. -
understanding fine motion from the book
JeffJetton replied to SavedByZero's topic in Atari 2600 Programming
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 -
Looks great, acasabo!
-
As I'm learning VCS programming, I notice that the examples in Steven Hugg's books tend to only define the "top" two vectors (RESET and IRQ/BRK), like so: org $fffc .word Start .word Start Whereas Andrew Davie's "Newbies" guide and Darrell Spice Jr's "Collect" tutorial both make a point of setting the NMI vector, one word earlier. For example: ORG $FFFA .word Reset ; NMI .word Reset ; RESET .word Reset ; IRQ Now I know it's largely academic, since neither NMI nor IRQ will ever get used on a 6507. Of course, since you have to set RESET, you might as well set IRQ to something. But I see the appeal of ignoring NMI--you get one more byte to use. Still there's also the thought that maybe one day the code will be running on a 6502 or a more-modern variant or something. Heck, there's still an `sei` in CLEAN_START, presumably for a similar reason. Then I got to wondering how the "original" programmers did it. I realized that I could attempt to find out by whipping up a small program that read in all the .bin files of my Atari ROM collection, and examined the last six bytes. I decided that, if the value found at the third-to-last word of the file matched either the second-to-last (RESET) or last (IRQ/BRK), then I would take that as evidence that the programmer deliberately set NMI. Otherwise, I'll assume they didn't, and any value in that spot was code/data/filler. Here, for example, is what I found for the nine launch titles (or as close as I had to them): title size nmi reset irq nmiset AIRSEA2.BIN 4096 F000 F000 F000 TRUE Basmath.bin 2048 6002 F000 0000 FALSE Black_j.bin 2048 0202 F000 F769 FALSE Combat.bin 2048 0000 F000 110F FALSE Indy_500.bin 2048 7777 F000 F000 FALSE Outerspc.bin 2048 F000 F000 F000 TRUE Street Racer - Speedway II (1978).bin 2048 4488 F000 8040 FALSE Surround (1978).bin 2048 F394 F394 F394 TRUE Vid_olym.bin 2048 1111 F000 F438 FALSE Note that only three of them seem to have set NMI. And here's the full breakdown of all the ROMs I have: size num_unset num_set pct_unset pct_set 2048 37 14 72.5 27.5 4096 208 89 70.0 30.0 8192 54 81 40.0 60.0 10495 1 0 100.0 0.0 12288 2 1 66.7 33.3 16384 13 33 28.3 71.7 32768 0 1 0.0 00.0 It's interesting how the results for the 4K and 2K carts mirror what was hinted at with the launch titles: Only about a third of them look like they've bothered to set NMI. I'm not sure how bankswitching works, so I'm not sure that these results mean anything for carts over 4K. But if they do, the higher NMI percentages with the 8K and 16K carts might be due to less of a need to squeeze out every byte (plenty of room, so why not set it?). Or maybe, given that these carts are typically more recent than the smaller ones, the programmers eventually learned something that caused them to be more likely to think it important to set? What do you folks think? Do you typically set your NMIs? Why or why not? - JJ
-
My Non-Tendo R.O.B. project controlling Nintendo's ROB with an Atari
JeffJetton replied to Pioneer4x4's topic in batari Basic
Pioneer, if you paste your code into your post using the "Code" format (the <> button in the editing toolbar, it will preserve your spaces and such (as well as use a monospaced font that will ensure everything's "lined up" as it should be, such as your playfield data blocks, etc.) In any case, a very cool project! Thanks for releasing it into the wild. -
How do I release my code/bin to the public?
JeffJetton replied to Pioneer4x4's topic in Atari 2600 Programming
This ^^^ was the first thought I had as well. -
Clever or interesting uses of the BRK instruction?
JeffJetton replied to AkashicRecord's topic in Atari 2600 Programming
Wow! I'm new-ish at this stuff, and that's the first time I've seen the "cookbook" at that link. There's some seriously great stuff there. It looks like there were more ideas planned that were yet-to-be collected in it. Did it ever get finished or more fleshed-out? -
Oh, and there are a few other things you might want to think about (and bear in mind that I'm no expert here either): Yes you absolutely should check out the above-mentioned tutorial. Your playfield data is sandwiched in-between your initialization code and the beginning of your kernel (your "frame" loop). I don't see that you're skipping over it with a jmp or anything, which means all that data is getting executed as if they were instructions! It just so happens that %10101010 (or $AA) is the TAX opcode. So your program starts out by transferring A to X 146 times in a row. (In fact, you could actually replace some or all of your .byte %10101010 lines with an equivalent number of TAX instructions and your program would run exactly the same.) You could skip the data with a jmp StartOfFrame, but it's probably more common to simply define your data at the end of your program, right before that final ORG $FFFA. And yes, unlike many (most?) structured programming languages, here it's perfectly legal to reference data that you don't "define" until later in your program. Another thing that's different from programming languages you may be used to is that it's usually better to count down to zero in all your loops rather than counting up to your target value. CPX is really just subtracting X from A and not storing the result anywhere. BNE merely tests to see if the last math operation resulted in a zero and branches if it didn't. By using a DEX instead of an INX, your final decrement to zero will trigger the desired no-branch condition. That eliminates the need for the compare instruction, saving two bytes and two processor cycles per iteration. For example... ; Vertical Blank LDX #37 VerticalBlank STA WSYNC DEX BNE VerticalBlank
-
Hi George, Your set of data for PFG0 is only 48 bytes long. PFG1 and PFG2 are both 49 bytes. Your X register, on the other hand, is holding values ranging from 0 to 190. Since you're using X as an offset to the base address of each of your sets of playfield data, it eventually "overshoots the runway", causing your LDA to start loading the values of your actual program instructions and using those values to set the playfield. For example, once X reaches 49 (beyond the end of your PFG0 data), the LDA PFG0,X instruction actually loads A with the first byte of PFG1. When X gets to 98, it moves beyond PFG1 and reads the first byte of PFG2. And by the time X hits 147, you're now loading in the value of that LDA that comes after your data, at the "StartOfFrame" label. (It happens to have a value of $A9, by the way). Of course, your LDA PFG1,X and LDA PFG2,X instructions are doing the same thing, only they start farther down in your data and therefore wind up reading more of your program instructions. If you used each byte of playfield data for four consecutive scanlines, that would fix your problem. Woah LDA PFG0,X STA PF0 LDA PFG1,X STA PF1 LDA PFG2,X STA PF2 INX STA WSYNC STA WSYNC STA WSYNC STA WSYNC cpx #47 bne Woah ;FINISH AREA LDA %00000000 STA PF0 STA PF1 STA PF2 FED INX LDA #$0 STA COLUBK STA WSYNC CPX #48 BNE FED Alternatively, you could store your offset in memory somewhere instead of in X. You'll still use X to keep track of which scanline you were on, but you'd only INC your offset variable every 4 scanlines.
