Jump to content

SeaGtGruff

Members
  • Posts

    5,591
  • Joined

  • Last visited

  • Days Won

    2

Everything posted by SeaGtGruff

  1. Regarding the format used for counting cycles, use whatever you like or come up with your own style-- what matters is that it makes sense to you. I'm sure some programmers look at my style and say "WTF?" Some people like a very simple, basic style-- and worrying about lining everything up with leading zeros or extra spaces, and/or showing the color clock and screen position counts, may not be worth the extra effort in cases where all you need is to keep a running total of the machine cycles. In the SLEEP 12, there's no need to add 5 to the screen position-- that would be relevant only for a RESP0 or RESP1 instruction (or add 4 for a RESM0, RESM1, or RESBL instruction). There are only two or three reasons I can think of why you'd ever want or need to waste cycles: (1) In 2600 programming, timing can be very important because of the way registers are often updated in the middle of a scan line so as to squeeze the most out of the 2600's limited graphics abilities. (After all, if you had a dozen sprites, or a dozen color registers and fancy multicolor graphics mode at your disposal, there'd be no need to "recycle" any of the graphics registers by changing their values partway through a scan line.) So in order to make some change at just the right point on a scan line, you might need to waste some cycles so that a particular instruction can be executed at a specific cycle. (2) Typically a kernel will be designed to use a loop for drawing one or maybe two scan lines during each pass through the loop. But since the 2600 is so slow-- each machine cycle corresponding to 3 color clocks, and only 76 machine cycles per scan line-- sometimes (or really, just about every time) you don't have quite enough cycles available to do all the things you want, so you have to start making compromises, or else find creative ways to shave off a cycle here and there so you have a little more time to do things. One of the ways that programmers might reclaim a few cycles is to get rid of the WSYNC command-- but since strobing WSYNC is the main way that you keep the instructions lined up nicely from line to line (e.g., so that a graphics change will always occur at a specific cycle on the line), getting rid of WSYNC means you might have to pad the loop so that it takes up exactly 76 cycles (including the cycles used by the comparison and branch instruction). You might even need to "unroll the loop," which means getting rid of the comparison and branch instruction, and drawing the scan lines one after another without using a loop, either for the entire screen or maybe for just a section of the screen. (3) Another possible reason for wasting cycles (and bytes) might be if you have some code in ROM that you want to copy into RAM so you can execute it from RAM. This allows you to modify the code dynamically as the program is executing. This isn't nearly as common as the first two reasons, but it isn't unheard of. In such a case you might conceivably want to pad parts of the code so there's room to change the code as needed-- i.e., you might use filler code such as NOPs, or change the addressing mode used by some of the instructions, so that the code always occupies the same number of bytes in RAM, and/or executes in the same number of cycles, even though portions of the code are being modified as the program executes. Note that it's more common when you execute code from RAM to just change the operands, in which case there's no reason to pad the code by wasting bytes or cycles. I haven't looked at the full code you've quoted from-- I assume this is from Dragonfire-- but in this case the cycles are being wasted for reason 1, to make sure the GRP0 and GRP1 registers get changed at just the right times. I'm not sure whether or not the VDELxx registers are being used for the dragon, but I'm guessing they aren't. That means GRP0 and GRP1 can be loaded much earlier on the scan line for the left half of the dragon (the first copy of player0 and the first copy of player1), but then the program needs to wait until it's safe to change GRP0 and GRP1 for the right half of the dragon, otherwise the graphics would change while the left half is still being drawn and the dragon wouldn't look right. There's a little bit of leeway for updating GRP0 and GRP1 for the right half of the dragon, since GRP0 can be updated any time while the first copy of player1 is being drawn, and then GRP1 can be updated any time while the second copy of player0 is being drawn. I assume the entry into the loop occurs on different cycles based on the position of the dragon, that way the same number of cycles can be wasted each time before updating GRP0 and GRP1 for the right half of the dragon. As for the 48-pixel "score trick," it must be programmed differently than this example because it has more stringent requirements. The 6502/6507 has only 3 data registers-- A, X, and Y-- so that means if you preload them with the desired data then you can write their values to page 0 one after the other and it will take 9 cycles (3 cycles per write). Since 1 cycle equals 3 color clocks, the scanning beam moves 9 color clocks during each write. If you weren't using the VDELP0 and VDELP1 registers, you could preload GRP0 and GRP1 for the first copies of player0 and player1, then write to GRP0, GRP1, and GRP0 at just the right moment for the next 24 pixels. But then you'd need to load another value into A, X, or Y so you can update GRP1 again for the last 8 pixels, and there simply isn't enough time. But if you turn on VDELP0 and VDELP1, you can preload GRP0 twice, since there are really two copies of GRP0 and two copies of GRP1 inside the TIA-- an "old" register and a "new" register. Whenever you write to GRP0, the new data you've written doesn't get displayed until you write to GRP1, and vice versa-- the new data you write to GRP1 isn't displayed until you write to GRP0. So at some early point on the line, you write the first value to GRP0 (but it doesn't display yet), then write the next value to GRP1 (but it doesn't display yet, although it does cause the first value of GRP0 to be displayed), then you write another value to GRP0 (but it doesn't display yet, although it does cause the first value of GRP1 to be displayed). That means you've preloaded the first 3 sprites but only the first 2 have been displayed (by which I mean their graphics are "active," whether or not they've been drawn onscreen yet). Thus, you have only 3 more sprites left to worry about, so you can load up A, X, and Y, then quickly write to GRP1, GRP0, and GRP1 one after the other, causing the third, fourth, and fifth sprites to be displayed. To get the sixth sprite to display you must write to GRP0 one more time, but it doesn't matter what you write to it since the new data won't be displayed, it will merely trigger the data for the sixth sprite (copy 3 of player1) to be displayed. If you're drawing a 40-pixel sprite (2 copies of one player positioned between 3 copies of the other player), or anything less than that-- 32 pixels, 24 pixels, or 16 pixels-- then you generally don't need to use VDELP0 and VDELP1, although they're sometimes used in other cases (apparently they were originally intended to be used in conjunction with "2-line kernels," but then some clever 2600 programmer came up with the idea for the "score trick"). As for "the red eye trick," I haven't looked at the code, but one thing that comes to mind is positioning the ball behind the dragon's head and setting COLUPF to red so the red ball can be seen through the eye hole.
  2. You should set HMP1 before you strobe WSYNC again, so you can strobe HMOVE right after WSYNC. In your code HMOVE is getting strobed too late, which will affect the number of pixels that you can shift the players left or right, as well as changing what each value represents (i.e., $10 won't represent "move the sprite left 1 pixel" as you are expecting). Also, setting HMP1 before strobing RESP0 and RESP1 will save a few bytes of ROM: Layout NOP ; 2 | 2 NOP ; 2 | 4 NOP ; 2 | 6 NOP ; 2 | 8 NOP ; 2 | 10 NOP ; 2 | 12 NOP ; 2 | 14 NOP ; 2 | 16 NOP ; 2 | 18 NOP ; 2 | 20 NOP ; 2 | 22 - HBLANK stops here NOP ; 2 | 24 | CC +6 NOP ; 2 | 26 | CC +12 NOP ; 2 | 28 | CC +18 LDA #%00010000 ; 2 | 30 STA HMP1 ; 3 | 33 STA HMP1 ; 3 | 36 STA RESP0 ; 3 | 39 | CC +51 after written STA RESP1 ; 3 | 42 | CC +60 after written STA WSYNC ; 3 | 0 STA HMOVE ; 3 | 3 Note that I used "STA HMP1" twice. That's just to waste 3 more cycles to keep the timing as desired. You could also use "NOP 0" which is a 2-byte, 3-cycle instruction-- but since "NOP 0" is technically a so-called "illegal" 6502 instruction, and takes exactly the same number of bytes and cycles as "STA HMP1," I decided to just write to HMP1 twice in a row since that's a "legal" instruction. As for counting cycles, different programmers have their own formats of choice. The format I like to use would look something like the following: Layout SLEEP 28 ; +28 ; 28 LDA #$10 ; +02 ; 30 STA HMP1 ; +03 ; 33 STA HMP1 ; +03 ; 36 STA RESP0 ; +03 ; 39 ; 117 ; 049 ; 054 STA RESP1 ; +03 ; 42 ; 126 ; 058 ; 063 STA WSYNC ; +03 ; 00 STA HMOVE ; +03 ; 03 I use leading 0s to help keep the numbers lined up nicely in columns, depending on how large the values in a particular column can be-- 2 digits for machine cycles (maximum 76 per line), or 3 digits for color clocks (228 per line) or screen positions (160 per line). The columns after the instructions are as follows: - machine cycles used by the instruction, written with a plus sign since they add to the cycle count; the extra spaces are for asterisks where appropriate (e.g., for a branch instruction I would write +02** since a branch instruction uses 2 cycles if the branch is not taken, plus 1 more cycle if the branch is taken, plus 1 more cycle on top of that if the branch crosses a page boundary) - the current running total of machine cycles used on the given scan line - the current running total of color clocks elapsed on the given scan line (equal to the current machine cycles times 3) - the current screen position on the given scan line (equal to the current color clock minus 68, written with a negative if it's within the HBLANK-- e.g., -68, or -04, or 007, or 029, or 159, etc.) - the screen position where the sprite will appear if it isn't shifted left or right by HMOVE (equal to the current screen position plus 4 if the sprite is a missile or the ball, or plus 5 if the sprite is a player) I omit any columns that aren't pertinent-- for instance, if all I need to know is what the running total of machine cycles happens to be, then I just give the cycles added by the instruction followed by the running total. But if the instruction is related to graphics and it would be helpful to know the corresponding screen position, then I include the current color clock and the current screen position. And if the instruction is resetting a sprite's position, I also include the column showing the sprite's new screen position after the reset instruction has completed. You can simplify the appearance of your code if you use the SLEEP macro instead of writing a bunch of NOP instructions. The assembler will expand the SLEEP instruction into the appropriate number of NOP instructions, so it doesn't actually save you any ROM; but it does look more concise and is more convenient to code. Another thing I like to do is put a blank line before I strobe WSYNC, since that can help make it more clear when reading the code where one scan line ends and another begins. But then I also sometimes like to insert blank lines to group related instructions together for increased legibility, as in the following example: LDA #1 STA $80 LDX #2 STX $81 LDY #3 STY $82 But you should develop whatever format and style works best for you.
  3. The five RESxx ("reset") registers have only one function-- to reset a sprite's horizontal position on the scan line-- so they have no other use per se. However, they can be used to trigger extra copies of the sprites beyond the normal three-copy limit. There are a few things you should know about the RESxx registers. First, writing to ("strobing") a RESxx register lets you position the corresponding sprite at intervals of 3 color clocks (CCs), since 1 machine cycle (MC) equals 3 CCs. The new screen position is equal to 3 times the MC immediately following the completion of the write instruction, minus 68 CCs to get the pixel position on the visible portion of the scan line, plus an extra number of CCs to compensate for the START signal. For the players you need to add 5 CCs, but for the missiles and ball you need to add only 4 CCs, so in theory the possible positions are as follows: MC CC M+4 -68 P+5 -68 -- --- --- --- --- --- 00 000 004 -64 005 -63 01 003 007 -61 008 -60 02 006 010 -58 011 -57 03 009 013 -55 014 -54 04 012 016 -52 017 -51 05 015 019 -49 020 -48 06 018 022 -46 023 -45 07 021 025 -43 026 -42 08 024 028 -40 029 -39 09 027 031 -37 032 -36 10 030 034 -34 035 -33 11 033 037 -31 038 -30 12 036 040 -28 041 -27 13 039 043 -25 044 -24 14 042 046 -22 047 -21 15 045 049 -19 050 -18 16 048 052 -16 053 -15 17 051 055 -13 056 -12 18 054 058 -10 059 -09 19 057 061 -07 062 -06 20 060 064 -04 065 -03 21 063 067 -01 068 000 22 066 070 002 071 003 23 069 073 005 074 006 24 072 076 008 077 009 25 075 079 011 080 012 26 078 082 014 083 015 27 081 085 017 086 018 28 084 088 020 089 021 29 087 091 023 092 024 30 090 094 026 095 027 31 093 097 029 098 030 32 096 100 032 101 033 33 099 103 035 104 036 34 102 106 038 107 039 35 105 109 041 110 042 36 108 112 044 113 045 37 111 115 047 116 048 38 114 118 050 119 051 39 117 121 053 122 054 40 120 124 056 125 057 41 123 127 059 128 060 42 126 130 062 131 063 43 129 133 065 134 066 44 132 136 068 137 069 45 135 139 071 140 072 46 138 142 074 143 075 47 141 145 077 146 078 48 144 148 080 149 081 49 147 151 083 152 084 50 150 154 086 155 087 51 153 157 089 158 090 52 156 160 092 161 093 53 159 163 095 164 096 54 162 166 098 167 099 55 165 169 101 170 102 56 168 172 104 173 105 57 171 175 107 176 108 58 174 178 110 179 111 59 177 181 113 182 114 60 180 184 116 185 117 61 183 187 119 188 120 62 186 190 122 191 123 63 189 193 125 194 126 64 192 196 128 197 129 65 195 199 131 200 132 66 198 202 134 203 135 67 201 205 137 206 138 68 204 208 140 209 141 69 207 211 143 212 144 70 210 214 146 215 147 71 213 217 149 218 150 72 216 220 152 221 153 73 219 223 155 224 156 74 222 226 158 227 159 75 225 229 161 230 162 However, the first 68 CCs correspond to the horizontal blanking period, and due to the way the TIA draws the sprites they can't be positioned "offscreen" inside the HBLANK area-- i.e., you can strobe the RESxx registers during HBLANK, but the sprites won't be positioned within the HBLANK region; instead, they be positioned as though you'd strobed their RESxx registers at cycle 22 (i.e., where cycle 22 is the first cycle following the completion of the write instruction). If you look at the list above, it looks like the players should be able to be positioned at pixel 000 by strobing their RESxx registers at cycle 21, but it doesn't work that way. I'll explain this more in a moment. Second, since you can position a sprite only at intervals of 3 CCs, you'll need to also use the HMxx ("horizontal motion") registers and the HMOVE register to position them at the other pixel positions. Third, when you reset a sprite's position, it may not appear on that scan line-- it depends on whether the sprite has already been drawn on that scan line (at the old position) and whether you're displaying any copies of the sprite, but it can also depend on whether you strobe the RESxx register more than once. To understand why, you need to know how the TIA draws the sprites. When you reset a sprite's position by strobing its RESxx register, the TIA sets the sprite's START signal at that position. The START signal lasts for 4 CCs, but the TIA doesn't actually begin to draw the sprite until after the START signal is turned off again. So, for example, if you were to strobe the RESP0 register at cycle 25 (meaning the write instruction is executed during cycles 22, 23, and 24, with cycle 25 being the first cycle after the write instruction has completed), then player0's START signal would be automatically turned on at CC 075 (or pixel position 007) of each scan line (until you reset the sprite's position to a different location), and automatically turned off again at CC 079 (or pixel position 011). Thus, the sprite won't actually be drawn until CC 079. However, for the players it's as if the players were 9 bits wide but with the first pixel always being blank-- i.e., the drawing of the players is delayed by an extra CC, as follows: MC 22 = CCs 066, 067, and 068 -- begin STA RESxx instruction MC 23 = CCs 069, 070, and 071 -- STA RESxx continues executing MC 24 = CCs 072, 073, and 074 -- STA RESxx completes executing MC 25 = CC 075 -- sprite's START signal begins CC 076 -- START signal continues CC 077 -- START signal continues CC 078 -- START signal continues CC 079 -- drawing begins here for the missiles and the ball CC 080 -- drawing begins here for the players If we use SSSS to represent the 4-CC START signal, then for a player it would look like the following: SSSSx76543210 where "x" represents the extra blank CC and "76543210" represents bits 7 through 0 of the player's GRPx register. However, the TIA doesn't actually generate a START signal after the RESxx register has been strobed-- it doesn't begin generating the START signal until 228 CCs later. So if you're displaying just 1 copy (i.e., the main copy) of player0, and player0 hasn't already been drawn on the scan line where you've strobed RESP0, then player0 won't appear on that scan line at all. But if player0 had already been drawn on that scan line before you strobed RESP0, then player0 will appear at the old position on that scan line, and at the new position on the subsequent scan lines. (Note: I think the ball may be an exception to this rule-- i.e., its START signal may begin immediately rather than being delayed by 228 CCs, as I've seen references in the 2600 literature that say you can trigger the ball multiple times on a scan line-- but I haven't tried it to see whether or not it works.) On the other hand, if you're displaying additional copies of the sprite, the TIA will generate START signals for the additional copies, so they *will* appear on the scan line where you strobe the RESxx register. These additional START signals will occur 16, 32, or 64 CCs after the beginning of the first START signal, as follows: ssssx76543210xxxSSSSx76543210xxxSSSSx76543210xxxxxxxxxxxxxxxxxxxSSSSx76543210xxx In the line above, ssss represents the START signal that's delayed by 228 CCs (1 scan line), which triggers the main copy of the sprite. The other START signals (SSSS) are for the close copy, medium copy, and far copy of the sprite, which *won't* be delayed by 1 scan line-- but they'll occur only if those copies are turned on. The trick that lets you draw more than 3 copies of a sprite on a scan line works because the extra START signals do occur right away-- i.e., you won't get copy 1 (the main copy) on that scan line, but you will get copy 2 and copy 3. So if you strobe RESxx multiple times-- with a suitable delay between the multiple strobes-- you can draw copy 2 and copy 3 multiple times. Now, there's an interesting fact that lets you get all 3 copies if you time things just right. If you strobe RESxx such that the ssss signal for the main copy would occur sometime during one of the SSSS signals, that SSSS signal will act like the new ssss signal, although it will be "repositioned" to be aligned with the sprite's new position. For example, if you have the NUSIZ0 register set to draw 3 close copies of player0, you can draw all 3 copies on the scan line by doing the following: STA RESP0 sleep 3 STA RESP0 This is because the first STA RESP0 sets the main copy's ssss signal (which doesn't actually get triggered until 228 CCs later) at the new position, and the first SSSS signal (for copy 2) occurs 16 CCs later. But the second STA RESP0 is timed so that the ssss signal for the second STA RESP0 occurs 18 CCs later, or 2 CCs into the SSSS signal for copy 2, therefore you end up getting a START signal that's 6 CCs long. This is useful if you want to display more than 3 copies of the sprite, but in most cases you'll want to just strobe RESxx once and then use HMOVE to fine-position the sprite. In that case you'll need to strobe RESxx once, store the desired fine-positioning motion value in the HMxx register, and then use HMOVE (typically right after WSYNC), so you can basically count on needing 1 scan line to position the sprite, such that there will always be at least 1 blank scan line between one version of the sprite (at the old position) and the next version of the sprite (at the new position). The only exception would be if the old position were far enough to the left of the new position, in which case you could theoretically draw the two versions of the sprite with no blank line between them-- although in practice a positioning loop is typically used, so you will get at least one blank line on which you'll call the subroutine that executes the positioning loop. I hope the above explanation wasn't too confusing. You don't need to worry about the intricate details unless you're planning to use the multi-reset trick-- and you probably shouldn't attempt that until after you've understood the basics of sprite positioning.
  4. [Random Terrain mode, PG-13 version] I think Bethesda is starting to suffer from "My poop don't stink" syndrome, thinking that people are so enamored with Bethesda's poop that they'll shovel out a poop-load of money up front to buy it, and then keep shoveling out more money every month just to keep sniffing it. [/Random Terrain mode, PG-13 version]
  5. What about making the parameters optional, so the programmer could do something like TSOUND channel, freq, waveform, volume ; to set all parameters at once TSOUND channel, freq ; to set just the frequency TSOUND channel, , waveform ; to set just the waveform TSOUND channel, , , volume ; to set just the volume ; or some combination of the above: TSOUND channel, freq, waveform TSOUND channel, freq, , volume TSOUND channel, , waveform, volume
  6. From the programmer's perspective, TIA color register changes appear to take effect immediately. For example, if you strobe WSYNC, wait 21 CPU cycles, then write a new value to the COLUBK register-- such that the first cycle after the write instruction has completed is cycle 24-- the new background color will appear at color clock 72 (3 * 24), or screen position 4 (72 - 68).
  7. I looked at the NTSC version of Tomcat: The F-14 Flight Simulator in Stella. VSYNC gets turned on at cycles 3 through 5, and gets turned off 3 scan lines later at cycles 6 through 8, which is probably okay-- so the best thing to do is make sure you're using the NTSC ROM rather than the PAL ROM. PS -- The NTSC version does draw 262 scan lines, so that isn't a problem.
  8. According to Atarimania, F-14 Tomcat is a PAL game. I don't know if there's an NTSC version with that name, but there's an NTSC version called Tomcat: The F-14 Flight Simulator, so maybe you should try that version instead.
  9. Some games have vertical sync signals that aren't lined up properly on the scan lines-- vertical sync gets turned on and off mid-line rather than at the beginning of the scan line, which can cause the screen to roll on some TV sets. I don't know if that's the problem with F-14 Tomcat, but if it is then it might be fixable with a hack.
  10. I just saw this after answering your PM, so you can ignore some of the questions I asked in my reply. Assuming the DASM.EXE executable is in the C:\DASM folder, there are a few things that might be causing your problem: (1) The file you're trying to assemble isn't in the C:\DASM folder and you didn't include its file path in the command line. (2) The file you're trying to assemble *is* in the C:\DASM folder, or you *did* include its file path, but the file name itself is wrong-- e.g., it might be "example.asm" but you entered it as "mygame.asm" because that's what the example command line said. (3) The problem might be in the other parts of the command line-- the "-f3" parameter or the "-o" parameter. I think these parameters need to be in lowercase, and "-o" is the letter "o" as in "output." There should be no spaces between the parameters and their arguments.
  11. How large of a playfield are you wanting to scroll-- how many PF pixels wide, and how many PF pixels tall?
  12. In that case, I'd suggest looking at the threads that talk about scrolling a large playfield. Note that data and sdata in batari Basic are just two different approaches to the same thing-- letting you read data from an array. They can accomplish the same things, but the methods will necessarily be different, so you end up picking the one that's faster and requires less work, or maybe the one you understand the best. For a large 3x3 scrolling playfield, the data should be stored differently than for a grid of 3x3 screens: 3x3 grid: byte 1 = screen 1, row 1, column 1 byte 2 = screen 1, row 1, column 2 byte 3 = screen 1, row 1, column 3 byte 4 = screen 1, row 1, column 4 byte 5 = screen 1, row 2, column 1 byte 6 = screen 1, row 2, column 2 etc. In other words, for a grid you'd store each screen as a separate screen, with its own rows and columns (byte columns, not pfpixel columns). But for a large playfield you'd want to treat the entire set of data as a single playfield. You could organize it either by row or by column: 3x3 playfield, organized by rows: byte 1 = row 1, column 1 byte 2 = row 1, column 2 byte 3 = row 1, column 3 byte 4 = row 1, column 4 byte 5 = row 1, column 5 byte 6 = row 1, column 6 etc. byte 12 = row 1, column 12 byte 13 = row 2, column 1 byte 14 = row 2, column 2 etc. 3x3 playfield, organized by columns: byte 1 = column 1, row 1 byte 2 = column 1, row 2 byte 3 = column 1, row 3 byte 4 = column 1, row 4 etc. Depending on whether you want to use (indexed) data or sdata, the method you use to organize the data may impact how large the playfield can be, since an indexed array is limited to 256 bytes. So if you organize by columns, each column could be up to 256 bytes tall, which is just over 23 screens tall. But if you organize by rows, each row could be up to 256 bytes long, which is 64 screens wide. So for your situation either method would be fine, but I'd probably go with organizing by columns. Anyway, you could use either an indexed array, or sdata, depending on which is easier for you to understand or (if both are equally easy/difficult for you) which is faster or takes less ROM for the routines (depending on whether speed or space is the more important consideration). Using indexed arrays: data column1 ; 33 bytes of data here end data column2 ; data end data column3 ; data end ; etc. For sdata it's the same, except you'd do it as a single set of data: sdata roomdata=a ; 33 bytes of data for column 1 ; 33 bytes of data for column 2 ; 33 bytes of data for column 3 ; 33 bytes of data for column 4 ; 33 bytes of data for column 5 ; etc. end With the indexed array you can just use the names of the tables (column1, column2, column3, etc.) to access the data, but with sdata you'd want to define some constants for the lo-/hi-byte pointers as mentioned previously: const column1lo = <roomdata const column1hi = >roomdata const column2lo = <(roomdata+33) const column2hi = >(roomdata+33) const column3lo = <(roomdata+66) const column3hi = >(roomdata+66) ; etc.
  13. That would be fine, except that it would require code to jump between the pages, which could be clumsy if a room was half of one page and half of another page, wouldn't it? It'd need some sort of on..goto statement to switch between data sets, as I don't know of any way to do so elegantly without on..goto In this particular case, I'm inclined to think that sdata is the better solution. But to read the address Array+1624, you'd actually be setting Hi = (1624 / 255) : Lo = (1624 & 255), right? No, you'd need to set Hi = (Array + 1624) / 255 and Lo = Array + 1624 - 256 * Hi. You can't do that in bB, because bB doesn't have 16-bit math. That's why it's easier, given what you're wanting to do, to define constants for the hi and lo bytes of the various places you want to start reading from in the sdata (since the assembler will do all the math for you when you're compiling your program). Before I try to answer any of your other questions, I need to understand what you mean by a scrolling playfield. When you said 9 rooms of data arranged in a 3x3 grid, I thought you were going to be moving from room to room. If that's not the case-- if you're actually talking about scrolling around within a large playfield-- then I'll have to rethink this, and I'm kind of fuzzy-headed right now because of flu medicine. However, there are a couple of topics that talk about scrolling large playfields.
  14. I haven't looked at your code to see what you're trying to do, but collisions only register if they occur while the screen is being drawn-- i.e., when the TIA is drawing a pixel at a specific spot on the screen, it's able to detect when two or more objects are overlapping at that position (because it's being told to draw them at the same time), in which case it sets the collision flag(s) for the objects in question. So if you move the objects below the screen-- by which I assume you mean behind the score-- the collisions can't occur, since the objects aren't drawn down there.
  15. Let's assume the sdata looks something like this: sdata roomdata=a ; room 1's data ; room 2's data ; room 3's data ; room 4's data ; room 5's data ; room 6's data ; room 7's data ; room 8's data ; room 9's data end To start reading from a given location within the sdata, either you're going to have to calculate the desired address yourself-- which would be a bit of a hassle-- or you can define some constants and let the assembler do all of the hard work for you: const room1lo = <roomdata const room1hi = >roomdata const room2lo = <(roomdata+44) const room2hi = >(roomdata+44) const room3lo = <(roomdata+88) const room3hi = >(roomdata+88) ; ... const room9lo = <(roomdata+352) const room9hi = >(roomdata+352) Then when you want to read the data for a particular room you just set the sdata pointer and start reading: ; to read the data for room 6 a = room6lo : b = room6hi ; we defined the sdata to be variable a, so the lo-byte is in a and the hi-byte is in b ; now read the 44 bytes of data sequentially: var0 = sread(roomdata) var1 = sread(roomdata) var2 = sread(roomdata) ; etc. var47 = sread(roomdata) You could also use a for loop or nested for loop.
  16. That's because the data is read from the array using an index, and an index is a 1-byte register that can have a value of only 0 through 255. So the data you're reading must be at Array+X, where Array is the starting address of the data and X is the 1-byte index (the 6507's X register, not bB's X variable), therefore the range is Array+0 through Array+255. The disadvantage of using indexed data is that (normally) the data can't exceed 256 bytes, but the advantage is that it can be read randomly. To get around the 256-byte limitation you can break the data into 256-byte pages, each page having its own address, and read from the desired page-- e.g., ArrayPage3[138]. With sdata there's no index per se, because the entire address is stored as a 2-byte pointer. Note that bB's instructions about using sdata stress that your program must run over the sdata itself before you try to use it (I didn't look up the exact wording, but it's something like that). That's because when bB encounters the sdata it stores the starting address in the 2-byte pointer. Then as you read from the sdata the address of the pointer is updated so that it always points to the next unread byte of data. You aren't reading from an indexed position per se, but from a 2-byte address. The range is 256*Hi+Lo, where Hi is the hi-byte of the address and Lo is the lo-byte. Since Hi and Lo both have ranges of 0 through 255, the range for the sdata is 256*0+0 through 256*255+255, or 0 through 65535. The advantage of sdata is that the data can exceed 256 bytes, but the disadvantage is that (normally) it can't be read randomly. You can get around the non-random limitation by setting the 2-byte pointer yourself-- e.g., if you want to randomly access the data at offset 1624, you'd need to set the pointer to the address Array+1624.
  17. I've edited the following list from DASM's mne6502.c file. It shows all the supported 6502 mnemonics in alphabetical order, followed by the hex machine codes. I edited out the addressing mode indicators to keep this simple (it's just a QAD edit), but at least it shows which abbreviations 6502 accepts for the "illegal" opcodes, which is nice to know since many of the documents about these codes use different abbreviations from each other. adc - 0x69, 0x65, 0x75, 0x6D, 0x7D, 0x79, 0x61, 0x71 anc - 0x0b and - 0x29, 0x25, 0x35, 0x2D, 0x3D, 0x39, 0x21, 0x31 ane - 0x8b arr - 0x6b asl - 0x0A, 0x06, 0x16, 0x0E, 0x1E asr - 0x4b bcc - 0x90 bcs - 0xB0 beq - 0xF0 bit - 0x24, 0x2C bmi - 0x30 bne - 0xD0 bpl - 0x10 brk - 0x00 bvc - 0x50 bvs - 0x70 clc - 0x18 cld - 0xD8 cli - 0x58 clv - 0xB8 cmp - 0xC9, 0xC5, 0xD5, 0xCD, 0xDD, 0xD9, 0xC1, 0xD1 cpx - 0xE0, 0xE4, 0xEC cpy - 0xC0, 0xC4, 0xCC dcp - 0xc7, 0xd7, 0xcf, 0xdf, 0xdb, 0xc3, 0xd3 dec - 0xC6, 0xD6, 0xCE, 0xDE dex - 0xCA dey - 0x88 eor - 0x49, 0x45, 0x55, 0x4D, 0x5D, 0x59, 0x41,0x51 inc - 0xE6, 0xF6, 0xEE, 0xFE inx - 0xE8 iny - 0xC8 isb - 0xe7, 0xf7, 0xef, 0xff, 0xfb, 0xe3, 0xf3 jmp - 0x4C, 0x6C jsr - 0x20 las - 0xbb lax - 0xa7, 0xb7, 0xaf, 0xbf, 0xa3, 0xb3 lda - 0xA9, 0xA5, 0xB5, 0xAD, 0xBD, 0xB9, 0xA1, 0xB1 ldx - 0xA2, 0xA6, 0xB6, 0xAE, 0xBE ldy - 0xA0, 0xA4, 0xB4, 0xAC, 0xBC lsr - 0x4A, 0x46, 0x56, 0x4E, 0x5E lxa - 0xab nop - 0xEA, 0x80, 0x04, 0x14, 0x0c, 0x1c ora - 0x09, 0x05, 0x15, 0x0D, 0x1D, 0x19, 0x01, 0x11 pha - 0x48 php - 0x08 pla - 0x68 plp - 0x28 rla - 0x27, 0x37, 0x2f, 0x3f, 0x3b, 0x23, 0x33 rol - 0x2A, 0x26, 0x36, 0x2E, 0x3E ror - 0x6A, 0x66, 0x76, 0x6E, 0x7E rra - 0x67, 0x77, 0x6f, 0x7f, 0x7b, 0x63, 0x73 rti - 0x40 rts - 0x60 sax - 0x87, 0x97, 0x8f, 0x83 sbc - 0xE9, 0xE5, 0xF5, 0xED, 0xFD, 0xF9, 0xE1, 0xF1 sbx - 0xcb sec - 0x38 sed - 0xF8 sei - 0x78 sha - 0x9f, 0x93 shs - 0x9b shx - 0x9e shy - 0x9c slo - 0x07, 0x17, 0x0f, 0x1f, 0x1b, 0x03, 0x13 sre - 0x47, 0x57, 0x4f, 0x5f, 0x5b, 0x43, 0x53 sta - 0x85, 0x95, 0x8D, 0x9D, 0x99, 0x81, 0x91 stx - 0x86, 0x96, 0x8E sty - 0x84, 0x94, 0x8C tax - 0xAA tay - 0xA8 tsx - 0xBA txa - 0x8A txs - 0x9A tya - 0x98 mne6502_c.txt
  18. I could live with a product-placement type of ad if it was part of the game. But I hate free games that pop up random ads at the top of the screen, or in one of the corners, while you're playing the game, and then remind you that you can remove the ads by buying a registered copy of the game. "Hey everybody, download my new game and play it FOR FREE! Oh, and if you want to get rid of the pop-up ads that I keep shoving in your face at the most annoying times or screen locations, you can easily disable them by SENDING ME MONEY!" Bleah. Either release your game FOR FREE without any annoying ads, or CHARGE MONEY FOR IT in the first place. Many of these games are very reasonably-priced (a few bucks or so), but telling you the game is FREE and then threatening to keep shoving unwanted ads in your face unless you BUY the game smacks of extortion.
  19. Harmony Fortissimo Harmony Accelerando Harmony Alto Harmony Animato Harmony Arpeggio Harmony Espressivo Harmony Dolce Harmony Grandioso Harmony Maestro Harmony Octave Harmony Primo Harmony Polyphony Harmony Vivace
  20. I logged onto my old computer and compiled a simple multisprite program to see how the sprite data was stored. I haven't worked out all the details of the kernel yet, but it looks like the data must start at $xx5A or above-- $5A in decimal notation is 90. It appears this is related to the way the kernel draws the sprites-- it uses Y as the index, but Y is initialized to 88 (or to the height of the playfield) and then decremented as each line-pair is drawn. There's also a bit of math in which the kernel temporarily changes the value of the player's lo pointer, as well as its y position, then later restores them to their original values. Consequently if you want to store all the sprite shapes in a continuous strip, it's far simpler to just define a single sprite that contains all the shapes (with a 0 byte between each shape), and let batari Basic handle the details. If you define the sprite at the beginning of your program (as was done in the Tone Toy program), and don't add any more lines in front of it, you can still use constants to store the lo and hi bytes of the shapes so you don't need to keep looking up the values each time you change the program. The easiest way to do this would be to put the player0 definition right after the "set kernel multisprite" line, before your dim lines and const lines, as follows: set kernel multisprite player0: ; insert sprite shape data here end dim something = a const somethingelse = 123 When batari Basic compiles a program, it assigns line numbers to each non-blank line of the program, including rem lines-- the first line is .L00, the second is .L01, the third is .L02, etc. When it compiles a line that contains sprite graphics data, it stores the graphics data after the program code-- hence the location of the data can change as you continue working on your program-- but it assigns a label to the starting address of the data so it knows where the data is. The label will be playerLxx_y, where xx is the line number and y is the sprite number. For example, if a player1 statement were on line .L123, the label for its starting address would be playerL123_1. Or if a player0 statement were on line .L07, the label would be playerL07_0. Thus, if you put the player0 statement near the start of your program and don't add any more lines in front of it, you can easily predict what the label for the sprite's data will be, and then you can use that label in your const statements, as follows: rem * example of using constants to store a sprite's address set kernel multisprite player0: ; insert sprite data here end const plo = <playerL2_0 const phi = >playerL2_0 In the example above, line .L00 is the rem, line .L01 is the "set kernel" statement, and line .L02 is the player0 statement. As long as you don't add any more (non-blank) lines above the player0 statement, the sprite data will still be at the address labeled playerL02_0, even if the location of playerL02_0 changes as you continue to edit your program. If the "tall sprite" that you're using to store all of the sprite shapes contains different shapes and they have different heights, you can assign constants for location of each individual shape and its height, as follows: rem * another example set kernel multisprite player0: %11111111 %00000000 %11111111 %11111111 %00000000 %11111111 %11111111 %11111111 end const shape1lo = <playerL02_0 const shape1hi = >playerL02_0 const shape1height = 2 rem * the height includes the 0 byte that precedes the actual shape data! const shape2lo = <playerL02_0+2 rem * +2 is for the height of any shapes that precede the second shape rem * in this case, only one shape precedes it, and its height is 2 const shape2hi = >playerL02_0+2 const shape2height = 3 const shape3lo = <playerL02_0+5 const shape3hi = >playerL02_0+5 const shape3height = 4 Of course, you'd probably want to give more meaningful names to the constants, such as enemyshiplo, enemyshiphi, enemyshipheight, explosionlo, explosionhi, explosionheight, boss1lo, boss1hi, boss1height, etc.
  21. A "value mismatch" message usually means that something in the program has changed location (address) from one pass of the assembler to another. This is normal, because it usually takes several passes for the assembler to determine the final addresses for everything in the program. You should check to see whether the ROM was created successfully despite the message, or whether the assembly was halted. I think the standard batari Basic compiler batch should contain some bits of code that will filter out this type of message, but I might be mistaken. And I remember there was a problem with different versions of the DASM assembler, but I don't remember if it had to do with this type of message or something else. The version of DASM that had the problem was 2.20.10, and the version that was okay was 2.20.07-- so make sure you're using the 2.20.07 version (I think the DASM.EXE file should have a "date modified" of 07/15/2003). Edit: I just realized I had left out the word "pointer" in my previous post, so I've edited the example code in that post to correct the omission.
  22. RetroFiends, you should look into using constants as described in my other post. If you put the sprite shapes in data tables and define constants for the lo and hi bytes of the addresses for the data tables, then you don't need to look in the assembly listing to see where batari Basic put the sprite shapes. However, if you use that method (data tables) then you might also want to learn how to tell the assembler to automatically position the data so no page-crossing occurs. That's another whole topic so I won't get into it here, but if you look at the assembly listing for your program then you can see it being used by batari Basic to make sure that sprite data and playfield data don't cross any page boundaries. As for whether sharing data between sprites is worth the extra hassle-- obviously it shouldn't make much difference if you're using only a few shapes anyway; but in the case of Random Terrain's Tone Toy it helped him save a lot of ROM, because otherwise he would have needed to have six copies of each shape (one copy for each of the six sprites), not to mention the extra code for all the if...then or on...goto statements to set the shapes as needed. So like any other technique, sometimes it's helpful but other times it isn't.
×
×
  • Create New...