######## ################## ###### ###### ##### ##### #### #### ## ##### #### #### #### #### #### ##### ##### ## ## #### ## ## ## ### ## #### ## ## ## ##### ######## ## ## ## ##### ## ## ## ## ## ##### ## ## ######## ## ## ## ### ## ## #### ## ## ##### #### #### #### #### ##### #### #### #### #### #### ###### ##### ## ###### ###### Issue #10 ################## June 30, 1995 ######## ---------------------------------------------------------------------(v1.5)--- Editor's Notes by Craig Taylor This is my last issue of Commodore Hacking (having finally gotten out the door, but I couldn't break tradition and get it out on time :-) ). I'm having to give it up because I've gradually lost interest in Commodore computers over the years and with the search for a job (anyone wanna hire a csc graduate?) and as I get older I seem to have less and less time. I'm gonna be handing the reigns of Commodore Hacking over to Jim Brain, who is a very active member of the Commodore Internet community. He will also be running a mailserver that will take the place of mine (Mine will become unavailable after July 1st and will send pointers to Jim Brain's mailserver). It's been interesting to watch the Commodore computers evolve, take off like a rocket and then have Commodore go into liquidation. Commodore computers have been and still are, (with some exceptions - 1541 head-banging comes to mind), technologically sound. For a "hacking" machine they're wonderful. My email address has changed to duck@nando.net. I periodically still check mail at duck@pembvax1.pembroke.edu but only every 2 weeks or so. I am still going to try to be in the Commodore community but time will govern my ability to do that. I'm going to miss editing this rag.... And here is Jim Brain: Mail Server Changes: With Issue 10, the address for the Commodore Hacking mail server has changed. The new address is brain@mail.msen.com The commands are the same as before. Not all of the files have been moved yet, so please email the administrator (Jim Brain, brain@mail.msen.com) if a file you need is not on the new site. Howdy: Howdy, my name is Jim Brain, and I will be taking over the position of editor for Commodore Hacking starting with Issue 11. Some of you may know me as the Commodore Trivia Contest administrator, the USENET newsgroup comp.sys.cbm FAQ Manitainer, or the keeper of a Commodore Information WWW Site at http://www.msen.com/~brain/cbmhome.html. Wherever you have heard of me from, or even if you haven't, I will state that I plan on handling Commodore Hacking in the following way. The next issue will possibly look different cosmetically, as I edit somewhat differently than Craig, but the content and basic layout will remain the same. The types of material will not change, and the structure for submitting articles will change only in the address to mail them to: brain@mail.msen.com. However, I do have a few changes in mind: 1) Try to stabilize the issue generation so that Commodore Hacking will become a quarterly publication. 2) Attempt a fully "HTML"ized version of the magazine, while still providing a text version. 3) Pursue the possibility of providing a printed version of these issues for those who have no online access to them. 4) Encourage User Groups and other CBM related organizations to carry this magazine for their members. So, again I say howdy. As always, Commodore Hacking will accept your articles at any time, so please help us keep this quality magazine running. If you have any questions or comments about the change in editorship, the possible changes, or other matters, please feel free to drop me a note. I look forward to hearing from you and publishing your articles. Jim Brain brain@mail.msen.com =========================================================================== Legal Mumbo-Jumbo Permission is granted to re-distribute this "net-magazine", in whole, freely for non-profit use. However, please contact individual authors for permission to publish or re-distribute articles separately. A charge of no greater than 5 US dollars or equivlent may be charged for library service / diskettte costs for this "net-magazine". ------------------------------------------------------------------------------- Please note that this issue and prior ones are available via anonymous FTP from ccnga.uwaterloo.ca (among others) under /pub/cbm/hacking.mag and via a mailserver which documentation can be obtained by sending mail to "brain@mail.msen.com" with a subject line of "mailserver" and the lines of "help" and "catalog" in the body of the message. ------------------------------------------------------------------------------- In This Issue: Commodore Trivia Trivia Edition #13-18 are in this article. As you may know, these questions form part of a contest in which the monthly winner gets a prize (Thanks to my various prize donators). The whole thing is mainly just for fun, so please enjoy. Try your hand at Commodore trivia!! BFLI - New graphics modes 2 FLI gave us more color to the screen, AFLI increased the horizontal resolution and color selection by using the hires mode. BFLI stands for 'Big FLI' and gives us 400 lines instead of the usual two hundred. AFLI and BFLI can be combined, but we are not going into that. Making stable raster routines (C64 and VIC-20) In this article, I document two methods of creating stable raster routines on Commodore computers. The principles apply for most 8-bit computers, not only Commodores, but raster effects are very rarely seen on other computers. A Differant Perspective - Part III. Yes!!! It's yet another article on 3D graphics! Even if you haven't been following this series, you can use this program. This time around we will write a completely general polygon plotter -- if you can type basic data statements, you can create a three-dimensional object out of polygons and rotate and project it to your heart's content. For the more technically inclined we will look at optimizations to the line routine, EOR-buffer filling, and more! Yow! Second SID Chip Installation This article describes how to add a second sid chip for use in SidPlayer and other programs. As always, be extra careful when making modifications to your computer. Solving Large Systems of Linear Equations on a C64 Without Memory OK, now that I have your attention, I lied. You can't solve dense linear systems of equations by direct methods without using memory to store the problem data. However, I'll come back to this memory free assertion later. The main purpose of this article is to rescue a usefull numerical algorithm, "Quartersolve", and also to provide a brief look at the COMAL programming language and BLAS routines. The World of IRC - A New Life for the C64/128 I've heard people talking about IRC. What is it? Why is it useful to me as a Commodore user? Bill "Coolhand" Lueck explains the hows and whys in this article. SwiftLink-232 Application Notes (version 1.0b) This information is made available from a paper document published by CMD, with CMD's permission. Design and Implementation of a Simple/Efficient Upload/Download Protocol This article details how to implement a custom upload/download protocol that is faster than most of the ones common to the C64/128 computers. Design and Implementation of a 'Real' Operating System for the 128: Part II There has been a slight change in plans. I originally intended this article to give the design of a theoretical distributed multitasking microkernel operating systemfor the C128. I have decided to go a different route: to take out the distributed component for now and implement a real multitasking microkernel OS for a single machine and extend the system to be distributed later. The implementation so far is, of course, only in the prototype stage and the application for it is only a demo. Part III of this series will extend this demo system into, perhaps, a usable distributed operating system. ======================================================================== Trivia by Jim Brain (brain@mail.msen.com) Well, summer is upon the Brain household, and things are moving at a fast clip at the house. However, the trivia still keeps coming. I appreciate all the people who contribute to the trivia and all the people who take part in the monthly contest. I have collected Trivia Edition #13-18 in this article. As you may know, these questions form part of a contest in which the monthly winner gets a prize (Thanks to my various prize donators). The whole thing is mainly just for fun, so please enjoy. As the summer months start up, news on the trivia includes: 1) I now have access to some more orphan machines (C65, C116), so expect some trivia questions on those models. 2) The new home now has a number of machines set up, so testing answers to the trivia is even easier. I am still trying to get the old PET machines in house, but the others are here. 3) The Commodore World Wide Web Pages (http://www.msen.com/~brain/cbmhome.html) that I maintain and place the trivia on caught the eye of USA Today and the Pheonix Gazette. I was interviewed for both articles. Look in the June 20th edition of USA Today for the segment, and possibly a picture of Jim Brain and the machines he uses to create the trivia. As always, I welcome any questions (with answers), and encourage people to enter their responses to the trivia, now at #18. Jim. The following article contains the answers to the December edition of trivia ($0C0 - $0CF), the questions and answers for January ($0D0 - $0DF), February ($0E0 - $0EF), March ($0F0 - $0FF), April ($100 - $10F), and the questions for the May edition ($110 - $11F). Enjoy them! Here are the answers to Commodore Trivia Edition #13 for December, 1994 Q $0C0) The early 1541 drives used a mechanism developed by ______. Name the company. A $0C0) Alps. Q $0C1) On later models, Commodore subsequently changed manufacturers for the 1541 drive mechanism. Name the new manufacturer. A $0C1) Newtronics. Q $0C2) What is the most obvious difference(s). (Only one difference is necessary) A $0C2) Alps: push-type latch, round LED. Newtronics: lever-type latch, rectangular LED. Q $0C3) On Commodore BASIC V2.0, what answer does the following give: PRINT (SQR(9)=3) A $0C3) 0. According to Commodore BASIC, the answer should bby -1, which is the BASIC value of TRUE. However, the above equation is NOT true. Doing PRINT SQR(9) yields 3, but doing PRINT (SQR(9)-3) yields 9.31322575E-10 (C64). This anomaly can be attributed to roundoff errors in the floating point math routines in Commodore BASIC. Q $0C4) In Commodore BASIC (Any version) what does B equal after the following runs: C=0:B=C=0 A $0C4) B = -1. The second statement is the one to look at. The second equals sign is treated as a comparison, while the first is treated as a assignment. B gets set to the outcome of the comparison, which is TRUE (-1). Q $0C5) The first PET cassette decks were actually _______ brand cassette players, modified for the PET computers. Name the comapny. A $0C5) Sanyo. Specifically, Model M1540A. What a model number! Q $0C6) In Commodore BASIC (Any version), what happens if the following program is run: 10 J=0 20 IF J=0 GO TO 40 30 PRINT "J<>0" 40 PRINT "J=0" A $0C6) On BASIC 2.0 or greater: ?SYNTAX ERROR IN 20 READY. On BASIC 1.0: (found on the PET 2001 series) J=0 READY. BASIC 1.0 totally ignored spaces, so line 20 became "IFJ=0GOTO40". That statement would be correctly parsed, sicne it contains the "GOTO" keyword. However, on BASIC 2.0 or greater, spaces weren't ignored so completely, and the "TO" in "GO TO" would be tokenized separately, so some code was added to BASIC to check to "GO". As the code that accepts GOTO as a special case for THEN after an IF statement wasn't patched this way, the above fails, because GO is not a valid keyword after IF. The statement SHOULD work correctly, but does not because of this failure to fix the IF command parsing. On BASIC 2.0 or greater, substituting the following line for line 20 will cause the program to work: 20 IF J=0 THEN GO TO 40 Q $0C7) In question $068, we learned how Jack Tramiel first happened upon the name "COMMODORE". According to the story, though, in what country was he in when he first saw it? A $0C7) Germany. Q $0C8) On the Commodore user port connector, how many edge contacts are there? A $0C8) 24. Two rows of 12 contacts each. Q $0C9) On most Commodore computers, a logical BASIC screen line can contain up to 80 characters. On what Commodore computer(s) is this not true? A $0C9) According to Commodore documentation, a _physical_ screen line is defined as one screen line of characters. A _logical_ screen line is defined as how many _physical_ lines can be chained together to create a valid BASIC program line. With that in mind, most Commodore computers chose a _logical_ screen line that was a multiple of the screen width. This works fine for 40 and 80 column screens, but what do we do with the VIC-20, with its 22 column screen. Solution: make the _logical_ line length equal to 4 _physical_ lines, or 88 columns. When the Commdore 128 was introduced, the number rose to 160 characters, which is 4 _physical_ lines in 40 column mode, or 2 _physical_ lines in 80 column mode. However, you can only take advantage of this in 128 mode. 64 mode is limited to 80 characters. To add to all this confusion, a valid BASIC program line (in memory) can actually be 255 (tokenized) characters long, but creating such a long line cannot be done from the built-in editor in direct mode. The AmigaBASIC, available on the Amiga, also does not have the 80 column line limit. However, that BASIC is SOOO much different that I am not surprised. The older CBM BASICs, on the other hand, were all derivatives of the original Level 1 BASIC for the PET. Q $0CA) If a file is saved to a Commodore Disk Drive with the following characters: chr$(65);chr$(160);chr$(66), what will the directory entry look like? A $0CA) The filename will show up as "A"B, with the 'B' showing up to the right of the '"' mark. This could be used to make program loading easier. A file that showed up as "filename",8,1 could be loaded by simply hitting shift-run/stop on that line. Q $0CB) What is the maximum length (in characters) of a CBM datasette filename? A $0CB) References I have on hand say 128 characters. However, the actual code on the 8032 and the C64 acts as though 187 characters can actually be sent (tape buffer-5 control bytes = 192-5=187). The references that claim 128 characters are Nick Hampshire's _The VIC Revealed_ and _The PET Revealed_. ANyone care to lay this one to rest? Q $0CC) How many keys are on a stock Commodore 64 keyboard? A $0CC) 66 keys. This is the same number as found on the VIC-20 and the Commodore 16. Q $0CD) Commodore BASIC uses keyword "tokens" to save program space. Token 129 becomes "FOR". What two tokens expand to include a left parenthesis as well as a BASIC keyword? A $0CD) TAB( (163) and SPC( (166). Q $0CE) There are 6 wires in the Commodore serial bus. Name the 6 wires. A $0CE) 1) Serial /SRQIN 2) GND 3) Serial ATN IN/OUT 4) Serial CLK IN/OUT 5) Serial DATA IN/OUT 6) /RESET Q $0CF) On the Commodore datasette connector, how many logical connections are there? A $0CF) 6. Opposing pins on the connector are hooked together electrically. Here are the answers to Commodore Trivia Edition #14 for January, 1995 Q $0D0) How many keys were there on the "original" PET and what was special about them? A $0D0) the original PET had 73 calculator-style keys that were laid out in a rectangular matrix, not typewriter-style. Q $0D1) How do you produce the "hidden" message(s) on the Commodore 128? A $0D1) SYS 32800,123,45,6. The screen will clear, and the software and hardware developers on the 128 project will be named. The exact text is as follows: [RVS] Brought to you by... Software: Fred Bowen Terry Ryan Von Ertwine Herdware: Bil Herd Dave Haynie Frank Palaia [RVS]Link arms,don't make them. Q $0D2) How much memory did the "original" PET show on bootup? A $0D2) The "original" PET came in two configurations, 4K and 8K, so: The PET 2001-4 had 3071 bytes. The PET 2001-8 had 7167 bytes. Q $0D3) We all know the "reboot" sys for the 64 is sys 64738, but who knows the same sys location to reboot the CBM 8032? A $0D3) sys 64790 Q $0D4) Which computer(s) beeped at bootup? (May be more than one, but only one required) A $0D4) I know some of these are corect, but the sheer size of the list prevents me from checking them ALL out. FAT 40XX series 80XX series PC-10 (I suspect a number of IBM clones did, and these things have no consistent naming convention across country boundaries.) PC-20 Amiga 1000 SP9000 (SuperPET) Q $0D5) How much memory did the CBM 8032 show on bootup? A $0D5) 31743 bytes. Q $0D6) Certain Commodore computers provided emtpy EPROM sockets on the motherboard. Give me the number of empty sockets on the following machines: a) CBM 30XX. b) CBM 8XXX. c) CBM C128. d) Plus/4. A $0D6) a) 3 sockets. b) 2 sockets. c) 1 socket. d) 1 socket. Q $0D7) In Germany, the CBM 8032 came with a 4kB EPROM for the EXXX area, while the US version only had a 2kB EPROM. Why? A $0D7) The German version had additional keybaord drivers for umlaut characters and dead keys. Q $0D8) Who published the first PET memory map in the "PET Gazette"? A $0D8) None other than the infamous Jim Butterfield. Q $0D9) Which is faster to move the sursor on a PET/CBM or C64: SYS or PRINT? A $0D9) PRINT is faster, since the sys approach must process the pokes before the sys, which are very slow. Q $0DA) On the Amiga 1000, where are the signatures of the first Amiga developers located? A $0DA) Inside the top case of the Amiga (1000). There is an interesting footnote to this question. It seems that at least some original Amiga machines were labeled as Amiga (with nu number). Then, at some later point, the number was added. In addition, Commodore produced some Amiga 1000 machines without the signatures, but most had the telltale handwriting on the inside of the case. Q $0DB) On the 6502, what does the accumulator contain after the following is executed: lda #$aa sed adc #01 A $0DB) Assume carry was clear. If so, then $11 is the correct answer. Q $0DC) What is the model number of the US NTSC VIC-II chip? A $0DC) Its first number was 6567, and that is the number most people know it by, but Commodore produced a VIC-II using a new manufacturing process that was numbered the 8562. Q $0DD) What is the European PAL VIC-II chip's model number? (Not sure if that's its rightful term, but I hope you understand). A $0DD) Same here. The part number 6569 is the most remembered number, but an 8565 will work as well. Q $0DE) Assume you have two computers, one with each of the above chips inside. Which chip draws more pixels on the screen per second? A $0DE) Note, for the purposes of the calculation I am performing, "pixels" refers to picture elements that can be adddress and modified using normal VIC modes, so there are 320*200 "pixels" on both the PAL and NTSC screens. (I probably should have stated this, but it is too late now.) Also, the screen refresh rates used in the calculations are those defined by the respective television standards (60Hz U.S., 50Hz European), even though the actual frequencies are off by a small percentage. (for example, the actual 50Hz refresh rate on European VIC-II chips was calculates as 50.124567Hz by Andreas Boose) So, the PAL draws 320*200*50 pixels per second = 3200000 pixels/s NTSC draws 320*200*60 pixels per second = 3840000 pixles/s Now, some people thought I meant the whole screen, not just the display area provided by the VIC-II chip. Well, I am not sure exactly you calculate pixels on a screen, since the numbers could vary from display to display, but if we measure in scanlines: PAL = 312 scanlines * 50 = 15600 scanlines/s NTSC = 262 scanlines * 60 = 15720 scanlines/s The NTSC machines wins both ways. Q $0DF) In Commodore BASIC, which statement executes faster: a = 2--2 or a = 2+2 A $0DF) b is the correct answer, and there are a couple of reasons why: 1) 2--2 takes longer to parse in the BASIC interpreter. 2) Commodore BASIC subtracts by complementing the sign of the second number and adding. This incurs extra time. There are even more subtle ones, but I leave them as an exercise for the reader. Send me your reason why. Here are the answers to Commodore Trivia Edition #15 for February, 1995 Q $0E0) What is the difference(s) between the Newtronics 1541 and the 1541C? (only one difference is needed) A $0E0) (George Page, a noted authority on CBM Drives, indicated that Commodore made this a tough question to answer.) By the time the 1541C was introduced, Commodore threw a number of drives together and called them 1541Cs. The theoretical 1541C exhibited the following features: No head banging, and other problems fixed by modified ROMs. Case color matches C64C and C128 computers. Q $0E1) What happens when you type 35072121 in direct mode on the C64 and hit return? A $0E1) Simple answer: Most likely, the screen clears and the word READY. is printed at screen top. This is the behavior seen when pressing RUN-STOP/RESTORE. Alternately, nothing could happen, or the computer could lock up. Involved answer: There is a bug in BASIC 2.0. Easily fixed, but destined to live life immortal. (long) The bug is in the PETSCII number to binary conversion routine at $a69b (LINGET). The routine basically reads in a character from the line, multiplies a partial result by 10 and adds the new character to the partial result. Here is a code snippet: a96a rts a96b ldx #$00 ; zero out partial result a96d stx $14 a96f stx $15 a971 bcs $a96a ; not a number, return a973 sbc #$2f ; PETSCII to binary a975 sta $07 a977 lda $15 ; get hi byte or partial result a979 sta $22 a97b cmp #$19 ; partial > 6399 a97d bcs $a953 ; yes, goto error a97f lda $14 ; load lo byte of result a981 asl ; lo*2 a982 rol $22 ; hi*2 + c a984 asl ; lo*2 a985 rol $22 ; hi*2 + c a987 adc $14 ; complete lo*5 a989 sta $14 a98b lda $22 a98d adc $15 ; complete hi*5 a98f sta $15 a991 asl $14 ; lo*2 complete lo*10 a993 rol $15 ; hi*2 complete hi*10 a995 lda $14 a997 adc $07 ; add new char a999 sta $14 a99b bcc $a99f ; did lo overflow? a99d inc $15 ; yes, inc hi a99f jsr $0073 ; get next char a9a2 jmp $a971 ; go through it again. The problem is at $a97d. when the partial result is greater than 6399, (if partial > 6399, then new partial result will be over 63999) the routine needs to get to $af08 to print an error, but can't due to branch restrictions. However, a branch that will get there is in the preceding function, which handles the ON GOTO/GOSUB keywords ($a94b, ONGOTO). So, the BASIC writers just branched to the code in ONGOTO; specifically $a953: a94b jsr $b79e a94e pha a94f cmp #$8d ; is the keyword GOSUB ($8d) a951 beq $a957 ; yes a953 cmp #$89 ; is the keyword GOTO ($89) a955 bne $a8e8 ; no, print SYNTAX ERROR. a957 ... ; handle ON GOTO/GOSUB This code is checking to make sure the ON (var) is followed with a GOTO or GOSUB keyword. The LINGET error handler branches to $a953, which compares .A (which holds hi byte of partial result) to $89. Normally, this fails, and the normal SYNTAX ERROR code is reached through the branch to $a8e8. However, for partial results of the form $89XX, the check succeeds, and BASIC tries to execute an ON GOTO/GOSUB call. By the way, it is no coincidence that this error occurs on 35072121, since one of the partial results is $8900 (hi byte is $89). In fact, 350721 will achieve the same result. If the check succeeds, the code limps along until $a96a: a969 pla ; complement to $a94e a96a rts ; return But we never executed $a94e, the push, so the stack is now messed up. Since the stack held $9e, $79, $a5 before the PLA, (The stack could hold other values, but I always saw these) the RTS gets address $a579 to return to, which usually holds a BRK opcode. The break handler is invoked, and the screen clears with the READY. at the top. Now, the BASIC 2.0 authors were justified in reusing the error handler code in ONGOTO for LINGET, but they calculated the branch offset wrong, according to my tests. If you have the LINGET error handler branch to $a955, all these troubles disappear. You can verify this procedure with the following BASIC program on a 64: 10 for t=57344 to 65535:poke t,peek(t):next 20 for t=40960 to 49151:poke t,peek(t):next 30 poke 43390, 214 40 poke 1, peek(1) and 254 Just to be complete, this error occurs when a 6 digit or greater line number is entered and the first 6 digits indicate a number in the range 35072-35327 ($8900-$89ff). Also, it appears the error occurs on the VIC-20, but I didn't completely verify it. It would be interesting to note if the error is found on all version of CBM BASIC. Whew, what a mouthful. Q $0E2) If a SID chip is producing a "sawtooth waveform", does the waveform look like: a) "/|/|/|/|" or b) "|\|\|\|\" ? A $0E2) a is the correct answer. Q $0E3) On BASIC 2.0, what special precaution(s) must one take when working with relative files? (only one is needed) A $0E3) Because BASIC 2.0 doesn't handle positioning in relative files quite right, one must position the relative file pointer before AND AFTER a read or write to a relative file. Q $0E4) What incompatibility existed between C128 Rev. 0 ROMS and the REU? A $0E4) OK, I admit it. I placed this answer and its discussion somewhere in my store of information, and it must have fallen behind the cabinet, because I cannot find it. I will post an answer to this as soon as I can find it, but the answers really must go out, as they have been held up long enough. Q $0E5) What can trigger an NMI interrupt? (count all sources on one chip as one) A $0E5) The following sources can trigger an NMI interrupt: 1) The expansion port. 2) CIA #2. 3) The RESTORE key. Q $0E6) What can trigger an IRQ interrupt? (count all sources on one chip as one) A $0E6) The following sources can trigger an IRQ interrupt: 1) The VIC-II chip. 2) CIA #1. 3) The expansion port. Q $0E7) Where is the ROM in a 1541 located in the 64K memory map? A $0E7) The ROM is located from $C000 to $FFFF, yet the ROM code does not begin until $C100. Q $0E8) Which VIA on the 1541 is hooked to the read/write head? A $0E8) VIA #2, located in memory from $1C00 to $1C0E. Q $0E9) In the Commodore DOS, what bit in the file type byte denotes a "locked" file? A $0E9) bit 6. Q $0EA) If files are "locked" under Commodore DOS, under what condition(s) may the file be changed? A $0EA) Depending on the file, the following operations can be done on a locked file: 1) Rename will change file name, although not contents of file. 2) Random access can be used to alter file. 3) Formatting the disk will alter the file. (duh!) 4) Save-with-replace (@0:) will replace file and unlock it. 5) Opening file in append mode will allow it to be changed, and unlock it. 6) Opening a relative file and adding or changing a record will succeed and unlock file. Q $0EB) How big can a program file be on a 1541 or similar? A $0EB) The file can be as large as a sequential file, since both are stored in the same way: 168656 bytes. However, since a program contains its load address as bytes 0 and 1, the largest program size is 168654 bytes. Q $0EC) Under BASIC 2.0, how does one open a random access file on a disk drive? A $0EC) Random access (or direct access) files are a misnomer. What you really doing is opening the disk for reading and writing. You need two open command to access a random file: (assume drive 8) open 15,8,15 and open 1,8,4,"#1" will open a random access file using buffer 1. open 1,8,4,"#" will open a random access file using the first available buffer Now, by using B-R, B-W, B-A or their replacements, you can write data to sectors on the disk. Note that Random access files are different from relative files. Q $0ED) A file that has a '*' immediately before the filetype is called a _________ file. A $0ED) a splat file. This is its correct term, believe it or not. Q $0EE) We know the 1541 and similar drives have 5 internal buffer areas, but how many does an 8050 drive have? A $0EE) Since the 8050 has twice the on-board RAM (4kB), it has 16 buffers, but only 13 are available. (All CBM drives use one buffer for zero-page memory, one for stack memory, and one for temporary variables.) Q $0EF) On a "save-with-replace", where is the location of the first track and sector of the new copy of the program saved in the directory entry for the old copy? A $0EF) The new first track is stored at location 26, and the new first sector is stored at location 27. These values are copied to their correct locations after the save is completed. Here are the answers to Commodore Trivia Edition #16 for March, 1995 Q $0F0) What size matrix of pixels comprises a character on a PET 2001 computer? A $0F0) The matrix was 8 by 8. Q $0F1) How many bytes did the opening screen on a CBM 4016 show as available for use by BASIC? A $0F1) 15359 bytes free. Q $0F2) The character set that produces uppercase letters on unshifted keys is the ________________ character set. A $0F2) "standard mode". Q $0F3) The character set that produces lowercase letters on unshifted keys is the ________________ character set. A $0F3) "alternate mode" Q $0F4) To get to the set mentioned in $F2, what character code would be printed to the screen? A $0F4) chr$(142) Q $0F5) What character code would one print to the screen to invoke the chararacter set in $F3? A $0F5) chr$(14) Q $0F6) If one does LIST 60-100, will line 100 get "listed"? A $0F6) Yes. The above translates as: LIST 60 through to and including 100. Q $0F7) The abbreviation for the BASIC 4.0 command "COLLECT" is ________. A $0F7) coL. "C" "O" "SHIFT-L". For those who are interested, the COLLECT command is analogous to the VALIDATE operation. Q $0F8) When you use a subscripted variable in BASIC, how many elements are created by default if no DIM statement is issued? A $0F8) 11 elements. A(0) - A(10). Almost everyone who has ever programmed in Commodore BASIC has seen the "BAD SUBSCRIPT" error when they try to use the 12th element in a un-DIMensioned array. Q $0F9) How large is the keyboard buffer in CBM computers? A $0F9) 10 bytes. Since this area could be POKEd to, many boot programs would poke characters into this buffer to simulate keypresses. Q $0FA) On the Commodore 1581, how large is a physical sector in bytes? A $0FA) A physical sector is 512 bytes in length. Internally, the 1581 creates 2 256 "logical" sectors in a physical sector, to maintain compatibility with older Commodore drives. Q $0FB) You'll find BASIC 3.5 on the _____________ line of CBM computers. A $0FB) The X64 series. That includes the Commodore 16, the Commodore 116, and the Commodore Plus/4. Q $0FC) On the Commodore 1351 mouse, what registers in the Commodore computer would the X and Y proportional information be read from? A $0FC) Even though you are looking for digital information (how far the mouse has traveled since the last movement in a particular axis), the information is read from the "paddle" or potentiometer (POT) registers. On the C64, the POT registers are part of the SID chip, and are at 54297 ($D419) for POTX, and 54298 ($D41A) for POTY. Q $0FD) What is the maximum size of a sequential file on a 1581 drive? A $0FD) 802640 bytes. Q $0FE) What flaw exists in the early Commodore 1670 modems? A $0FE) When the 1670 modem was first introduced, it powered up in auto- answer mode, which means it would answer incoming calls after the phong rang. You could turn this feature off through software control, but if the power was reset, the modem would answer the phone. So many people complained to Commodore that CBM revised the 1670 to include an extra DIP switch that turned this feature off. Q $0FF) What is the model number of the first modem for the VIC and C64? A $0FF) The 1600 manual dial/manual answer 0-300 bps modem. The author owns one, and used it for many years. To operate, you must use a phone with a detachable handset cord. You dialed the number on the phone, waited for the answer, unplugged the handset, and plugged the cord into the 1600. A switch toggled between using originate or answer frequencies. The 1600 was manufactured by Anchor Automation for Commodore. (As an aside, this unit claimed 300 bps, but I never could get 300 to work well. Most of my telecommunications happened at 150 bps.) -------Commodore Trivia Edition #17 Questions and Answers (BEGIN)-------- Q $100) On the MOS Technology's KIM-1, how many keys were on the keypad? A $100) 23 keys. The keypad has room for 24, but one spot is taken by a switch that puts the system into single-step mode. Interestingly, some pictures have the switch on the upper left, some on the upper right. Q $101) The KIM-1 keypad had the common 0-9A-F keys on the keypad, but also had some special keys. Name them. A $101) GO (Go) Executes an instruction and displays the address of next, ST (Stop) Stops execution of program and return control to monitor, RS (Reset), AD (Address) Address entry mode, DA (Data) Data entry mode, PC (Program Counter) Displays and restores program counter to values in PCL and PCH, + (Increment) Increments the address without changing the entry mode. Q $102) The KIM-1 was a set of modules that could be plugged together to expand the system. Each module had a model number. What was the model number of the KIM-1 motherboard? A $102) The KIM-4. Q $103) On the 1525 line of printers, if you wanted to create the following graphic, what bytes would you send to the printer after turning on graphics mode? **** * * * * * * * * * * **** A $103) I guess I should have stipulated that this is a bitmap. ASCII just has a few limitations. Anyway, the correct bytes to send are: 255, 193, 193, 255. You got these by assigning each bit in a column a value, and adding 128 to the result for each column. Q $104) What is the horizontal resolution of the 1525 line of printers? A $104) Character resolution: 80 chars, or 10 chars/inch (cpi). Graphics resolution: 480 dots, or 60 dots/inch (dpi). Q $105) On Commodore drives, explain the difference between the B-R command and the U1 command. A $105) The two commands read in data from a disk sector. However, the U1 command always reads a full sector (255 bytes). The B-R command reads the number of bytes specified in the first byte of the sector. If the first byte is a 15, B-R will read 15 bytes from the sector. (From the 1581 manual) Q $106) On the Commodore 1541 drive, what does the U: command do? A $106) This command has been traditionally used to reset Commodore drives, including the CBM 1541. However, some early versions of the Drive DOS did not correctly handle this command. In these versions, the drive and computer failed to complete the command transaction successfully, and what looked like a hung machine resulted. Commodore later fixed this problem. If U: seems to not work on your drive, try U; instead. Q $107) What does the first routine in the 1541 drive ROM actually do? A $107) The function, called SETLDA and residing at $C100, turns on the drive active LED for the current drive. The routine loads the current drive from $7F and sets bit 3 of DSKCNT ($1C00). Q $108) How many files will a 1581 disk drive hold? A $108) 296 files. Note that it is not a multiple of 144. Q $109) Commodore 1581 drives have a special "autoboot" feature that enables the drive to load and run a program off a disk upon drive bootup. What is the required name of the file? A $109) COPYRIGHT CBM 86 Q $10A) What filetype must the file mentioned in $109 be? A $10A) USR. Q $10B) To power up a 1351 mouse in "joystick mode", what must the user do? A $10B) If one depresses the right mouse button during power-up, the 1351 will behave just like a joystick. Q $10C) Describe the contents of the POTX or POTY registers when using a 1351 mouse. A $10C) Each register holds the same type of information, just for a separate axis, so we will describe just one register: Bit: Function 7 Don't care 6-1 Mouse axis position mod 64. 0 Noise Bit. (check this bit to see whether mouse has moved) Q $10D) Commodore computers typically use most of zero page for temporary variables and other items. However, both the VIC-20 and the 64 reserve 4 bytes for user programs that need zero page memory. Where are these locations? A $10D) $FB-$FE (251-254). I am not sure these were "reserved" for programmers as much as they were just not utilized by the CBM programmers. Q $10E) Name the 16 colors available on the 64. A $10E) Black White Red Cyan (Light Blue-Green) Purple Green Blue Yellow Orange Brown Light Red Dark Gray (Gray 1) Medium Grey (Gray 2) Light Green Light Blue Light Gray (Gray 3) Q $10F) Both the VIC-20 and the C64 emulate the operation of the 6551 UART. How many "mock 6551" registers are mapped into the memory map? A $10F) 5, from $293-$297 (659-663). The register contents: $293 6551 Control Register $294 6551 Command Register $295-6 6551 User Defined Baud Rate value. $297 6551 Status Register ------------Commodore Trivia Edition #18 Questions (BEGIN)-------------- Q $110) What is the name of the company that recently purchased the liquidated Commodore assets? Q $111) At one time, Commodore attempted to manufacture a dual drive version of the 1571 called the 1572. For what technical reason did it utimately fail? Q $112) Over what computer system did a User Group sue Commodore and win? Q $113) In $103, the question asked how to create a graphic of a small box on the 1525. In this quesrtion, we have made a different design. If you wanted to create the following graphic using individual dots on the printer, what bytes would you send to the printer after turning on graphics mode? ** * * * *** * ** *** * * * * * ** ** * * * * ** Q $114) (Some C65 questions) How many SID chips does the the development Commodore 65 machine contain? Q $115) What CPU does the Commodore 65 use? Q $116) What is the alternate name for the Commodore 65? Q $117) How many processors does the internal 1581-compatible drive on the C65 contain? Q $118) In the tradition of naming certian ICs after famous cartoon characters, one of the ICs in the C65 is named after a Warner Brothers cartoon character. Which one? Q $119) What version of BASIC is included on the Commodore 65 in C65 mode? Q $11A) How many I/O ports does a Commodore 65 contain? Q $11B) What common Commodore 64 I/O port does the C65 NOT have? Q $11C) How many function keys are on a Commodore 65? Q $11D) What CBM disk drive DOS was used as the template for the internal C65 drive DOS? Q $11E) What resolution of text screen does the C65 power up in? (Please give answers in characters). Q $11F) What distinguishing non-textual characteristic in the C65 is not present in othe Commodore 8-bit computers? The information in this between the lines marked by (BEGIN) and (END) is copyright 1995 by Jim Brain. Provided that the information between the (BEGIN) and (END) lines is not changed except to correct typographical errors, the so marked copyrighted information may be reproduced in its entirety on other networks or in other mediums. For more information about using this file, please contact the address shown below. Jim Brain brain@mail.msen.com 602 North Lemen Fenton, MI 48430 (810) 737-7300 x8528 Some are easy, some are hard, try your hand at: Commodore Trivia #18! ======================================================================== BFLI - New graphics modes 2 by Pasi 'Albert' Ojala One day I was watching some demos that used linecrunch routines for whole-screen multicolor-graphics upscrollers. I already had my theories about how and why linecrunch worked, but because I had not used it anywhere, the details were a bit vague. In fact, I have many times accidentally created linecrunch effects when trying to do something else with $D011. Probably every demo coder has. But you learn by doing. I had the idea of using linecrunch for FLI instead of a simple multicolor picture as it always seemed to be used. However, this has probably been done before and because I don't like to do things that have been done before, I decided to use linecrunch to show a two-screen-tall FLI picture. _Linecrunch Basics_ For those not familiar with linecrunch routines: linecrunch is used to scroll the screen UPWARDS by convincing VIC-II that it has already showed more character rows than it in reality has shown. Surprisingly (or then, maybe not :) this consists of fiddling with $D011. The timing is critical as always. Linecrunch works by setting $D011 equal the line before the current line and VIC-II will happily think that it is time to move on to the next character row - add 40 to the video matrix counter, 320 to the graphics memory counter and be ready to start a bad line. Or, maybe 'NOT to go back to the current row' would be a more suitable description. (Programming VIC-II is slowly becoming a science.) The required timing also does not cause bad lines so that you can skip another line immediately on the successive line. In addition, lines can be skipped only after the first character row and half of the second character row have been displayed. This has something to do with the way VIC-II decides when there is a bad line. Because linecrunch causes VIC-II to skip rows, it will run out of video matrix and color memory (and graphics memory) before reaching the end of the screen. However, VIC-II does not stop displaying the graphics nor does it reset the internal counters. The counters keep on running and wrap around instead. Normally, when VIC-II is displaying the last character row, it is showing the memory from offsets $3c0 to $3e7. If VIC-II has skipped one character row, it is displaying from $3e8 to $40f instead. But, there are only 10 bits for the video matrix counter (0..1023), so it wraps around to zero after $3ff. This means that the beginning of the video matrix is displayed at the bottom of the screen. The character rows become shifted by 24 character positions to the right because there were originally 24 unused memory locations at the end of the memory (1000..1023). (To be honest, sprite image pointers are not unused memory, but they are not used with normal FLI.) ____________________ ____________________ |abcdefghijklmnopqrst| |abcdefghijklmnopqrst| | | |--------------------| <- Skipped row : : : : : : : : : : : : | | |normally last line | |normally last line | |XXXXXXXXZZZZabcdefgh| `--------------------' `--------------------' X = unused mem (1000..1015) Z = sprite pointers (1016..1023) Figure 1: Linecrunch The same thing happens for color memory because it uses the same counter for addressing the memory (in fact, color memory access and character data access are performed simultaneosly, 12 bits at a time). The graphics memory behaves the same way, except that the counter has three bits more and it counts at eight times the speed, so that it wraps at the exact same time as the other counter. The first character row can't be used for linecrunch and the second one is also lost in the process. The first usable line to display is the third character row. However, those two lost rows can still be used as an extension at the end of the first screen. You must notice, however, that the alignment has been changed. After these two rows have been displayed, the video bank is switched to get new fresh data on the screen. _Back to BFLI_ Wrapped data is nothing difficult to work with. It is just the matter of writing the right conversion program. Also, the normal FLI routine can be used, we just have to make sure VIC always has the right bank visible - simple LDA bank,x:sta $DD00 can accomplish that. The more difficult aspect is to make the display freely locatable. We have 32 kilobytes of graphics data, this is the main reason we can't even think about using copying. Linecrunch combined with the bad line delaying technique will do the job much more nicely. Figure 2 shows the principles. To make things simpler I have chosen location 0 to mean that the top of the picture is visible, 1 means that the picture is scrolled one line upwards and so on. We can see that linecrunch is not used at all for the location 0. To make the picture start at the same point whether linecrunch has crunched lines or not we compensate the non-lost raster lines by delaying the next bad line. When the location is n*8 (n=0,1,2..), the sum of the linecrunched and delayed lines is constant - the graphics display always starts at the same point. Then how do we deal with the location values that are not evenly dividable by eight ? Now, lets assume that the location is L, and we have C, which is the location divided by eight (C = L/8), and R, which is the remainder (R = L%8). To make the picture scroll to the right position, we need to delay the bad line less than before - R lines less for location L than for location C*8. E.g. for location 2 we delay the bad line two lines less than for location 0. This also shows that we need 7 lines more than is needed for to compensate for the linecrunch. Determining the number of linecrunch lines is a recursive process, because when you use more linecrunch lines, that decreases the number of lines you have available for the display and you need bigger range for the location value. The linecrunch can be started after 12 lines, and we need at least 7 lines to use the soft y-scroll. This makes 181 lines available for the display originally. Because we need to show 400 lines of graphics, we would need (400-181)/8=28 linecrunch lines. However, this in turn reduces the number of lines we have for graphics to 181-28=153 and we need (400-153)/8=31 linecrunch lines. Again, 181-31 is 150. We get (400-150)/8=32 and there it finally converges and we have 149 lines for graphics, which makes location values 0..251 valid. Location 0 1 2 .. 8 9 .. 251 ___________________.. ___________.. ________ ___________________.. ___________.. ________ Linecrunch -------------------.. ___________.. ^ ^ ^ | | | ^ ^ | | | | | Bad line delayed| | | | | | | | | | ======== | | v | | 244 | v ___.. | v : v ________0 v ___.. : Gfx Enabled ________0_______1__.. ________8__.. 250_____ 0 1 2 8 9 251 1 2 3 9 10 252 2 3 4 10 11 253 3 4 5 11 12 254 4 5 6 12 13 255 5 6 7 13 14 256 6 7 8 14 15 257 7 8 9 15 16 258 : : : : : : : : : : : : 148 149 150.. 156 157.. 399 Figure 2: Linecrunch and DMA delay in BFLI (Graphics lines not in scale) _Clipping added_ Now we can scroll the picture to any location we want, but the top of the picture is not clipped and it is very annoying to watch. We need to enable the graphics at the same point regardless of the y-scroll value. The answer is in the extended color mode (ECM). When both ECM and multicolor mode (MCM) are selected, VIC-II will turn the display to black. This is because there is a conflicting situation and it just can't decide which color scheme to use. The video accesses will continue to happen just like before, the data is just not displayed. When the ECM bit is cleared again, the normal multicolor graphics is shown. So, we set the ECM bit and start to display the first eight lines of the FLI. Because the FLI routine already writes to $D011, we just make sure the ECM bit is set in the first R number of writes to $D011 and zero in all other. The viewer is now 'complete'. You can take a look at the code below or you can get C64Gfx1_4.lha and see it in action yourself and not just rely on my word. The package includes converter programs for BFLI, FLI and Koala (ANSI-C), couple of example pictures and viewers for PAL and NTSC machines. -Pasi 'Albert' Ojala albert@cs.tut.fi -------------------------------------------------------------------------- BFLI viewer program for PAL machines UPOS = $C00 ; temporary area for tables BANK = $D00 ; UPOS for linecrunch, BANK for FLI bank select RASTER = 29 ; where to position the sprite -> IRQ 20 lines later DUMMY = $FFF ; dummy location for timing purposes FLISZ = 19-1 ; visible FLI size in character rows - 1 *= $810 SEI LDA #$7F:STA $DC0D ; IRQ setup LDA #1:STA $D01A STA $D015:STA KEYW+1 LDA #IRQ:STA $315 LDA #RASTER:STA $D001:CLC:ADC #20:STA $D012 LDA #0:STA $D017 LDA #0:STA 2 JSR NEWPOS ; Init the FLI routines LDA #$A:STA $D011 ; Blank screen LDX #23 ; Init tables BLOOP LDA #$94:STA BANK,X LDA #$96:STA BANK+24,X DEX:BPL BLOOP LDX #15 LOOP0 LDA YINIT,X:AND #$77 ; Change to $37 to better see the STA UPOS,X ; workings of the routines STA UPOS+16,X STA UPOS+32,X DEX:BPL LOOP0 LDA #$34:STA 1 ; Copy to the last video bank LDA #$80:STA SRC+2 ; from $8000-$BFFF to $C000-$FFFF LDA #$C0:STA DST+2 LDX #0:LDY #$40 SRC LDA $8000,X DST STA $C000,X INX:BNE SRC INC SRC+2:INC DST+2 DEY:BNE SRC LDA #$37:STA 1 LDX #0 ; Init color memory LP LDA $3C00,X:STA $D800,X ; All 1024 bytes are used LDA $3D00,X:STA $D900,X ; - some even twice! LDA $3E00,X:STA $DA00,X LDA $3F00,X:STA $DB00,X INX:BNE LP LDA $DC0D:CLI KEYW LDX #0:BNE KEYW ; Wait for space to be pressed SEI ; System to normal LDA #$37:STA 1 JSR $FDA3 LDA #$97:STA $DD00 JSR $E5A0 LDY #3 IRQL LDA $FD30,Y:STA $314,Y DEY:BPL IRQL LDX #0:LDA #1 ; Clear color memory CLL STA $D800,X:STA $D900,X STA $DA00,X:STA $DB00,X INX:BNE CLL CLI:RTS YINIT BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F *=*-<*+256 IRQ LDA #$18:STA $D016:LDX #0:LDA #$5A INC DUMMY:DEC DUMMY ; Synchronization STX $D020:STX $D021:STA $D011 LDA #$15:STA $D018 LDA #$97:STA $DD00 LDX #44 ; Wait for the 4th line LL DEX:BPL LL:NOP LDX #0 LOOP3 NOP ; Linecrunch-part routine LDA UPOS+6,X:INC DUMMY:STA $D011 NOP:NOP:INC DUMMY NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:NOP INX E1 CPX #$10:BNE LOOP3 ; Skip that many character rows-4 BIT $EA LOOP4 LDA UPOS,X:INC DUMMY:STA $D011 NOP:NOP:NOP:INC DUMMY NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:LDA #0 INX E2 CPX #$1F:BNE LOOP4 ; Delay DMA until we are at the ; 'same place' each time LDA #0:STA $D020 ; Now wait for the bad line and start FLI BIT $EA:NOP NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP B0 LDA #$92:STA $DD00:NOP ; The right video bank ; Wait for 0-7 lines to set the ECM mode off ; (makes the graphics visible) F0 LDA #0:STA $D011:LDA #$08:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F1 LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F2 LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F3 LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F4 LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F5 LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F6 LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F7 LDA #0:STA $D011:LDA #$78:STA $D018 LDX #FLISZ:NOP:NOP:NOP:BIT $EA ; Do FLI 18 more character rows F8 LDA #0:STA $D011:LDA #$08:STA $D018 B1 LDA BANK,X:STA $DD00:BIT $EA F9 LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FA LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FB LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FC LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FD LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FE LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FF LDA #0:STA $D011:LDA #$78:STA $D018:NOP:NOP:DEX:BMI EFLI:JMP F8 EFLI NOP LDA #$FC WL CMP $D012:BNE WL INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY INC DUMMY:INC DUMMY:STA $D020 JSR NEWPOS ; Update the location JSR CHPOS ; Change to a new location LDA $DC01:AND #$10:BNE OV3 ; Check for the space bar LDA #0:STA KEYW+1 OV3 LDX #$53:STX $D011:INC $D019:JMP $EA81 NEWPOS LDA #0 ; Init the IRQ routine for this position LSR:LSR:LSR:CLC:ADC #4:STA E1+1 LDA #7:SEC:SBC NEWPOS+1:AND #7:TAX:TAY:CLC:ADC #35:STA E2+1 LDA UPOS+3+7,Y:DEX:BMI J0:AND #$3F J0 STA F7+1:AND #$3F:STA FF+1 LDA UPOS+3+6,Y:DEX:BMI J1:AND #$3F J1 STA F6+1:AND #$3F:STA FE+1 LDA UPOS+3+5,Y:DEX:BMI J2:AND #$3F J2 STA F5+1:AND #$3F:STA FD+1 LDA UPOS+3+4,Y:DEX:BMI J3:AND #$3F J3 STA F4+1:AND #$3F:STA FC+1 LDA UPOS+3+3,Y:DEX:BMI J4:AND #$3F J4 STA F3+1:AND #$3F:STA FB+1 LDA UPOS+3+2,Y:DEX:BMI J5:AND #$3F J5 STA F2+1:AND #$3F:STA FA+1 LDA UPOS+3+1,Y:DEX:BMI J6:AND #$3F J6 STA F1+1:AND #$3F:STA F9+1 LDA UPOS+3+0,Y:DEX:BMI J7:AND #$3F J7 STA F0+1:AND #$3F:STA F8+1 LDA #$96:STA B0+1:LDA #199:SEC:SBC NEWPOS+1:BCC OV2 LSR:LSR:LSR:CLC:ADC #5:STA B1+1 RTS OV2 LDA #0:STA B1+1:LDX #$94:STX B0+1:RTS CHPOS LDX NEWPOS+1 LDA $DC00:TAY ; Get joystick AND #$10:BNE DIR ; If no button pressed TYA:AND #1:BEQ UP ; If joy up TYA:AND #2:BEQ DOWN ; If joy down RTS DIR LDA #0:BEQ UP DOWN DEX:CPX #$FF:BNE DOK LDX #0:STX DIR+1 ; Change direction DOK STX NEWPOS+1:RTS UP INX:CPX #$FD:BCC UOK ; 251(locations)+149(visible)=400 LDX #$FC:STX DIR+1 ; Change direction UOK STX NEWPOS+1:RTS -------------------------------------------------------------------------- The BFLI file format: File BFLI Display Lines Offset Offset Lines Size Colors 0-1.3 0..55 944..999 22.7-24 56 I 1.3-2 56..79 - 24 2-24 80..999 0..919 0-22 920 24-24.7 1000..1023 920..943 22-22.7 24 II 0-1.3 0..55 1968..2024 49.3-50.6 56 1.3-24.7 56..1023 1000..1967 24-49.3 968 Gfx 0-1.3 0..447 7552..7999 22.7-24 448 I 1.3-2 448..639 - 192 2-24 640..7999 0..7359 0-22 7360 24-24.7 8000..8191 7360..7551 22-22.7 192 II 0-1.3 0..447 15744..16192 49.3-50.6 448 1.3-24.7 448..8191 8000..15743 24-49.3 7744 ======================================================================== Making stable raster routines (C64 and VIC-20) by Marko Makela (Marko.Makela@HUT.FI) Preface Too many graphical effects, also called raster effects, have been coded in a very sloppy way. For instance, if there are any color bars on the screen in a game or demo, the colors often jitter a bit, e.g. they are not stable. And also, it is far too easy to make virtually any demo crash by hitting the Restore key, or at least cause visual distortions on the screen. As late as a year ago I still hadn't coded a stable raster interrupt routine myself. But then I had to do it, since I was researching the video chip timing details together with my German friend Andreas Boose. It was ashaming that we had the same level of knowledge when it came to the hardware, but he was the only of us who had written a stable raster routine. Well, finally I made me to start coding. I used the same double-interrupt idea as Andreas used in his routine. After a couple of errors my routine worked, and I understood how it works exactly. (This is something that separates us normal coders from demo people: They often code by instinct; by patching the routine until it works, without knowing exactly what is happening. That's why demos often rely on weird things, like crash if the memory is not initialized properly.) In this article, I document two methods of creating stable raster routines on Commodore computers. The principles apply for most 8-bit computers, not only Commodores, but raster effects are very rarely seen on other computers. Background What are raster effects? They are effects, where you change the screen appearance while it is being drawn. For instance, you can set the screen color to white in the top of the screen, and to black in the middle of the screen. In that way, you will get a picture whose top half is white and bottom half black. Normally such effects are implemented with interrupt routines that are executed synchronized with the screen refresh. The video chip on the Commodore 64 and many other videochips have a special interrupt feature called the Raster interrupt. It will generate an IRQ in the beginning of a specified raster line. On other computers, like the VIC-20, there is no Raster interrupt, but you can generate the interrupts with a timer, provided that the timer and the videochip are clocked from the same source. Even if the processor gets an interrupt signal at the same position on each video frame, it won't always be executing the first instruction of the interrupt routine at the same screen position. The NMOS 6502 machine instructions can take 2 to 9 machine cycles to execute, and if the main program contains instructions of very varying lengths, the beginning position of the interrupt can jump between 7 different positions. This is why you need to synchronize the raster routine when doing serious effects. Also, executing the interrupt sequence will take 7 additional cycles, and the interrupt sequence will only start after the current instruction if the interrupt arrived at least two cycles before the end of the current instruction. It is even possible that an interrupt arrives while interrupts are disabled and the processor is just starting to execute a CLI instruction. Alas, the processor will not jump to the interrupt right after the CLI, but it will execute the next instruction before jumping to it. This is natural, since the CLI takes only two cycles. But anyway, this is only a constant in our equation, and actually out of the scope of this article. How to synchronize a raster interrupt routine? The only way is to check the current screen position and delay appropriately many cycles. There are several ways of doing this, some of which are very awful and inefficient. The ugliest ways of doing this on the Commodore 64 I know are busy-waiting several raster lines and polling the raster line value, or using the Light pen feature, which will fail if the user presses the fire button on Joystick port 1. Here I will present two ways, both very elegant in my opinion. Using an auxiliary timer On the VIC-20, there is no Raster interrupt feature in the video chip. All you can do is to use a timer for generating raster interrupts. And if you use two timers running at a constant phase difference, you can get full synchronization. The first timer generates the raster interrupt, and the second timer, the auxiliary timer, tells the raster routine where it is running. Actually you could even use the first timer also for the checking, but the code will look nicer in the way I will be presenting now. Besides, you can use the auxiliary timer idea even when real raster interrupts are available. The major drawback of using an auxiliary timer is initializing it. The initialization routine must synchronize with the screen, that is, wait for the beginning of the wanted raster line. To accomplish this, the routine must first wait for a raster line that occurs a bit earlier. About the only way to do this is with a loop like LDA #value loop CMP raster BNE loop One round of this loop will take 4+3=7 cycles to execute, assuming that absolute addressing is being used. The loop will be finished if the raster register contains the wanted value while the processor reads it on the last cycle of the CMP instruction. The raster register can actually have changed already on the first cycle of the BNE instruction on the previous run of the loop, that is 7 cycles earlier! Because of this, the routine must poll the raster register for several raster lines, always consuming one cycle more if the raster register changed too early. As the synchronization can be off at most by 7 cycles, a loop of 7 raster register value changes would do, but I made the loop a bit longer in my VIC-20 routine. (Well, I have to admit it, I was too lazy to make it work only with 7 rounds.) After the initialization routine is fully synchronized the screen, it can set up the timer(s) and interrupts and exit. The auxiliary timer in my VIC-20 demo routine is several dozens of cycles after the primary timer, see the source code for comments. It is arranged so that the auxiliary timer will be at least 0 when it is being read in the raster routine. The raster routine will wait as many extra cycles as the auxiliary timer reads, however at most 15 cycles. Using double raster interrupt On the Commodore 64, I have never seen the auxiliary timer scheme being used. Actually I haven't seen it being used anywhere, I was probably the first one who made a stable raster interrupt routine on the VIC-20. Instead, the double interrupt method is becoming the standard on the C64 side. The double interrupt method is based entirely on the Raster interrupt feature of the video chip. In the first raster interrupt routine, the program sets up another raster interrupt on a further line, changes the interrupt vector and enables interrupts. In the place where the second raster interrupt will occur, there will be 2-byte instructions in the first interrupt routine. In this way, the beginning of the next raster interrupt will be off at most by one cycle. Some coders might not care about this one cycle, but if you can do it right, why wouldn't you do it right until the end? At the beginning of the second raster interrupt routine, you will read the raster line counter register at the point where it is about to change. When the raster routine is being executed, there are two possibilities: Either the raster counter has just changed, or it will change on the next cycle. So, you just need to compare if the register changed one cycle too early or not, and delay a cycle when needed. This is easily accomplished with a branch to the next address. Of course, somewhere in your second raster interrupt routine you must restore the original raster interrupt position and set the interrupt vector to point to the first interrupt routine. Applying in practice I almost forgot my complaints about demos crashing when you actively hit the Restore key. On the VIC-20, you can disable NMI interrupts generated by the Restore key, and on the C64, you can generate an NMI interrupt with the CIA2 timer and leave the NMI-line low, so that no further high-to-low transitions will be recognized on the line. The example programs demonstrate how to do this. So far, this article has been pretty theoretical. To apply these results in practice, you must definitely know how many CPU clock cycles the video chip consumes while drawing a scan line. This is fairly easy to measure with a timer interrupt, if you patch the interrupt handler so that it changes the screen color on each run. Set the timer interval to LINES*COLUMNS cycles, where LINES is the amount of raster lines and COLUMNS is your guess for the amount of clock cycles spent in a raster line. If your guess is right, the color will always be changed in the same screen position (neglecting the 7-cycle jitter). When adjusting the timer, remember that the timers on the 6522 VIA require 2 cycles for re-loading, and the ones on the 6526 CIA need one extra cycle. Keep trying different timer values until you the screen color changes at one fixed position. Commodore used several different values for LINES and COLUMNS on its videochips. They never managed to make the screen refresh rate exactly 50 or 60 Hertz, but they didn't hesitate to claim that their computers comply with the PAL-B or NTSC-M standards. In the following tables I have gathered some information of some Commodore video chips. NTSC-M systems: Chip Crystal Dot Processor Cycles/ Lines/ Host ID freq/Hz clock/Hz clock/Hz line frame ------ -------- -------- -------- --------- ------- ------ VIC-20 6560-101 14318181 4090909 1022727 65 261 C64 6567R56A 14318181 8181818 1022727 64 262 C64 6567R8 14318181 8181818 1022727 65 263 Later NTSC-M video chips were most probably like the 6567R8. Note that the processor clock is a 14th of the crystal frequency on all NTSC-M systems. PAL-B systems: Chip Crystal Dot Processor Cycles/ Lines/ Host ID freq/Hz clock/Hz clock/Hz line frame ------ -------- -------- -------- --------- ------- ------ VIC-20 6561-101 4433618 4433618 1108405 71 312 C64 6569 17734472 7881988 985248 63 312 On the PAL-B VIC-20, the crystal frequency is simultaneously the dot clock, which is BTW a 4th of the crystal frequency used on the C64. On the C64, the crystal frequency is divided by 18 to generate the processor clock, which in turn is multiplied by 8 to generate the dot clock. The basic timings are the same on all 6569 revisions, and also on any later C64 and C128 video chips. If I remember correctly, these values were the same on the C16 videochip TED as well. Note that the dot clock is 4 times the processor clock on the VIC-20, and 8 times that on the C64. That is, one processor cycle is half a character wide on the VIC-20, and a full character on a C64. I don't have exact measurements of the VIC-20 timing, but it seems that while the VIC-20 videochips draw the characters on the screen, it first reads the character code, and then, on the following video cycle, the appearance on the current character line. There are no bad lines, like on the C64, where the character codes (and colors) are fetched on every 8th raster line. Those ones who got upset when I said that Commodore has never managed to make a fully PAL-B or NTSC-M compliant 8-bit computer should take a closer look at the "Lines/frame" columns. If that does not convince you, calculate the raster line rate and the screen refresh rate from the values in the table and see that they don't comply with the standards. To calculate the line rate, divide the processor clock frequency by the amount of cycles per line. To get the screen refresh rate, divide that frequency by the amount of raster lines. The Code OK, enough theory and background. Here are the two example programs, one for the VIC-20 and one for the C64. In order to fully understand them, you need to know the exact execution times of NMOS 6502 instructions. (All 8-bit Commodore computers use the NMOS 6502 processor core, except the C65 prototype, which used a inferior CMOS version with all nice poorly-documented features removed.) You should check the 64doc document, available on my WWW pages at http://www.hut.fi/~msmakela/cbm/emul/x64/64doc.html, or via FTP at ftp.funet.fi:/pub/cbm/documents/64doc. I can also e-mail it to you on request. Also, I have written a complete description of the video timing on the 6567R56A, 6567R8 and 6569 video chips, which could maybe be turned into another C=Hacking article. The document is currently partially in English and partially in German. The English part is available from ftp.funet.fi as /pub/cbm/documents/pal.timing, and I can send copies of the German part (screen resolution, sprite disturbance measurements, and more precise timing information) via e-mail. The code is written for the DASM assembler, or more precisely for a extended ANSI C port of it made by Olaf Seibert. This excellent cross-assembler is available at ftp.funet.fi in /pub/cbm/programming. First the raster demo for the VIC-20. Note that on the VIC-20, the $9004 register contains the upper 8 bits of the raster counter. So, this register changes only on every second line. I have tested the program on my 6561-101-based VIC-20, but not on an NTSC-M system. It was hard to get in contact with NTSC-M VIC-20 owners. Daniel Dallmann, who has a NTSC-M VIC-20, although he lives in Germany, ran my test to determine the amount of cycles per line and lines per frame on the 6560-101. Unfortunately, the second VIA of his VIC-20 is partially broken, and because of this, this program did not work on his computer. Craig Bruce ran the program once, and he reported that it almost worked. I corrected a little bug in the code, so that now the display should be stable on an NTSC-M system, too. But the actual raster effect, six 16*16-pixel boxes centered at the top border, are very likely to be off their position. processor 6502 NTSC = 1 PAL = 2 ;SYSTEM = NTSC ; 6560-101: 65 cycles per raster line, 261 lines SYSTEM = PAL ; 6561-101: 71 cycles per raster line, 312 lines #if SYSTEM & PAL LINES = 312 CYCLES_PER_LINE = 71 #endif #if SYSTEM & NTSC LINES = 261 CYCLES_PER_LINE = 65 #endif TIMER_VALUE = LINES * CYCLES_PER_LINE - 2 .org $1001 ; for the unexpanded Vic-20 ; The BASIC line basic: .word 0$ ; link to next line .word 1995 ; line number .byte $9E ; SYS token ; SYS digits .if (* + 8) / 10000 .byte $30 + (* + 8) / 10000 .endif .if (* + 7) / 1000 .byte $30 + (* + 7) % 10000 / 1000 .endif .if (* + 6) / 100 .byte $30 + (* + 6) % 1000 / 100 .endif .if (* + 5) / 10 .byte $30 + (* + 5) % 100 / 10 .endif .byte $30 + (* + 4) % 10 0$: .byte 0,0,0 ; end of BASIC program start: lda #$7f sta $912e ; disable and acknowledge interrupts sta $912d sta $911e ; disable NMIs (Restore key) ;synchronize with the screen sync: ldx #28 ; wait for this raster line (times 2) 0$: cpx $9004 bne 0$ ; at this stage, the inaccuracy is 7 clock cycles ; the processor is in this place 2 to 9 cycles ; after $9004 has changed ldy #9 bit $24 1$: ldx $9004 txa bit $24 #if SYSTEM & PAL ldx #24 #endif #if SYSTEM & NTSC bit $24 ldx #21 #endif dex bne *-1 ; first spend some time (so that the whole cmp $9004 ; loop will be 2 raster lines) bcs *+2 ; save one cycle if $9004 changed too late dey bne 1$ ; now it is fully synchronized ; 6 cycles have passed since last $9004 change ; and we are on line 2(28+9)=74 ;initialize the timers timers: lda #$40 ; enable Timer A free run of both VIAs sta $911b sta $912b lda #TIMER_VALUE sta $9116 ; load the timer low byte latches sta $9126 #if SYSTEM & PAL ldy #7 ; make a little delay to get the raster effect to the dey ; right place bne *-1 nop nop #endif #if SYSTEM & NTSC ldy #6 dey bne *-1 bit $24 #endif stx $9125 ; start the IRQ timer A ; 6560-101: 65 cycles from $9004 change ; 6561-101: 77 cycles from $9004 change ldy #10 ; spend some time (1+5*9+4=55 cycles) dey ; before starting the reference timer bne *-1 stx $9115 ; start the reference timer pointers: lda #irq sta $315 lda #$c0 sta $912e ; enable Timer A underflow interrupts rts ; return irq: ; irq (event) ; > 7 + at least 2 cycles of last instruction (9 to 16 total) ; pha ; 3 ; txa ; 2 ; pha ; 3 ; tya ; 2 ; pha ; 3 ; tsx ; 2 ; lda $0104,x ; 4 ; and #xx ; 2 ; beq ; 3 ; jmp ($314) ; 5 ; --- ; 38 to 45 cycles delay at this stage lda $9114 ; get the NMI timer A value ; (42 to 49 cycles delay at this stage) ; sta $1e00 ; uncomment these if you want to monitor ; ldy $9115 ; the reference timer on the screen ; sty $1e01 cmp #8 ; are we more than 7 cycles ahead of time? bcc 0$ pha ; yes, spend 8 extra cycles pla and #7 ; and reset the high bit 0$: cmp #4 bcc 1$ bit $24 ; waste 4 cycles and #3 1$: cmp #2 ; spend the rest of the cycles bcs *+2 bcs *+2 lsr bcs *+2 ; now it has taken 82 cycles from the beginning of the IRQ effect: ldy #16 ; perform amazing video effect lda $900f tax eor #$f7 0$: sta $900f stx $900f sta $900f stx $900f sta $900f stx $900f sta $900f stx $900f sta $900f stx $900f sta $900f stx $900f pha pla #if SYSTEM & PAL pha pla nop #endif #if SYSTEM & NTSC bit $24 #endif nop dey bne 0$ ; end of amazing video effect jmp $eabf ; return to normal IRQ And after you have recovered from the schock of seeing a VIC-20 program, here is an example for the C64. It does also something noteworthy; it removes the side borders on a normal screen while displaying all eight sprites. Well, it cannot remove the borders on bad lines, and the bad lines look pretty bad. But I could use the program for what I wanted: I measured the sprite distortions on all videochip types I had at hand. (FYI: the sprites 0-2 get distorted at the very right of the screen, and the sprites 6 and 7 are invisible at the very left of the screen. You will need a monitor with horizontal size controls to witness these effects.) This program is really robust, it installs itself nicely to the interrupt routine chain. It even has an entry point for deinstalling itself. But in its robustness it uses self-modifying code to store the original interrupt routine address. :-) The code also relies on the page boundaries in being where they are. The cycles are counted so that the branches "irqloop" must take 4 cycles. If the "irqloop" comes to the same CPU page with the branch instructions, you must add one cycle to the loop in a way or another. When coding the routine, I noticed again how stupid assembly coding can be, especially conditional assembling. In a machine language monitor you have far better control on page boundaries. BTW, you might wonder why I disable the Restore key in a subroutine at the end and not in the beginning of the program. Well, the routine was so long that it would have affected the "irqloop" page boundaries. And I didn't want to risk the modified programs working on all three different videochip types on the first try. In the code, there are some comments that document the video timing, like this one: ;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II ;ssssssssss ||ssssss Phi-2 VIC-II ;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510 ; ^ now we are here The two vertical bars "|" denote optional cycles. On PAL-B systems (63 cycles per line), they are not present. On 6567R56A, which has 64 cycles per line, there is one additional cycle on this position, and the 6567R8 has two additional cycles there. The numbers 0 through 7 are sprite pointer fetches (from the end of the character matrix, e.g. the text screen), the "s" characters denote sprite image fetches, the "r"s are memory refresh, and the "g" are graphics fetches. The two idle video chip cycles are marked with "-". On the processor timing line, the "=" signs show halted CPU, "x" means free bus, and "X" means that the processor will be halted at once, unless it is performing write cycles. processor 6502 ; Select the video timing (processor clock cycles per raster line) CYCLES = 65 ; 6567R8 and above, NTSC-M ;CYCLES = 64 ; 6567R5 6A, NTSC-M ;CYCLES = 63 ; 6569 (all revisions), PAL-B cinv = $314 cnmi = $318 raster = 52 ; start of raster interrupt m = $fb ; zero page variable .org $801 basic: .word 0$ ; link to next line .word 1995 ; line number .byte $9E ; SYS token ; SYS digits .if (* + 8) / 10000 .byte $30 + (* + 8) / 10000 .endif .if (* + 7) / 1000 .byte $30 + (* + 7) % 10000 / 1000 .endif .if (* + 6) / 100 .byte $30 + (* + 6) % 1000 / 100 .endif .if (* + 5) / 10 .byte $30 + (* + 5) % 100 / 10 .endif .byte $30 + (* + 4) % 10 0$: .byte 0,0,0 ; end of BASIC program start: jmp install jmp deinstall install: ; install the raster routine jsr restore ; Disable the Restore key (disable NMI interrupts) checkirq: lda cinv ; check the original IRQ vector ldx cinv+1 ; (to avoid multiple installation) cmp #irq1 beq skipinit irqinit: sei sta oldirq ; store the old IRQ vector stx oldirq+1 lda #irq1 sta cinv ; set the new interrupt vector stx cinv+1 skipinit: lda #$1b sta $d011 ; set the raster interrupt location lda #raster sta $d012 ldx #$e clc adc #3 tay lda #0 sta m 0$: lda m sta $d000,x ; set the sprite X adc #24 sta m tya sta $d001,x ; and Y coordinates dex dex bpl 0$ lda #$7f sta $dc0d ; disable timer interrupts sta $dd0d ldx #1 stx $d01a ; enable raster interrupt lda $dc0d ; acknowledge CIA interrupts lsr $d019 ; and video interrupts ldy #$ff sty $d015 ; turn on all sprites cli rts deinstall: sei ; disable interrupts lda #$1b sta $d011 ; restore text screen mode lda #$81 sta $dc0d ; enable Timer A interrupts on CIA 1 lda #0 sta $d01a ; disable video interrupts lda oldirq sta cinv ; restore old IRQ vector lda oldirq+1 sta cinv+1 bit $dd0d ; re-enable NMI interrupts cli rts ; Auxiliary raster interrupt (for syncronization) irq1: ; irq (event) ; > 7 + at least 2 cycles of last instruction (9 to 16 total) ; pha ; 3 ; txa ; 2 ; pha ; 3 ; tya ; 2 ; pha ; 3 ; tsx ; 2 ; lda $0104,x ; 4 ; and #xx ; 2 ; beq ; 3 ; jmp ($314) ; 5 ; --- ; 38 to 45 cycles delay at this stage lda #irq2 sta cinv+1 nop ; waste at least 12 cycles nop ; (up to 64 cycles delay allowed here) nop nop nop nop inc $d012 ; At this stage, $d012 has already been incremented by one. lda #1 sta $d019 ; acknowledge the first raster interrupt cli ; enable interrupts (the second interrupt can now occur) ldy #9 dey bne *-1 ; delay nop ; The second interrupt will occur while executing these nop ; two-cycle instructions. nop nop nop oldirq = * + 1 ; Placeholder for self-modifying code jmp * ; Return to the original interrupt ; Main raster interrupt irq2: ; irq (event) ; 7 + 2 or 3 cycles of last instruction (9 or 10 total) ; pha ; 3 ; txa ; 2 ; pha ; 3 ; tya ; 2 ; pha ; 3 ; tsx ; 2 ; lda $0104,x ; 4 ; and #xx ; 2 ; beq ; 3 ; jmp (cinv) ; 5 ; --- ; 38 or 39 cycles delay at this stage lda #irq1 sta cinv+1 ldx $d012 nop #if CYCLES - 63 #if CYCLES - 64 nop ; 6567R8, 65 cycles/line bit $24 #else nop ; 6567R56A, 64 cycles/line nop #endif #else bit $24 ; 6569, 63 cycles/line #endif cpx $d012 ; The comparison cycle is executed CYCLES or CYCLES+1 cycles ; after the interrupt has occurred. beq *+2 ; Delay by one cycle if $d012 hadn't changed. ; Now exactly CYCLES+3 cycles have passed since the interrupt. dex dex stx $d012 ; restore original raster interrupt position ldx #1 stx $d019 ; acknowledge the raster interrupt ldx #2 dex bne *-1 nop nop lda #20 ; set the amount of raster lines-1 for the loop sta m ldx #$c8 irqloop: ldy #2 dey bne *-1 ; delay dec $d016 ; narrow the screen (exact timing required) ;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II ;ssssssssss ||ssssss Phi-2 VIC-II ;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510 ; ^ now we are here stx $d016 ; expand the screen #if CYCLES - 63 #if CYCLES - 64 bit $24 ; 6567R8 #else nop ; 6567R56A #endif #else nop ; 6569 #endif dec m bmi endirq clc lda $d011 sbc $d012 and #7 bne irqloop ; This instruction takes 4 cycles instead of 3, ; because the page boundary is crossed. badline: dec m nop nop nop nop dec $d016 ;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II ;ssssssssss cccccccccccccccccccccccccccccccccccccccc ||ssssss Phi-2 VIC-II ;==========xXXX========================================||***====== Phi-2 6510 ; ^ we are here stx $d016 ;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II ;ssssssssss ||ssssss Phi-2 VIC-II ;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510 ; ^ ^^- we are here (6569) ; | \- or here (6567R56A) ; \- or here (6567R8) ldy #2 dey bne *-1 nop nop #if CYCLES - 63 #if CYCLES - 64 nop ; 6567R8, 65 cycles/line nop nop #else bit $24 ; 6567R56A, 64 cycles/line #endif #else nop ; 6569, 63 cycles/line #endif dec m bpl irqloop ; This is a 4-cycle branch (page boundary crossed) endirq: jmp $ea81 ; return to the auxiliary raster interrupt restore: ; disable the Restore key lda cnmi ldy cnmi+1 pha lda #nmi sta cnmi+1 ldx #$81 stx $dd0d ; Enable CIA 2 Timer A interrupt ldx #0 stx $dd05 inx stx $dd04 ; Prepare Timer A to count from 1 to 0. ldx #$dd stx $dd0e ; Cause an interrupt. nmi = * + 1 lda #$40 ; RTI placeholder pla sta cnmi sty cnmi+1 ; restore original NMI vector (although it won't be used) rts Binaries Here are the programs in uuencoded format. First the VIC-20 programs: Color boxes for the VIC-20, NTSC-M version (probably distorted display): begin 644 copper.6560 M`1`*$,L'GC0Q,#D```"I?XTND8TMD8T>D:(<[`20T/N@"20DK@20BB0D)"2B M%D:(<[`20T/N@"20DK@20BB0DHAC* MT/W-!)"P`(C0[:E`C1N1C2N1J8:B5HT6D8TFD:`'B-#]ZNJ.)9&@"HC0_8X5 MD:EJC10#J1"-%0.IP(TND6"M%)')")`$2&@I!\D$D`0D)"D#R0*P`+``2K`` MH!"M#Y"J2?>-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y!( +:$AHZNJ(T--,O^J. ` end Removed sideborders with 8 sprites and bad lines, PAL-B version: begin 644 raster.63 M`0@*",L'GC(P-C$```!,$PA,=`@@'0FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0 M[ZE_C0W*D;C1'0J8&-#=RI`(T:T*VY M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#] MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZB0D[!+0\`#*RHX2T*(!CAG0H@+*T/WJ MZJD4A?NBR*`"B-#]SA;0CA;0ZL;[,",8K1'0[1+0*0?0Y<;[ZNKJZLX6T(X6 MT*`"B-#]ZNKJQOL0S4R!ZJT8`ZP9`TBI0HT8`ZD)C1D#HH&.#=VB`(X%W>B. 1!-VBW8X.W:E`:(T8`XP9`V`8 ` end Removed sideborders with 8 sprites and bad lines, 6567R56A version (very old NTSC-M C64s): begin 644 raster.64 M`0@*",L'GC(P-C$```!,$PA,=`@@'@FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0 M[ZE_C0W*D;C1'0J8&-#=RI`(T:T*VY M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#] MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZNKJ[!+0\`#*RHX2T*(!CAG0H@+*T/WJ MZJD4A?NBR*`"B-#]SA;0CA;0ZL;[,"08K1'0[1+0*0?0Y<;[ZNKJZLX6T(X6 MT*`"B-#]ZNHD),;[$,Q,@>JM&`.L&0-(J4.-&`.I"8T9`Z*!C@W=H@".!=WH 2C@3=HMV.#MVI0&B-&`.,&0-@ ` end Removed sideborders with 8 sprites and bad lines, 6567R8 and above (not too old NTSC-M C64s and all C128s) begin 644 raster.65 M`0@*",L'GC(P-C$```!,$PA,=`@@(0FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0 M[ZE_C0W*D;C1'0J8&-#=RI`(T:T*VY M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#] MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZNHD).P2T/``RLJ.$M"B`8X9T*("RM#] MZNJI%(7[HLB@`HC0_JM&`.L&0-(J4:-&`.I"8T9`Z*!C@W=H@". 5!=WHC@3=HMV.#MVI0&B-&`.,&0-@ ` end That was all, folks! I hope you learned something from this article. Feel free to e-mail me at Marko.Makela@HUT.FI, should anything remain unclear. ======================================================================== A Different Perspective, part III by Stephen Judd --- sjudd@nwu.edu George Taylor --- aa601@cfn.cs.dal.ca Whew! What a busy time it's been -- research to get done, conferences, classes... between getting things done and blowing other things off, I one day reflected for a moment and realized that I had three days left to get the next article together for C=Hacking! So everything has been slapped together at the last minute, and I hope you'll forgive any bugs or unclear concepts. >>> ANECDOTE ALERT <<< And that reminds me: I just got JiffyDOS and an FD-2000 drive -- what a wonderful device. I have a 1.6 megabyte disk formatted into three partitions. The first contains my Merlin 128 assembler, the second is some 4000 blocks large and I use it for all my various versions of code while debugging, and the third is maybe 1000 blocks, and contains only finished code -- no more swapping disks, no more deleting old versions that I hope I don't need to make room on the disk. Also, when I installed JiffyDOS I found a serious bug in my 128D -- a cricket, dead among the IC's. This time we will cover a lot of ground which isn't so much cutting-edge as it is very useful. Let's face it: cubes are getting more than a little dull. A worthy end goal is to have a completely general routine for plotting a series of polygons -- that is, you supply a list of (x,y,z) coordinates from which the program can form a list of polygons. These polygons may then be displayed in 2D, rotated, magnified, filled, etc. And, much to my three-day astonishment, that is exactly what we are going to do. But first, a little excursion. One thing we are of course always thinking about is optimization possibilities: in the shower, while sleeping/dreaming, out on dates, etc. So, where to begin? The biggest cycle hogs in the program are line drawing and face filling -- well, filling faces is pretty straightforward. What about line drawing? Well, one downer of the routine is that every single pixel is plotted. But as we know, on a computer any given line is made up of several smaller vertical and horizontal lines -- wouldn't it be neat if we could think of a way to plot these line chunks all at once, instead of a pixel at a time? Heck yes it would! So here we go: Neat-o Enhanced Chunky Line Drawing Routine ------------------------------------------- First we need to be in the right mindframe. Let's say you're drawing a line where you move three pixels in x before it's time to take a step in y. Instead of plotting all three pixels it would of course be much more efficient to just stick a number like %00011100 in the drawing buffer. But somehow we need to keep track of a) how large the chunk needs to be, and b) where exactly the chunk is. In the above example, we started at a particular x-value: %00010000 and we want to keep adding ones to the right of the starting point; three, to be exact. Hmmm... we need to somehow rotate the starting bit in a way that leaves a trail of ones behind it. Maybe rotate and ORA with the original bit? But what happens when you take a step in Y? No, we need something far sneakier. Let's say that instead of %00010000 we start with x = %00011111 Now, with each step in the x direction, we do an arithmetic shift on x. So after one step we have x = %00001111 and after two steps x = %00000111 and at the third step of course x = %00000011 Now it is time to take a step in Y. But now look: if we EOR x with its original value xold = %00011111, we get x EOR xold = %00011100 which is exactly the chunk we wanted. Moreover, x still remembers where it is, so we don't have to do anything special each time a step is taken in the y-direction. So here is the algorithm for drawing a line in the x-direction: initialize x, dx, etc. xold = x take a step in x: LSR X have we hit the end of a column? If so, then plot and check on y is it time to take a step in y? if not, take another step in x if it is, then let a=x EOR xold plot a into the buffer let xold=x keep on going until we're finished This simple modification gives us a substantial speed increase -- on the old filled hires cube3d program, I measured a gain of one frame per second. Not earth-shattering, but not bad either! When faces are not filled, the difference is of course much more noticable. There are a few things to be careful of. There was a bug in the old routine when the line was a single point. In that case dx=dy=0, and the program would draw a vertical line on the screen. There are probably some other things to be careful of, but since I wrote this part of the code three months ago I really don't remember any of them! This takes care of horizontal line chunks -- what about vertical chunks? Well, because of the way points are plotted there is nothing we can do about them. But, as we shall soon see, if we use an EOR-buffer to fill faces we will be forced to take care of the vertical chunks! General Polygon Routine ----------------------- Now we can begin thinking about a general polygon routine. First we need a list of sets of points, where each set corresponds to a polygon. The first number in a set could be the number of (x,y,z) points in that set, and the points could then follow. So a triangle could be given by the data set: 3 -1 0 0 0 1 0 1 0 0 This would be a triangle with vertices at (-1,0,0), (0,1,0), and (1,0,0). We can mash a bunch of these sets together, but somehow we have to know when we've hit the end -- for this we can use a zero, since we don't want to plot polygons with zero points in them. For that matter, how many points should there be in a polygon? There must be at least three, otherwise it makes no sense. Since we want our polygons to be closed, the computer should be smart enough to connect the last point to the first point -- in our triangle above, the computer would join (-1,0,0) to (0,1,0), (0,1,0) to (1,0,0), and (1,0,0) to (-1,0,0). Now that we have a polygon, we want to rotate it. You will recall that we have calculated a rotation matrix M, which acts on points. So we need apply our rotation transform to each of the points in the polygon, i.e. multiply M times each point of the polygon. Furthermore, we need to project each of these points. Uh-oh: matrix multiplication. In the past we have avoided this issue by putting the vertices of our cube at 1 or -1. So we need to use our multiplication routine from last time. But wait! As you recall, the last program used a specially modified multiplication table. To get a wider range of numbers to multiply we will need another set of multiplication tables -- no big whoop. Now, if you review the multiplication routine from last time, it adds two numbers and subtracts two numbers. What kinds of numbers will we be dealing with? The matrix elements vary between -64..64. This then fixes our range of polygon coordinates from -64..64. Why? If the matrix element is 64, and we multiply it by 64, the multiplication routine will add 64 and 64 and get 128, which is right on the edge of our multiplication table. Can we improve this rotation process in any way? In fact, we can cut down on the number of multiplications (i.e. do eight or even seven instead of nine multiplications). However, there is a fair amount of overhead involved in doing so, and our multiply routine is fast enough that the extra overhead and complexity really gain us very little in all but the most complicated of polygons. In other words, I didn't bother. What about hidden faces? Again, from last time you may recall that a method was described which used the cross-product of the projected vectors. How do we implement this in the program? Well, if we take the first three points of the polygon, we have two vectors. Let's say these points are P1 P2 and P3. Then V1=P1-P2 and V2=P3-P2 are two vectors in the plane of the polygon which are connected at the point P2 (this analysis will of course only work if the polygon lies in some plane). Depending on how we take the cross product, the sign will be positive or negative, and this will tell us if the polygon is visible. Depending on how we take the cross product? Absolutely. v1 x v2 = -v2 x v1. What it really boils down to is how you define the points in your polygon. Specifically, what order they are in. Points that are specified in a clockwise manner will give a face pointing in the opposite direction of a polygon with the same points specified in a counter-clockwise order. In my program, the polygons must be entered in counter-clockwise order (with you facing the polygon) for hidden faces to work the way you want them to ;-). One other neat thing to have is the ability to zoom in and out. We know from the very first article that zooming corresponds to multiplying the projected points by a number, so that's what we'll do. The multiplication routine returns A=A*Y/64, so a zoom factor of 64 would be like multiplying the point by one. All the program does is multiply the projected points by a number zoom, unless zoom=64, in which case the program skips the zoom multiply. Be warned! No checks of any sort are made in the program, so you can zoom at your own risk! The important things to remember are: when entering polygons, make sure the numbers range from -64 to 64, and that you enter points in counterclockwise. Our triangle example above really should have been entered as, say, 3 -64 0 0 64 0 0 0 64 0 Filled Faces -- Using an EOR buffer ----------------------------------- Well we still have one thing left, which was alluded to in the previous article: using EOR to make a filled face. Some possible difficulties were raised, but when you plot a single polygon at a time, the problem becomes vastly simplified. First I should perhaps remind you what exclusive-or is: either A or B, but not both. So 1 EOR 0 = 1, as does 0 EOR 1, but 0 EOR 0 = 0 and 1 EOR 1 = 0. As a simple introduction to using this for filling faces, consider the following piece of the drawing buffer: 00001011 M1 00000000 M2 00000001 M3 00001010 M4 Lets say we move down memory, EORing as we go. Let M2 = M1 EOR M2. Then let M3 = M2 EOR M3. Then let M4 = M3 EOR M4. Our little piece of memory is now: 00001011 M1 00001011 M2 00001010 M3 00000000 M4 What just happened? Imagine that the original memory was a series of pieces of line segments. We have just filled in the area between the two line segments, like magic! If you still aren't getting it, draw a large section of memory, and then draw an object in it, like a triangle, or a trapazoid, and EOR the memory by hand, starting from the top and moving downwards. EOR flips bits. If you start with a zero, it stays zero until it hits a one. It will then stay one until it hits another one. So you can see that if you have an object bounded by ones, EORing successive memory locations will automagically fill the object. Right? Well, we have to be careful. One major problem is a vertical line: 1 1 1 goes to 0 1 1 1 0 Not only is the resultant line dashed, but if there are an odd number of points in the line segment, the last one will happily move downwards in memory, and give you a much longer vertical line than you expected! Since any line with slope greater than one is made up of a series of line segments, this is a major consideration. Another problem arises with single points: a one just sitting all by itself will also generate a nice streak down your drawing area. If you think about it, what we ideally want to have is an object that at any given value of x there are exactly two points, one defining the top of the object, and the other defining the bottom. This gives us the insight to solve the above two problems. First let's think about vertical lines. In principle we could plot the first and last endpoints of each vertical line chunk, but that is exactly what we don't want! Remember that these are closed polygons, which means that there are _two_ lines we need to think about. If I plot just a single point in each vertical line segment, there must be another point somehwere, either above or below it, from another line segment, which will close the point to EOR-filling. Remember, we want exactly two points at each value of x: one will come from the line, and the other will come from the other line which must lie above or below the current one. Furthermore, with any convex polygon there are exactly two lines which come together at each vertex of the polygon. This means that there are only certain cases which we need to worry about. For instance, two lines might join in any of the following ways: \ / \ / \ / \ / \_____ _____/ \/ etc. If you draw out the different cases involving vertical lines, you can see that you have to be careful about plotting the lines. One tricky one is where two vertical lines with different slopes overlap at the point of intersection. So after staring at these pictures for a while, you can find a consistent method which solves these difficulties. As long as you follow the following rules, the problems all disappear; the line routine needs to be modified slightly: 1) When plotting a vertical line (i.e. big steps in Y direction), don't plot the endpoints (i.e. x1,y1 and x2,y2). 2) When plotting a vertical line, consistently plot either the first part of each chunk or the last part of each chunk (excluding the endpoints of course). In other words, only plot a point when you take a step in x, and then plot one and only one point. Now I deduced these by staring at pictures for a few hours and trying different things like top/bottom of chunk, left/right, first/last, etc. You can see that in some cases this ensures that only one point appears on a given line segment. But to me the only way to convince yourself that this really does work is to draw a bunch of pictures, and try it out! You have cases where two vertical lines intersect, and where a vertical line intersects a horizontal line. But there is still one thing which we have forgotten -- the case of a single point. This can happen in, for instance, a pointy triangle, pointing in the x-direction. How do we fix this? By simply avoiding the point: in the line drawing routine, use EOR to plot the points instead of ORA. Since vertical lines skip the endpoints, vertical-horizontal intersections are OK. Horizontal- horizontal intersections will force the point of intersection to be zero. Uh-oh, what about intersections like -----*------. Quite frankly I just thought of it, and I think my program will fail on intersections like these. Drat. Well, that just gives us something for next time! One other thing needs to be mentioned: for EOR-filling to be useful you need to draw the polygon in a special buffer, and then EOR this buffer into the main display buffer. If you try to EOR the display buffer directly you are going to have a whole heap of trouble, such as the concerns raised last time. Finally, this gives a simple way of filling with patterns instead of boring monocolor. Instead of EOR (EORBUF),Y : ORA (DRAWBUF),Y you can use EOR (EORBUF),Y : AND PATTERN,Y : ORA (DRAWBUF),Y (as long as you preserve the original EOR (EORBUF),Y). Well I am extremely tired and I hope Craig hasn't sent out C=Hacking without me! I hope you have fun playing with the program, and I would be very interested in seeing any neat geometric shapes you might design! Program notes: -------------- - Hidden faces defaults to "on". If you enter a shape and a black screen comes up, hit 'h' to turn off hidden faces (you probably entered the polygon clockwise). - There is no pattern filling -- just simple EOR with a twist: the EOR buffer is EOR'd into the drawing buffer. - You might start hosing memory if you zoom too large. SLJ 6/15/95 Addendum -------- Stephen Judd sjudd@nwu.edu Last time we put a circle into the 2D graphics toolbox. Chris McBride has pointed something out to me about the algorithm, which makes it complete. As you may recall, the algorithm gave a very squarish circle for small radii. Chris told me that setting the initial counter value to R/2, instead of R, gave a perfect circle. What is going on? If you recall the algorithm, we are computing a fractional quantity, and when that quantity becomes larger than one, we decrease X. Wouldn't it be a whole lot smarter to round off that fraction instead of truncate it? Of course it would, and that is what starting the counter at R/2 does. So, to update the previous algorithm, A should be initialized to R/2 instead of R, which means that we change LDA R to LDA R LSR for a perfect circle every time. begin 666 cube3d3.2.s M ' J*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*@TJH*"@H*"@H*"@ MH*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'-415!(14Z@:E5$1*"@H*"@H*"@ MH*"@H*"@H*"@*@TJH&=%3U)'1:!T05E,3U*@H*"@H*"@H*"@H*"@H*"@*@TJ MH'-405)4140ZH#@04Y$H&E-4%)/5D5$(:"@H*"@ M*@TJH&Y/5Z!7251(H$9!4U1%4J!23U5424Y%4RR@H*"@*@TJH$A)1$1%3J!3 M55)&04-%4RR@1DE,3$5$H*"@H*"@*@TJH$9!0T53+*!!3D2@15A44D&@5$]0 MH%-%0U)%5*"@*@TJH%1%6%2@34534T%'15,AH*"@H*"@H*"@H*"@H*"@*@TJ MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S+C"@*Z!F05-4 MH$-(54Y+6:!,24Y%H*"@H*"@*@TJH%)/551)3D4NH*"@H*"@H*"@H*"@H*"@ MH*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S M+C&@*Z!G14Y%4D%,H%!/3%E'3TZ@4$Q/5*"@*@TJH%=)5$B@2$E$1$5.H$9! M0T53H"AX+5!23T150U0I*@TJH$%.1*!:3T]-H$9%05154D4NH*"@H*"@H*"@ MH*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S+C*@ M*Z!E;W(M0E5&1D52H$9)3$Q)3D>@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@ MH*"@H*"@H*"@H*"@*@TJH'1(25.@4%)/1U)!3:!)4Z!)3E1%3D1%1*!43Z"@ M*@TJH$%#0T]-4$%.6:!42$6@05)424-,1:!)3J"@H*"@*@TJH&,]:$%#2TE. M1RR@:E5.+J Y-:!)4U-512Z@H*"@*@TJH&9/4J!$151!24Q3H$].H%1(25.@ M4%)/1U)!32R@*@TJH%)%042@5$A%H$%25$E#3$4AH*"@H*"@H*"@H*"@*@TJ MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'=2251%H%1/H%53 M(:"@H*"@H*"@H*"@H*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@ MH*"@H*"@*@TJH&U94T5,1J!72$5.H%E/54Y'H$1)1*"@H*"@H*"@*@TJH$5! M1T523%F@1E)%455%3E2@H*"@H*"@H*"@H*"@*@TJH&1/0U1/4J!!3D2@@0UE-0D%,+J"@H*"@ MH*"@H*"@*@TJH*"@H"V@,:!C3U))3E1(24%.4Z Q,Z"@H*"@H*"@*@TJH*"@ MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH' N@0:!,24Y%#7DQ(&5Q=2 D9F,@ M.W1(15-%H%I%4D^@4$%'1:!!1$1215-315,->#(@97%U("1F9" [1$].)U2@ M0T].1DQ)0U2@5TE42*!B87-I8PUY,B!E<74@)&9E#6]L9'@@97%U("1F9 UC M:'5N:R!E<74@)&9E#61X(&5Q=2 D-C<@.W1(25.@25.@4TA!4D5$H%=)5$B@ M=#&@0D5,3U<-9'D@97%U("0V. UT96UP,2!E<74@)&9B(#MO1J!#3U524T4L MH$-/54Q$H$-/3D9,24-4H%=)5$B@6#$-=&5M<#(@97%U("1F8R [=$5-4$]2 M05)9H%9!4DE!0DQ%4PUZ=&5M<"!E<74@)# R(#MU4T5$H$9/4J!"549&15*@ M4U=!4"Z@H&1/3B=4H%1/54-(+@UZ,2!E<74@)#(R(#MU4T5$H$)9H$U!5$B@ M4D]55$E.10UZ,B!E<74@)#(T(#MD3TXG5*!43U5#2*!42$531:!%251(15(A M#7HS(&5Q=2 D,C8->C0@97%U("0R. UK(&5Q=2 D8C8@.V-/3E-404Y4H%53 M142@1D]2H$A)1$1%3@T@(" [4U521D%#1:!$151%0U1)3TZ@+:!$3TXG5*!4 M3U5#2 UH:61E(&5Q=2 D8C4@.V%21:!355)&04-%4Z!(241$14X_#69I;&P@ M97%U("0U," [85)%H%=%H%5324Y'H&5O&UA>" ]("0T," [1%)!5TE. M1Z H1TQ/0D%,*:!"549&15(-9VQO8GEM:6X@/2 D-#$-9VQO8GEM87@@/2 D M-#(-;&]C>&UI;B ]("0U-R [=$A%4T6@05)%H%53142@24Z@0TQ%05))3D>@ M5$A%#6QO8WAM87@@/2 D-3@@.V5OB ]("0Y- UP,G@@/2 D.34@.W1(15F@05)%H$A% M4D6@4T^@5$A!5*!710UP,GD@/2 D.38@.T1/3B=4H$A!5D6@5$^@4D5#04Q# M54Q!5$6@5$A%32X-<#)Z(#T@)&%E#7 S>" ]("1A9B [=$A%6:!-04M%H$Q) M1D6@14%362X-<#-Y(#T@)&(P#7 S>B ]("1B,2 [=TA9H$%21:!93U6@3$]/ M2TE.1Z!!5*!-1:!,24M%H%1(050_#7 Q=" ]("1B,B [9$].)U2@64]5H%12 M55-4H$U%/PUP,G0@/2 D8C,-<#-T(#T@)&(T(#MH059)3D>@04Y/5$A%4J!# M2$E,1*!705-.)U2@35F@241%02X-:6YD97@@/2 D-3$-8V]U;G1P=',@/2 D M-3(->F]O;2 ]("0W,2 [>D]/3:!&04-43U(-9'-X(#T@)#8Q(#MD2 ] M("0V,B [@UC,3,@/2 D83<-9#(Q(#T@)&$X(#MT2$6@ M3E5-0D52H$1%3D]415.@*%)/5RQ#3TQ534XI#64R,B ]("1A.0UF,C,@/2 D M86$-9S,Q(#T@)&%B#6@S,B ]("1A8PUI,S,@/2 D860-#0TJ*BJ@;4%#4D]3 M#0UM;W9E(&UA8PT@;&1A(%TQ#2!S=&$@73(-(#P\/ T-9V5T:V5Y(&UA8R @ M.W=!252@1D]2H$&@2T594%)%4U,-=V%I="!J" Y M,S U,3$Q,3$Q(#M#3$5!4J!30U)%14XLH%=(251%+*!#4E-2H$1.#2!T>'0@ M)Z"@H*"@H*"@H*"@H*!#54)%,T2@5C,N,B" Y.0T@='AT(">@H*"@1T5/4D=%H%1!64Q/4B" Y8@T@='AT(">@H$-(14-+H$]55*!42$6@2D%.+J Y-:!) M4U-51:!/1B" Y-@T@='AT(">@H$,]2$%#2TE.1R<-(&AE>" Y M8@T@='AT(">@1D]2H$U/4D6@1$5404E,4R$G+#!$#2!H97@@,&0Q9#%D.64Q M,@T@='AT("=&,2]&,B'0@)Z K+RV@)RPY,@T@='AT(">@+:!:3T]-H$E.+T]55"" Q9#%D,3(-('1X=" GH*!(H* G+#DR#2!T>'0@)Z MH%1/1T=,1:!( M241$14Z@4U521D%#15,G+#!$#2!H97@@,60Q9#$R#2!T>'0@)U-004-%)RPY M,@T@='AT(">@+:!43T='3$6@4U521D%#1:!&24Q,24Y')RPP1"PP1 T@='AT M(">@H%!215-3H%&@5$^@455)5"" P9# U#2!T>'0@)Z"@H*"@ MH%!215-3H$%.6:!+15F@5$^@0D5'24XG+#!$#2!H97@@,# -=&ET;&4@;&1A M("AT96UP,2DL>0T@8F5Q(#IC;VYT#2!J0T- M*BHJ*J!S152@55"@5$%"3$53*#\I#0TJH'1!0DQ%4Z!!4D6@0U524D5.5$Q9 MH%-%5*!54*!)3J!B87-I8PTJH$%.1*!"6:!42$6@05-314U"3$52+@T-=&%B M;&5S(&QD82 C/G1M871H,0T@C0K,0T-*BHJ*J!C3$5!4J!30U)%14Z@ M04Y$H%-%5*!54* B0DE434%0(@US971U<"!L9&$@(R0P,2 [=TA)5$4-('-T M82 D9# R,2 [=$A)4Z!)4Z!$3TY%H%-/H%1(052@3TQ$15(-(&QD82 C,30W M(#M-04-(24Y%4Z!724Q,H%-%5*!54 T@:G-R(&-H@.0T@;&1A(",P, T@;&1Y(",P, T@ M;&1X(",P," [6*!724Q,H$-/54Y4H#$VH%)/5U.@1D]2H%53#2!C;&,-#3IL M;V]P('-T82 H=&5M<#$I+'D-(&EN>0T@861C(",Q-@T@8F-C(#IL;V]P#2!C M;&,-(&QD82!T96UP,0T@861C(",T," [;D5%1*!43Z!!1$2@-#"@5$^@5$A% MH$)!4T6@4$])3E1%4@T@&$@(#MXH$E3H$%, M4T^@04Z@24Y$15B@24Y43Z!42$6@0TA!4D%#5$52H$Y534)%4@T@8W!X(",Q M-@T@8FYE(#IL;V]P(#MN145$H%1/H$1/H$E4H#$VH%1)3453#0TJ*BHJH&-, M14%2H$)51D9%4E,-#2!L9&$@(SQB=69F,0T@" C,C0@.V%34U5- M24Y'H$%,3*!42%)%1:!"549&15)3H$%210T@;&1A(",D,# @.T)!0TLM5$\M M0D%#2PTZ8FQO;W @0T@8FYE(#IB;&]O< T@ M:6YC(&)U9F9E6UI;@T@&UI M;@T@6UA> T@ M T@0T@@T@@T@F]O;0T-*BTM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T-*J!M M04E.H$Q/3U -#2HJ*BJ@9T54H$M%65!215-3#0UM86EN#2!C;&D-:W!R97-S M(&IS T@ M8VUP("-A;F=M87@O,B [;D^@34]21:!42$%.H%!)#2!B97$@.F-O;G0Q#2!I M;F,@9'-X(#M/5$A%4E=)4T6@24Y#4D5!4T6@6"U23U1!5$E/3@T@:FUP(#IC M;VYT#3IF,B!C;7 @(S$S-R [9C(_#2!B;F4@.F8S#2!L9&$@9'-X#2!B97$@ M.F-O;G0Q#2!D96,@9'-X#2!J;7 @.F-O;G0-.F8S(&-M<" C,3,T#2!B;F4@ M.F8T#2!L9&$@9'-Y#2!C;7 @(V%N9VUA>"\R#2!B97$@.F-O;G0Q#2!I;F,@ M9'-Y(#MI3D-214%31:!9+5)/5$%424].#2!J;7 @.F-O;G0-.F8T(&-M<" C M,3,X#2!B;F4@.F8U#2!L9&$@9'-Y#2!B97$@.F-O;G0Q#2!D96,@9'-Y#2!J M;7 @.F-O;G0-.F8U(&-M<" C,3,U#2!B;F4@.F8V#2!L9&$@9'-Z#2!C;7 @ M(V%N9VUA>"\R#2!B97$@.F-O;G0Q#2!I;F,@9'-Z(#M:+5)/5$%424].#2!J M;7 @.F-O;G0-.F8V(&-M<" C,3,Y#2!B;F4@.F8W#2!L9&$@9'-Z#2!B97$@ M.F-O;G0Q#2!D96,@9'-Z#2!J;7 @.F-O;G0-.F8W(&-M<" C,3,V#2!B;F4@ M.G!L=7,-(&IM<"!I;FET#3IC;VYT,2!J;7 @.F-O;G0-.G!L=7,@8VUP(",G M*R<-(&)N92 Z;6EN=7,-(&EN8R!Z;V]M(#MB04@LH%=(3Z!.145$4Z!%4E)/ M4J!#2$5#2TE.1S\-(&EN8R!Z;V]M#2!J;7 @.F-O;G0-.FUI;G5S(&-M<" C M)RTG#2!B;F4@.F@-(&1E8R!Z;V]M#2!D96,@>F]O;0T@8G!L(#IC;VYT#2!I M;F,@>F]O;0T@:6YC('IO;VT-(&IM<" Z8V]N= TZ:"!C;7 @(R=()PT@8FYE M(#IS<&%C90T@;&1A(&AI9&4-(&5O@)PT@8FYE(#IQ#2!L9&$@9FEL; T@96]R(",D M,#$-('-T82!F:6QL#2!J;7 @.F-O;G0-.G$@8VUP(",G42<@.U&@455)5%,- M(&)N92 Z8V]N= T@:FUP(&-L96%N=7 -#3IC;VYT('-E:2 @.W-0145$H%1( M24Y'4Z!54*!!H$))5 T-*BHJ*J!U4$1!5$6@04Y'3$53#0UU<&1A=&4@8VQC M#2!L9&$@ T@8VQC#2!L9&$@" [ T@8F-C(#IC;VYT,PT@@T- M*BHJ*J!R3U1!5$6@0T]/4D1)3D%415,-#7)O=&%T90T-*BHJH&9)4E-4+*!# M04Q#54Q!5$6@5#$L5#(L+BXN+%0Q, T-*BJ@=%=/H$U!0U)/4Z!43Z!324U0 M3$E&6:!/55*@3$E&10UA9&1A(&UA8R @.V%$1*!45T^@04Y'3$53H%1/1T54 M2$52#2!C;&,-(&QD82!=,0T@861C(%TR#2!C;7 @(V%N9VUA>" [:5.@5$A% MH%-53: ^H#(J4$D_#2!B8V,@9&]N90T@" [;T]04RR@5T6@3D5%1*!43Z!!1$2@,BI020UD;VYE(#P\/ T- M*BJ@;D]7H$-!3$-53$%41:!4,2Q4,BQ%5$,N#0T@/CX^('-U8F$L#MS>@T@#MT,0T@3MS M> T@@250G4Z!#3TU0 M3$5-14Y4#2!L@0:!-24Y/ M4J!,14%0#2HJH$]&H$9!251(H%1(052@3D^@3U9%4D9,3U=3H%=)3$R@3T-# M55(N#0TZ8V%L8V$@8VQC#2!L9'@@=#$-(&QD82!C;W,L> T@;&1X('0R#2!A M9&,@8V]S+'@-('-T82!A,3$@.V$]*$-/4RA4,2DK0T]3*%0R*2DO,@TZ8V%L M8V(@;&1X('0Q#2!L9&$@"!T-PT@"!T-0T@"!T-@T@861C(&-O M T@;&1X('0V#2!S8F,@ M T@8VQC#2!L M9'@@=#0-(&%D8R!C;W,L> T@ T@"!T-@T@;&1A('-I;BQX#2!S96,-(&QD>"!T. T@"!T-PT@"!T-0T@ T@;&1X('0W#2!A M9&,@8V]S+'@-('-E8PT@;&1X('0U#2!S8F,@8V]S+'@-('-E8PT@;&1X('0X M#2!S8F,@8V]S+'@@.VA)/2A#3U,H5#8I*T-/4RA4-RDM0T]3*%0U*2U#3U,H M5#@I*2\R#2 ^/CX@9&EV,@T@8VQC#2!L9'@@=#,-(&%D8R!S:6XL> T@8VQC M#2!L9'@@=#0-(&%D8R!S:6XL> T@"!T M,3 -(&%D8R!C;W,L> T@2 C,# -.F1O<&4@0T@8FYE(#ID;W!E#2!I;F,@8G5F9F5R*S$-(&1E> T@8FYE(#IF;V]L M#0TJ*BHJH&U9H$=/3T1.15-3H$)55*!I)TV@0:!$3U!%#2IC;')D&UI;@TJH&QS&UA> TJH'-T8:!G;&]B>6UA> TJH&QD M8: C)&9F#2J@@4$],64=/3E,-#7)E861D2 C,# - M('-T>2!I;F1E> UO8FIL;V]P(&QD>2!I;F1E> T@;&1A('!O;'EL:7-T+'D@ M.V9)4E-4+*!42$6@3E5-0D52H$]&H%!/24Y44PT@8FYE(#IC;VYT(#MB552@ M24:@3E5-4$])3E13H$E3H%I%4D^@5$A%3@T@:FUP(&]B:F1O;F4@.U=%H$%2 M1:!!5*!42$6@14Y$H$]&H%1(1:!,25-4#3IC;VYT('-T82!C;W5N='!T&UI;@T@8VUP(&=L;V)X;6EN#2!B8W,@.FYA: T@ M6UI;@T@ M8F-S(#IU:'5H#2!S=&$@9VQO8GEM:6X-.G5H=6@@;&1A(&QO8WAM87@-(&QS M<@T@;'-R#2!L&UA> TZ;F]W87D@;&1A(&QO8WEM87@-(&-M<"!G M;&]B>6UA> T@8F-C(&5O6UA> T-*J!I1J!54TE. M1Z!42$6@96]R+4)51D9%4BR@0T]06:!)3E1/H$1205=)3D>@0E5&1D52#2J@ M84Y$H%1(14Z@0TQ%05*@5$A%H&5O&UI;B [;&]C>&UI;J!.3U>@0T].5$%)3E.@0T],54U.#2!L2!B=69F97(-('-T>2!T96UP M,0T@8VQC#3IE=F5N('-T82!T,@T@861C(&)U9F9E" [ M=$]404R@3E5-0D52H$]&H$-/3%5-3E.@5$^@1$\-(&EN>" @.T4N1RZ@1DE, M3*!#3TQ534Y3H#$N+C,-(&QD>2!L;V-Y;6%X#2!B;F4@.F9O;W -(&EN8R!L M;V-Y;6%X#3IF;V]P(&QD>2!L;V-Y;6%X#2!L9&$@(S P#3IG;V]P(&5O0T@0T@8W!Y(&QO8WEM:6X-(&)C T@8FYE(#IF M;V]P#2!J;7 @;V)J;&]O< T-;V)J9&]N90TJ*BHJH'-705"@0E5&1D524PT- M@0E5&1D52H"AE;W*@3U*@3D]234%,*2X-#7)O='!R M;VH-#2J@8:!.14%4H$U!0U)/#6YE9R!M86,@(#MC2$%.1T6@5$A%H%-)1TZ@ M3T:@0:!45T\G4Z!#3TU03$5-14Y4#2!C;&,-(&QD82!=,2 [3E5-0D52+@T@ M96]R(",D9F8-(&%D8R C)# Q#2 \/#P-#2HM+2TM+2TM+2TM+2TM+2TM+2TM M+2TM+2TM+2TM+2TM#2J@=$A%4T6@34%#4D]3H%)%4$Q!0T6@5$A%H%!2159) M3U53H%!23TI%0U1)3TX-*J!354)23U5424Y%+@T-C0-(&QD8: H>C,I+'D-('-E8PT@0T@/#P\H* [84Q, MH$1/3D6@.BD-#7-M=6QT>B!M86,@.VU53%1)4$Q9H%173Z!324=.142@."U" M250-(" @.TY534)%4E,ZH&$J>2\V-* M/J!A#2!S=&$@>C$-(&-L8R @.V%. M1*!42$E3H$U53%1)4$Q9H$E3H%-014-)1DE#04Q,60T@96]R(",D9F8@.T9/ M4J!42$6@4%)/2D5#5$E/3J!005)4+*!72$5210T@861C(",D,#$@.TY534)% M4E.@05)%H"TQ,3 N+C$Q,*!!3D2@,"XN-# -('-T82!Z,@T@;&1A("AZ,2DL M>0T@