Jump to content

NoLand

New Members
  • Content Count

    24
  • Joined

  • Last visited

Community Reputation

20 Excellent

About NoLand

  • Rank
    Space Invader

Contact / Social Media

Profile Information

  • Gender
    Male
  • Location
    Vienna, Austria

Recent Profile Visitors

1,534 profile views
  1. 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. 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.)
  3. 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
  4. 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/
  5. "$" 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.
  6. 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.
  7. 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.
  8. 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* ...)
  9. 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.)
  10. 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.
  11. 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).
  12. 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.)
  13. 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.
  14. Hi, I just found out about the nomination of "Refraction" – thanks a lot for honoring the humble game! Norbert
×
×
  • Create New...