Jump to content

Tursi

Members
  • Content Count

    7,205
  • Joined

  • Last visited

  • Days Won

    8

Everything posted by Tursi

  1. Be careful comparing the entire word of R1 when you use VSBR, only the MSB is altered. For instance, I ran this piece of test code: LI R1,>FFFF CLR R0 BLWP @VSBR Assuming VSBR gets >20 (space character from the screen), R1 becomes >20FF. A compare against >2000 would fail. It's easy to work around, though, if you have to compare the whole word, just CLR R1 before you call VSBR. That guarantees the whole word is zeroed. In a standard, 32k TI, and when using the Editor/Assembler utilities, you have 24k in the high bank, about 6k free in the low bank (by default from >2676 to >3f30, but that top address is decreased by 8 for every DEF you use - you can get the real values for your code by loading it, then in a debugger look at the two bytes at >2028 - that has the first free address, and the next two bytes (>202A) has the beginning of the REF/DEF table, which is 1 past the last free address). You also have 256 bytes of scratchpad, but much of that is reserved for the operating system and as a side effect the Editor/Assembler utilities, check the E/A manual to learn what areas are safe in there. And of course you have 16k of video RAM - assuming a layout with sprites overlapping characters like in Extended BASIC, all 256 characters available and graphics 0, you have more than 13k of that free. So technically, in total, you have up to 43k RAM available, though only the 24k block has absolutely no restrictions or complications. VMBR - VDP Multi Byte Read -- but that will only be useful for the horizontal tests, the vertical test will still need two single byte reads. VMBR returns the data to a buffer rather than to a register, although if you read only two bytes you could cheat and give the address of a register (this is one advantage of the TI's memory-to-memory architecture). R0 is the VDP address to read, R1 is the CPU address to store it at, R2 is the number of bytes to read. So for instance, to read two bytes into R1 as it sounds like you'd like to try, this works: LWPI >8300 * HAVE TO KNOW WHERE THE WORKSPACE IS TO DO THIS TRICK LI R0,32 * ADDRESS IN VDP LI R1,>8302 * ADDRESS OF R1 (WE CAN SAFELY OVERWRITE IT) LI R2,2 * HOW MANY BYTES TO READ BLWP @VMBR * TWO BYTES ARE NOW IN R1! In this case, both bytes of R1 are filled, with the first character in the MSB, and the second in the LSB. If only one pattern is valid, you could do a CI on the whole word. For instance, if both characters MUST be space (>20) to be allowed, just CI R1,>2020. But for the vertical checks you will need to do two reads, since you need to add 32 between the reads. The horizontal multi-byte read and multi-byte write take advantage of the fact that the VDP can autoincrement it's internal address, they don't need to set a new address each time.
  2. I wonder if we should ask Okay, here's a new version, I think I have all the bugs out and everything is working more or less as per the XB version. TIFARMERASM.zip OLD VERSION, SEE NEWER ONE BELOW I've also added a lot of comments to help me remember what each part was for, hehe, and I've tested every event. Still not very sure about the random number generator, but it seems to be working better now - events come up at least! Next task is to get ZomBXB up and running, too. I wonder if we should ask the guy. I guess it's not like we'll build a real manual but that is pretty sweet.
  3. While trying out Matthew's example, I realized that if we aren't running interrupts (as we discussed in the other thread), and we don't manually run a vertical blank routine, we won't have a continuously changing counter to use. But then I thought about the 9901. There's a 14-bit timer in there that ticks at roughly 46.785kHz, independent of the rest of the system. Not very much TI software uses it (indeed, I only learned how to from Karsten's Sudoku). Setting that up to count continuously, and reading the value as needed, may provide a nice injection of unpredictability, too. If you stopped for user input a lot, you could pretty much consider the number totally random, but otherwise, I thought it might be nice to use as the shift counter in Matthew's code. The timer loops every 349.2 milliseconds if set to maximum range, so for anything where accessing it is not fully deterministic timewise, you could almost use it raw. If you set it for a smaller value like 255 (or just mask), where it would wrap every 5ms and for code that interacts with the user between reads, would appear pretty darn random. Not 100% sure on this, but using it would be something like this (based on Thierry's code): INIT01 CLR R12 CRU base of the TMS9901 SBO 0 Enter timer mode LI R1,>3FFF Maximum value INCT R12 Address of bit 1 LDCR R1,14 Load value DECT R12 There is a faster way (see below) (Tursi - I didn't follow the faster way, unless it's LDCR for all 15 bits) SBZ 0 Exit clock mode, start decrementer B *R11 Return to caller If you BL @INIT01, then, the timer will start running through it's maximum range continuously. To get the value, you just need to do this: READ01 CLR R12 CRU base of the TMS9901 again SBO 0 Enter timer mode STCR R0,15 Read current value (plus mode bit) SRL R0,1 Get rid of mode bit SBZ 0 Exit clock mode, decrementer continues B *R11 Return - 14-bit counter value is in R0 So the BL @READ01 should return the current value of the counter into R0. Theirry's page says it doesn't halt the counter to read it, and since we didn't reset it, it should keep counting. Maybe an extra little bit of randomness, anyway! (edit) I've tested this code now, and it appears to work in Classic99 at least.
  4. I can't give MUCH help here, I've never worked with the SCSI or MFM controllers. The IDE device using Thierry's DSRs just mounts floppy disk images, so everything on it works like floppies do. Fred's DSR is a port of one of the others so I imagine works the same as it, though I can't say what it is. Most assembly language programs that read the floppy disk directory do so with sector access to sectors 2 and up, rather than opening the disk and reading it as a file. (Probably because sector access is easier to understand in assembly). I didn't need to add support to Classic99 for the file access mode of directories for a really long time. Of course that's completely non-portable coding, it only works with floppy disks and compatible devices. I hope someone can talk more on the hard drive, I'm curious myself.
  5. As far as I understand the 99/4A does do debouncing, using the delay routine that Sometimes99er posted. I've seen it run when you press a key in the Classic99 debugger. Fair point that my code does not do debounce, but for the purposes I'm using it (game input only) it should be fine, but you're right that I should try it on real hardware. In theory the only error from lack of debounce that you should see is repeated keys on a single keypress - the wrong key should not come up. Correct that the PS/2 keyboards do their own debounce internally, you don't need to worry about it from the other end of the cable. To see my KSCAN code just go grab the TI Farmer assembly source from the TI Farmer thread - all my XB support functions are in there, and Owen, just for you I left in the XB lines of code, so you can see how I translated them. Not that my approach is the only way, but I thought you might appreciate it.
  6. Regexp in macros would be nice, you could do some seriously powerful code generation then. I have some old stack-based math parsing code I wrote, supports order of operations, parentheses, constant folding, andfour function math plus exponents. It's tailored to generating Jaguar RISC assembly, but the concepts are close enough and it's designed to be portable. It's written in C, if that's useful.
  7. Looks like Marc came around to our side eventually. My point was exactly as Sometimes99er correctly emphasized (Thank you!). If you are using the interrupt as LIMI 2/LIMI 0, then it would have exactly the same effect to check the status byte yourself, and call your interrupt function yourself (thus bypassing the console ROM code). MAINLP LIMI 2 * This is the only place the console interrupt code could run anyway! LIMI 0 versus MAINLP MOV @VDPST,R0 * get the VDP status register COC @BLANK,R0 * test the vertical interrupt bit (which was reset by the read) JEQ NOBLANK BL @MYINT * it was set, so call our custom interrupt code NOBLANK * carry on here It's a little more code inline, but substantially less code executed, especially if you're only interested in your own function. As for the scratchpad usage, I found KSCAN inconvenient since it uses the lower part of the scratchpad (>8374,>8375). I wrote a really simple ASCII-only replacement for TI Farmer - by disabling ints and using my own KSCAN the whole scratchpad is now available to me. Note that doesn't HAVE to be a concern. If you store your variables carefully in scratchpad, or even in VDP, you need not worry about it. It's just another way to do things! Anyway, I don't mean to derail it, I'm just a bit slow to get back in the conversation. So back to the show!
  8. A function I've been using since the Dreamcast days is this one. It has a couple of interesting values. 1) it's relatively fast, taking very few opcodes 2) it's relatively random-ish (definately could be better, especially in the low bits. For small values I tend to shift it first) 3) it's non-repeating over the entire range. For whatever range you select (8 bits, 16 bits, 32 bits), each value is guaranteed to be selected exactly once. This makes it nice for pixel dissolves, apparently. It's definately not the best for being RANDOM, too many things are predictable, but it has worked quite well for me for a number of years. Here's the original post from the DCDev list. Of course, in 9900 it would be something like this (I'll use RAND16 from above) MASK DATA >B400 MOV @RAND16,R0 * Seed -> r0, seed must not be 0 (normally read the seed from memory somewhere) * Calculate next random number, use r0 as seed and put result in r0 SRL R0,1 * Shift r0 1 bit to the right, carry goes to C bit in status register JNC NOCARR * If carry was not set, jump to label nocarry XOR @MASK,R0 * calculate new random number with 0xb400 as mask NOCARR MOV R0,@RAND16 Again, just another simple routine depending on your needs. I know I'll be trying some of the others in here.
  9. (LAUGHS!) That is bloody AWESOME! Okay... I have some work to do.
  10. That's actually not so unusual! Many of Nintendo's NES titles run entirely from vertical blank (see the thread here on the Mario Bros source, for instance). However, I've been thinking about it since reading Matthew's post this morning. At first, I was thinking that I'd write a contrary view to give both sides, but after a lot of thought, and indeed my current experience with the TI Farmer port, I think I agree. For a game, you are better off disabling interrupts and not using any console functions. For one, it frees up the entire scratchpad RAM for you (at only 256 bytes, it's silly to 'reserve' parts of it, especially if you aren't using the ISR code and just wanted the interrupt itself). Since most TI games run with interrupts disabled, and do LIMI 2/LIMI 0 when they are ready to deal with the ISR, you can easily replace that with a check of the VDP status bit yourself.. if it's set, then a blank has occurred and you can process your end of screen details. If it's not, then there was nothing to do yet, carry on. You gain even more doing it this way in that the ISR function no longer wastes half your blanking time deciding whether to do anything. So although this is indeed a new stance for me, I also agree. ASM titles on the TI are better off NOT using the console interrupt code. The benefits far outweigh the small inconvenience of checking it yourself.
  11. Can't answer that one for you, CC. (Cue for someone from the Jag programming scene to step in...) Jag programmers are definately crazy. The Jag at the low hardware level is harder to program than the NES, but there are some nice libraries available now that make it easier. Since the hardware is more powerful than the NES, you can produce less-tuned code and still get a very good result. Even so, the Jag doesn't have any homebrews with the scope and scale of SMB or Zelda, either. I haven't done the NES yet, though I have a basic understanding of the hardware. I'd definately like to spend some time on it.
  12. hehe.. no escape, the name and storyline are MINE now! Mad Cows... well. Let me get it WORKING first, then we'll see about enhancements. I have to keep my time to a minimum, but I like the idea.
  13. (laughs) I think I can use that
  14. Thanks, Codex! I have a pretty good idea what it all does, I admire your tricks to reduce the size of string prints! At times the dense code made for easier assembly! The big issue that bit me was the option base 1, of all things. I decided when I did the port to go ahead and honor that, and reduce all the array indexes by 1, but I didn't remember to actually do that in all the formulas that used arrays, so I've been fixing bugs in that. That, and the double pointer dereference used for the string variables, I sometimes forgot about, hehe. Still - was nice to see it come up!
  15. We need a name for the combined version. In the meantime, here's some proof I've worked on it. TI Farmer runs (ZomBXB is not started yet, don't bother running it, it's just some KSCAN test code). I don't think the random events are coming up, and it's not erasing text from the lines correctly, but it doesn't crash and that's a lot of code today. It took about 3 hours to port to assembly, another 3-4 hours writing the subroutines and probably 4-5 hrs in debugging so far. ZomBXB should go faster since I know that code and the XB replacement code is done and tested for the most part. Find attached source code and a runnable cartridge image. As I noted, this version has some notable bugs, but it's how far I got so far. TIFARMERASM.zip (OLD VERSION - NEWER ONE BELOW) No screenshot - it looks exactly like the BASIC one. I think, too, it loses a little charm when the crops grow so quickly. But first we'll get it working, then we'll tune.
  16. Then I'll do it myself. When I said "it's always four sprites on a line", I meant that the MAXIMUM number of sprites on a scanline, in any mode supporting sprites, from any language, on the TMS9918A VDP, is four. You can obviously display fewer. You can not display more in a single frame. IF you have to, that is where flicker comes in useful, and I've already gone into detail on how that works. It's important to remember that it's not visible parts of the sprite that count (as is true on machines like the 2600)... the VDP looks at the sprite positions and makes its decisions about which sprites to draw before looking at the patterns. So the entire square sprite counts. Flicker is never mandatory. Again, I considered this obvious, but in case of confusion I will explicitly state it. Flicker is just a technique to allow the illusion of more sprites than are normally permitted by the hardware. (It can also be used for special effects). Apparently my speaking too generically caused some friction here, I apologize as that was not my intent. Back to the game discussion.
  17. The docs included with my conversion tool should cover the RLE format I used in that. It's simple but works well enough -- if I didn't include source code I can paste the code I used here. I don't know what Sometimes used in his RLE - there's no fixed standard for HOW to use RLE so everyone tends to be a little different.
  18. I agree - this is something I actually have a (relatively) current need for. The character boundaries are enough for my needs, but anything along those lines would be very handy.
  19. Excellent humour mate. Where do you get it from ? The answers are sort of wrong though. It's not always 4 sprites on a line. And you don't have to flicker. Ah, so this is the post you were talking about. I am trying very hard not to take offense given the other things you have said privately, since it is in the past, but I would like to see an explanation of that statement. I'm sorry if I'm stepping on your toes. I thought our posts were good complements to each other.
  20. Finally got a chance to read here.. InsaneMultitaker's option is the best for saving space - by putting a different copy loop in each bank, you only need to select the appropriate bank, and then each bank would have a different set of data. Also, of course, the character set copy code could be reduced by removing the 99/4 support -- I don't expect anyone is using a 99/4 in actual use given their limitations and value, but it was a challenge in my original code to make it work. I'm glad it's working though.
  21. Looks like good progress! The questions were sort of unanswered - it's always 4 sprites on a line. This is a hardware limitation so it doesn't matter what language you code it in. Besides the clever tricks Sometimes already mentioned, sometimes you have to flicker. There are two ways to do this. One is to order your sprites so that the ones that disappear (higher number) won't really impact the game play. They vanish, but they were just extra colors. The other is an actual flicker routine. The purpose of a flicker routine is to change the sprite numbers every frame so that if a sprite disappears, it will only be gone for one frame. There are a lot of ways to do this, but the approach I prefer is very simple and I haven't seen better results from any other. You can only get good flicker results from assembly or another compiled language, Extended BASIC won't be fast enough and can't detect the frame sync. The first step is to determine how many sprites you need. The fewer sprites that flicker, the faster the flicker will be and the better it will look. For instance, let's say you want 16 sprites onscreen in your game. The second step is to arrange your sprites. Assume 0-15 for now. Space out your multi-colored sprites so the different colors are 4 or more sprites apart - this helps to ensure some part of the character is always onscreen. Finally, define your sprite table in CPU RAM - this is a mirror of the VDP sprite table. Do all your sprite updates there (this gives you a small speed boost, too, which is nice, and even a full 32-sprite table is only 128 bytes). Have an interrupt routine that runs during vertical blank. This interrupt routine will copy the sprite table to VDP RAM, starting at a different offset each frame. This way, your program still sees the sprites as 0-15, but the VDP will see different numbers each time, so different sprites are the 'highest' number and disappear on different frames. Persistence of vision does the rest, and you see the sprite as just 'flickering'. This routine is also nice because you don't have to care whether any sprites are on the same line and need flickering or not - because you rotate the table every frame, the flickering just happens automagically when it's needed. For example, let's say there are 8 sprites, and we have them all on one line. I will say they are the first 8 letters of the alphabet. When we first run the program, and we copy them to VDP, the VDP Sprite Table will see them like so: A B C D E F G H Because we can only have 4 sprites on a line, though, on screen we will see this: A B C D The next frame, we increment our start pointer by 4. We copy from there to the end of the table, then, knowing we aren't done, we copy from the beginning of the table to the start pointer. In essence, the CPU sprite table becomes a ring buffer. We increment by 4, because that's how many sprites are allowed on a line. So this time, the VDP Sprite table looks like this: E F G H A B C D but the actual sprite information has not changed, only the order in the table. Because of this, on screen we will see this: E F G H When the next frame comes, we again increment by 4. We notice we are off the end of the table (which was 8 entries long), and reset to 0, which will give us the first case again. This makes the sprites flicker 4 at a time (which is the worst case), and since there are only two frames before it repeats, the worst case is 1 in 2 frames. If you had 16 sprites, then it would take 4 frames to repeat, so the worst case is 1 in 4 frames, which is still tolerable. If you use all 32 sprites, the worst case becomes 1 in 8 frames, which is not very tolerable, but since it's usually not too frequent, it's okay. The pseudocode would look like this. I'll do it in Extended BASIC, and you can actually run it. You'll see how it works then, but remember to actually "flicker" you have to run it every frame, at that speed it looks good. (Even as is, you will probably want to speed up the emulator - with Classic99 use CPU Overdrive ). With the XB copy, it's slow enough that you can see the updates as they happen - this causes characters that shouldn't flicker to do so slightly as they are moved, and sometimes the wrong character is briefly visible. In assembly you do this during the vertical blank, so it's never visible. This code lets you move the 'A' with joystick 1, so you can see how you have to update the mirrored table and not the VDP one, but that it still works. You can also see how that affects the actual flicker, but it is very slow. Run it at normal speed first, then kick in CPU Overdrive (or in MESS, whatever the speed up mode there is), and you can get a better visualization of the flicker itself. 100 REM SPRITE FLICKER SAMPLE 110 REM SHOWS (SLOWLY!) A FLICKER 120 REM OF 12 SPRITES ON ONE LINE 130 REM MOVE THE 'A' WITH JOYSTICK 1 140 REM 150 REM THIS IS THE SPRITE TABLE 160 REM (ROW, COLUMN, CHAR, COLOR) 170 DIM ROW(12),COL(12),CH(12),HUE(12) 180 INDEX=1 :: REM WHICH SPRITE IS COPIED FIRST 190 REM 200 REM SET UP THE SPRITES 210 FOR I=1 TO 12 220 ROW(I)=100 :: COL(I)=I*12 :: CH(I)=64+I :: HUE(I)=1+I 230 NEXT I 240 CALL CLEAR :: CALL SCREEN(15) 250 REM 260 REM MAIN LOOP 270 GOSUB 350 :: REM COPY SPRITE TABLE TO VDP 280 CALL JOYST(1,X,Y):: REM READ JOYSTICK 290 REM NOTE WE HAVE TO UPDATE THE MIRROR, NOT THE 300 REM REAL TABLE, SO WE CAN'T USE AUTO-MOTION 310 ROW(1)=(ROW(1)-Y)AND 255 :: REM THE AND HELPS IT WRAP AROUND 320 COL(1)=(COL(1)+X)AND 255 :: REM THE AND HELPS IT WRAP AROUND 330 GOTO 270 340 REM 350 REM COPY SPRITE TABLE 360 REM YOU WOULD NORMALLY DO THIS IN ASSEMBLY 370 REM IN A SINGLE COPY OF A MIRRORED SPRITE 380 REM TABLE, BUT HERE WE HAVE TO DO IT 390 REM THE SLOW WAY. AT LEAST YOU CAN SEE 400 REM WHAT IT DOES! 410 REM COPY FROM INDEX 420 VDPINDEX=1 430 FOR I=INDEX TO 12 440 CALL SPRITE(#VDPINDEX,CH(I),HUE(I),ROW(I),COL(I)) 450 VDPINDEX=VDPINDEX+1 460 NEXT I 470 REM COPY FROM START 480 FOR I=1 TO INDEX-1 490 CALL SPRITE(#VDPINDEX,CH(I),HUE(I),ROW(I),COL(I)) 500 VDPINDEX=VDPINDEX+1 510 NEXT I 520 INDEX=INDEX+4 :: IF INDEX>12 THEN INDEX=1 530 RETURN
  22. My converter (http://www.harmlesslion.com/cgi-bin/onesoft.cgi?102) has got a lot of time invested to making the images look good, also have a look at my document about it, available here: http://www.harmlesslion.com/text/Modern%20Graphics%20on%20the%209918.pdf My tool uses perceptual color matching as well as Floyd-Steinberg dithering, though you can change the dither pattern yourself, as well as tweaking the resize filter and turning various optional settings on and off, even 'nudge' the image to work around the horizontal blocking if you like. It's about as plug-and-play as it gets - to convert an image, just drag it to the window. It resizes automatically (and has several options for dealing with images with a non-4:3 aspect ratio). It even has a secret 'random slideshow' mode... double-click on the empty window frame, and the 'Open' button becomes 'Next'. Click it, and select any file in the folder you want to slideshow. Each time you click 'next', it will randomly choose a file from that folder and convery it. As for the output format, the standard most widely used is "TI Artist format", which is two separate files, one containing a raw 6k dump of the color table, one containing a raw 6k dump of the pattern table. It's assumed in this format that the SIT contains the usual arrangement of three sets of characters 0-255. If you name them with the extensions TIAP and TIAC, I actually have a Windows-based Photoshop filter that can read (not write) the files (I use it with ThumbsPlus for organizing the hundreds of images I have converted and kept with my tool) here: http://www.harmlesslion.com/cgi-bin/onesoft.cgi?112 Since there's no requirement for which comes first in VDP (color or pattern), and because there is 2k of unused space between them, and because the normal TI image file loaders manage 8k blocks at a time, two files was probably a reasonable compromise. Personally I prefer to keep existing formats unless they actually don't work, than to introduce new formats at such a late stage, but I seem to be in the minority there. My converter can also RLC compress the tables, though I've more used this in my own software. As an example, here's what my tool with the "Perc"eptual option and default filter does with the sample pic from the top: (note that my tool scales the image 2:1 for display on the screen, that's why the pixels are large, but it is 256x192 )
  23. I'm kind of interested, actually. I don't have one, but I've been holding off getting one to inspire myself to build my PC interface already (it's amazing how much you can do without such a device when you're putting off harder work ). On the other hand, if nobody else claims it, I can probably use it for the faire. (Which I still need to make sure I can get to...)
  24. I finally got a chance to check this thread out, and it's looking fan-tastic! I'll definately chip in on publishing costs, this must come out!
×
×
  • Create New...