-
Content Count
57 -
Joined
-
Last visited
Posts posted by NoLand
-
-
OK, regarding 650x timing and when the write actually occurs, if I'm interpreting the following diagram right, it's only near the end of the third CPU cycle.
The diagram should match the third cycle (T2) in STA zeropage, on the address bus is 00, ADL (hi, low address) and the data to write is on the data bus with R/W going low. According to this, the data is put on the bus just before the middle of the second phase of a cycle, which should be about a TIA color clock before the end of the CPU cycle. (This wouldn't allow for much of another delay to occur on the TIA's side. Or, if there is a delay for the signal to become effective it would have to be compensated internally for accurate counter values.)
-
1
-
-
There are some pretty competent folks here on AtariAge, who are actually exploring the TIA and are coding emulations. Maybe, one of them may drop in with some details.
Also, regarding manuals, there's also some detail in the 6502 Programming Manual, http://www.bitsavers.org/components/mosTechnology/6500-50A_MCS6500pgmManJan76.pdf
There's detailed information on what happens during each CPU cycle, but not much on what's happening below the cycle level.
-
1
-
-
This is in the 6502 Hardware manual, http://www.bitsavers.org/components/mosTechnology/6500-10A_MCS6500hwMan_Jan76.pdf
What I do not really know, is how long the TIA takes to latch the signal. I recall somewhat faintly that it may be as much as 2 TIA clocks. But I wouldn't even know where to look this up.
-
1
-
-
Let's have a closer look at what's happening here, until the internal counter position for player 0 is actually latched in the TIA.
First, mind that for a value of 20 we're not just subtracting 15 once with a remainder of 5, since the remainder will be still positive, hence the carry is still set and SBC will branch. Only after the next subtraction, we'll end up with a value less than zero (-10) and proceed with the EOR.
At this point (after the last subtraction) the accumulator is $F6 (-10), but we're actually concerned with the lower 4 bits only (since the higher ones will be shifted out). After the EOR, the accumulator holds $F1, or, after the shifts, a $10, which corresponds to a HMOVE offset of +1.
Hum. Still not what we may have expected.
Let's have a look at the cycle count, right after the CPU restarts after WSYNC:
STA zeropage (3) 3 SEC (2) 5 SBC immediate (2) 7 BCS branching (3) 10 SBC immediate (2) 12 BCS no branch (2) 14 EOR immediate (2) 16 ASL (2) 18 ASL (2) 20 ASL (2) 22 ASL (2) 24 STA zeropage (3) 27 STA zeropage (3) 30 -------------------- 30 CPU cycles = 90 color clocks AC = $10 => HMOVE offset +1
After 90 color clocks, we're 22 pixels into the playfield (since HBLANK lasts for 68 color clocks).
An offset to the left of +1 (HMOVE) will give us 21, which is still a pixel off, at least in my calculations.
Still, we're missing some. The signal is actually set on the data bus during the third CPU cycle of the STA instruction, which may account for the difference. On the other hand, it'll take a bit until the signal is actually latched in the TIA. (From a CPU perspective, we're dealing here with a bit of a Heisenberg blur. But, given that a CPU cycle has actually two phases, one where the R/W signal is low and the CPU is writing to the bus, followed by a second phase, where the R/W goes high and the CPU reads from the bus, we may conclude that the write occurs during the first half of the third CPU cycle, when the color clock is closer to 20 or 21 than to 22.)
-
1
-
-
On 20. März 2020 at 10:17 PM, pvmpkin said:One issue I come up against each time is purely having instructions in the wrong place and getting unexpected results.
Mind that you can always set the address, the code will be assembled to, (the origin) by the directive `org <addr>` (e.g., `org $FF00`). There's also the `align` to align the origin to a certain byte in the given page (however, I personally find "org" a bit clearer). You definitely want to use this with these time cirtical routines. (The assembler will fill any "holes" in the code by zeros, so there's no danger in skipping a few addresses.)
-
1
-
-
Just make sure to have an even number of scanlines per field on PAL, otherwise color is lost. (Certainly on emulators, if PAL color loss is a real thing on actual CRTs, is a bit open to debate, compare this post.)
I once made a statistic of figures provided in the Atari 2600 scanline list at Digital Press, and came up with these numbers:
TV Scan Lines Observed System Standard Min Max Median NTSC 262 238 290 262 PAL 312 258 336 312
So, while most games historically stuck to the standards, there are significant deviations, some of them by rather popular games.
-
2
-
-
Hi,
maybe this blog covering my first project on the VCS is of help by providing another perspective: https://www.masswerk.at/rc2018/04/
Background: This was a project for RetroChallenge, which is a loose gathering about doing a project on retro hardware over a particular month (in this case April 2018) and providing means to participate by following your progress. Since the project was done in a month, it's not too complex. The intended audience of the blog were other RetroChallengers, meaning, folks who know a bit about programming and the 6502 (I covered the 6502 in another project before), but are not particularly familiar with the VCS / Atari 2600. So, regarding the VCS, the blog starts from zero. I had read up on the VCS before (mostly Andrew Davie's tutorial here on AtariAge, and the Stella Programmer's Guide), but these were my first actual steps. As always, things are actually a bit different, when doing them for real, and I tried to share everything I knew and what I had found on the way in these write-ups.
-
2
-
-
I think, the key is in when to iterate the random number. If you want to use it for procedural generation, iterate it in a controlled fashion from a known seed value (e.g., when entering a room). When you want "true" random, iterate it at every frame from the very beginning (users wont press a button to start a game in the exact millisecond) and/or iterate it at any user input (the exact duration of which will be some of a real world noise as well), e.g., for any frame the button is pressed or there's some other joystick or paddle input. In order to have both for a particular purpose (e.g., character generation), I'd opt for a splash screen and iterating the random number until the user deploys a button or switch to start the game – and by this the actual character generation. If you store this value, you may come back to it whenever you need/like and have the same results for the same seed. Putting this one step further, you might also use some bits of this value (or another random value) to determine any extra iterations between using the value in the LFSR to get some more (pseudo) random out of it. (So in one case, you might iterate the generator 2, 4 and 3 times between use and in another 5, 2 and 3 times, etc., avoiding subsequent values in the series, but still in a controlled manner.)
-
2
-
-
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.)
(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.)
-
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
-
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:
-
1
-
-
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.
-
15 minutes ago, Dionoid said:...
Seeing the actual code helps a bit… :-)
-
1
-
-
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 => %11100000by 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.
-
Are you advancing the linear shift register on any random input?
A linear shift register will produce the same series of values over and over again (which is, why it can be used in the TIA for storing/latching positional values). Another way to understand it is by looking at it as a way of hashing a series of sequentially incremented input values to another value space. (The series produced by the LFSR until it returns to its first value on encountering its "stop code" may be shorter than the 256 values of a byte.*) So, while we get some scrambling out of this, it's still deterministic and not random.
We still need to introduce some source of random on our own, otherwise the LFSR will produce the same series of "random" numbers over and over again, starting from the initial reset value – and the gameplay will be always the same. However, we can't alter the sequence, but, what we may do, is skipping some values. E.g., we may run the LSFR for an extra step every frame a button is pressed or on any other input, by this using the user as a source of random. It may be preferable to do so even before the actual game is started, so that the game already starts in a "more random" state.
Edit:
*) This is also why having an overly complicated LFSR code isn't always preferable, as applying multiple shift and XOR operations may actually shorten the series (period) produced and we may end up with less random, as the series repeats after just 40 values or so. There's nothing wrong with keeping it simple, like in the Batari BASIC example above.
-
Near label "SkipReverseDuckXVelocity" you are missing a "#", what you mean is probably:
ldx DuckState
cpx #STATE_DUCK_FLYAWAY
...
As is (cpx STATE_DUCK_FLYAWAY), you are comparing DuckState to the contents of address $02, which happens to be the collision register CXP0FB.
Edit:
This is really a common error and hard to spot, especially in your own code (this is a bit like proof reading your own text).
As you are already using upper-case constants for those values, setting up a regular expression search in your text editor to find any of those missing "#" may be a good idea. E.g.,
\sSTATE_\w*
meaning, we are searching for any words starting with "STATE_" and headed by a white-space character (like a blank).
Any hit should indicate one of those hard to catch errors.
(Alternatively, instead of using a regular expression, just search for " STATE_" [mind the blank!] with word-matching disabled. The regular expression is essentially the same, but matches both heading blanks and tabs. Moreover, you may extend it to match any of your constants at once, like \s(STATE|VALUE|POS)\w* ...)
-
1
-
-
As a historical note, this is one of the very first computer games ever, compare Kalah on the PDP-1.
https://www.computerhistory.org/pdp-1/_media/pdf/DEC.pdp_1.1961.102645673.pdf
(The game was done at MIT and is commonly attributed to 1959, but the PDP-1 didn't arrive at MIT before fall of 1961.)
-
1
-
-
Maybe using a binary tree as you go along in your logic and then just flatten the tree? However, you'd have to reserve some memory for this (assuming the tree may extend from the root-node to either side in its entirety) and traversing the tree may be just as expensive.
-
But couldn't the menu program pass in a value for the variable that stores the select switch counter? I assume there's some variable that stores the current variation number.
First, these really are just switches with just a bit of conventional meaning associated to them by the markings on the console. The program may "ask" the TIA chip for the state of the switches (as stored in one of the registers) and act to its liking, even the reset switch isn't hardwired in any way. (Nothing but convention hinders you from [mis]using the reset switch, say, as the fire button in your game.) And no, there's no way to preset the TIA register used for this, since it's read-only.
A game starts from a fixed address in the cartridge ROM, which will direct the game to its initialization routine. There, it will probably wipe the little RAM, there is, in order to start from a known state (so we already lost any information that might be in there). Then, it probably proceeds to the title screen, where it may read the switch register and adjust the value of a few RAM addresses based on the results. This is probably, where the select switch will be read with the game cycling through its options accordingly. Some switches will be even checked every frame during the game, like the reset switch or the difficulty switches (depending on the game). – So, each program will handle this differently on an individual basis and it will initialize the RAM on startup or just write directly to RAM to take note of the current state of a console switch.
Edit: The register for the switch states is, of course, in the RIOT chip (PLA).
-
I was wondering if there could be a generic menu program that could call the other game when you're done and pass in the variable containing the variation number when it's done (sort of like a wrapper). That way you wouldn't have to tinker with the game's code, just set a variable in it. Like I said, I'm not really a programmer so I don't know how doable that would be.
This would work as a helper to adjust static options, like by the BW and difficulty switches, but not for select, where the game has to keep track of the state as the users is cycling through options. (There is no API for options, programs are just checking the hardware to determine the state of the switches. E.g., the game starts with option 0 for select and then checks the state of the select switch every frame and increments an internal count, if the switch is active.)
-
For a simple, web-based VCS sprite editor in JavaScript see https://www.masswerk.at/rc2018/04/TinySpriteEditor/.
The "Tiny 8-Bit Sprite Editor" generates assembler code (for copy and paste) on the fly and accepts common assembler code as input, both in normal and in reversed byte order. Else, there are just a few basic commands, like draw/paint, move pixels, append lines.
-
Hi, I just found out about the nomination of "Refraction" – thanks a lot for honoring the humble game!
Norbert
-
1
-
-
I tried to provide a bit of an analysis here: http://www.masswerk.at/rc2018/04/10.html
(Includes a timing diagram showing when values are passed/copied to any registers.)
-
2
-
-
Yes I am the original author. Took many hours. I am happy for it to be shared and used, I was just having a bit of a laugh.
Thank's again, your effort is much appreciated!
Since it's about sharing, here's in return my amateurish version of the VCS block diagram for public use (It's close to what is found in the TIA-1A manual and in the "Reconstructed Stella Programmer's Guide): http://www.masswerk.at/rc2018/04/images/e01-stella-block-diagram.png
-
1
-

Constructive help needed on finishing first 2600 Assembly Game
in Atari 2600 Programming
Posted · Edited by NoLand
This is a sound player, I once came up with. You may address any of the two channels and play a sound, which is defined in a table:
; RAM addresses for sound control (use your own!) SoundIdx0 = $B9 SoundIdx1 = $BA SoundTmr0 = $BB SoundTmr1 = $BC ; sound subroutines PlaySound ; sound in Y, channel/player in X (0,1) sty SoundIdx0,X cpy #0 beq playSoundReset ; index is zero, mute and return lda SoundTable + 3,Y ; get duration in frames sta SoundTmr0,X lda SoundTable,Y sta AUDC0,X ; tone lda SoundTable + 1,Y sta AUDF0,X ; frequency/pitch lda SoundTable + 2,Y sta AUDV0,X ; volume rts playSoundReset lda #0 sta AUDV0,X ; reset volume sta SoundIdx0,X sta SoundTmr0,X playSoundDone rts HandleSounds ; channel in X (0,1) ldy SoundIdx0,X beq handleSoundsDone ; index = 0, no sound dec SoundTmr0,X ; decrement frame counter beq handleSoundsNext ; run to zero? next sound from table handleSoundsDone rts handleSoundsNext lda SoundTable + 4,Y ; next sound tay jmp PlaySound ; sound table SoundTable .byte 0 ; no sound / stop ; tone pitch vol frames next ; ------------------------- Snd_MissileBounce = * - SoundTable .byte $04, $04, $06, $04, $00 Snd_Barrier = * - SoundTable .byte $0F, $19, $07, $04, Snd_Barrier1 Snd_Barrier1 = * - SoundTable .byte $0F, $1a, $07, $10, Snd_Barrier2 Snd_Barrier2 = * - SoundTable .byte $0F, $1b, $04, $04, S00 ;(...) ; "Snd_MissileBounce = * - SoundTable": ; just defines a label holding the current offset into the sound table ; to be used when calling the subroutine, like in ldx #0 ; channel 0 ldy #Snd_MissileBounce ; sound index jsr PlaySound ; do it ; each frame, we then call subroutine HandleSounds for each channel: ldx #0 jsr HandleSounds ldx #1 jsr HandleSounds ; a call to PlaySound will stop the current sound and replace it ; by the new one, as it simply overwrites the timer and index data. ; (You may check SoundIdx0 and SoundIdx1 to check for any non-zero : values, indicating that the channel is occupied, before.) ; This will stop a sound in a given channel: ldx #0 ; channel 0 ldy #0 ; sound index: no sound jsr PlaySound ; mute the channelMind the 5th value, "next", for each of the table entries. This will allow you continue with another sound. (This may be also a pause, like "0, 0, 0, 5, <labelOfNextSoundToPlay>" for a pause of 5 frames.)
For a bit more of a background, see https://www.masswerk.at/rc2018/04/13.html
This one has a bit more on VCS sound basics and links to a test application: https://www.masswerk.at/rc2018/04/12.html
(The sound test application is here, online using Javatari, you may also download ROMs: https://www.masswerk.at/rc2018/04/studio2600/ )