User Tools

Site Tools

         ######            ######
    #####  ####  ####      ##      #####   ####  ####  ####  ####  ####   #####
  #####    ##    ##      ####    ##   ##   ##  ###     ##    ####  ##   ##   ##
 #####    ########     ##  ##   ##        #####       ##    ## ## ##   ##
#####    ##    ##    ########  ##   ##   ##  ###     ##    ##  ####   ##   ##
#####  ####  ####  ####  ####  #####   ####  ####  ####  ####  ####   ######
#####                                                                     ##
 ######            ######           Issue #9
   ##################            Jan. 24, 1995


Editor's Notes

by Craig Taylor

And *drum beat please* here's another issue of Commodore Hacking!! We've
lasted longer and had more issues put out than some other magazines I won't
discuss (*wondering where issue 39 of that mag is*).

Not many Commodore notes this time - things have gotten a little bit more
montatenous(sp - it's late) on the Commodore front.

I was unable to get an article by Craig Bruce in time but I got the next
best thing: An interview with him!! Users of his software may find this
interview interesting in how he looks at programming.

Right now I'm entertaining the thought of dropping C= Hacking after I
graduate which will be sometime around July 1st of this year. I'm interested
in somebody who would "carry the reign" so to speak, and take over my job of
nagging, bugging people etc :-) to write articles. I've got my system fairly
automated in handling requests here - if that person has a VAX account then
I could set them up with a mailserver, if a UNIX account then there's oodles
of them floating on the net that could be used. _PLEASE_ write to me and
lemme know if you're interested. I'm going to try to get one more issue of
Commodore Hacking out before July 1st.


Please note that this issue and prior ones are available via anonymous FTP
from (amongunders) under /pub/cbm/hacking.mag and via a
mailserver which documentation can be obtained by sending mail to
"" with a subject line of "mailserver" and the
lines of "help" and "catalog" in the body of the message.

Legal Mumbo-Jumbo

Permission is granted to re-distribut3e this "net-magazine", in whole,
freely for non-profit use. However, please contact individual authors for
permission to publish or re-distribute articles seperately. A charge of no
greater than 5 US dollars or equivlent may be charged for library service /
diskettte costs for this "net-magazine".


In This Issue

Commodore Trivia Corner

This edition of Commodore Trivia Corner contains the answers to the July
edition of trivia ($070 - $07F), the questions and answers for August ($080 -
$08F), September ($090 - $09F), October ($0A0 - $0AF), November ($0B0 - $0BF),
and the questions for the December edition ($0C0 - $0CF).  Enjoy them!

A Different Perspective, part II

This month George and Steve continue their series on 3D graphics on the C-64
with a look at hidden faces and filled faces.  In addition to adding these
features into last month's program some other improvements to the old program
will be discussed, such as fast multiplication (around 24 cycles) and various
bug fixes -- for instance, the program now works on older C-64's which
initialize color RAM to the background color when the screen is cleared (sorry
about that ;-).  A very primitive form of texture mapping is also included. As
usual, full source and executables are included.  The native C64 files are in a
Lynx archive, so you will obviously need Lynx to get at them -- check your
favorite BBS or FTP site.

2D Graphics Toolbox: Circles

To augment three-dimensional algorithms this series will focus on
two-dimensional drawing algortihms.  Circles are the subject this
time around (heh -- get it?), and a very fast algorithm for drawing
them on your C64 is presented, with examples in assembly and BASIC7.0.
How fast is fast?  How does 11 cycles per pixel without the use of
tables grab ya?

AFLI=specs v1.0

In AFLI we can get 120 colors in theory (counted like this
16!/(2!*14!)=120). When we put red and blue hires pixels close to
each other we get a vision of purple - thanks the television. This article
details what AFLI is, how it's used and done.

Coding Tricks

Included are a series of postings to comp.sys.cbm about neat coding tricks (in
machine language) that are interesting and useful.

C.S.Bruce Interview

An interview with the author of Zed, the ACE os and many other numerous
utilities for the Commodore 64/128.

Aligning 1541 Drives

A discussion regarding Commodore 1541 disk drive alignment procedures, with

Commodore Trivia Corner

by Jim Brain (

Well, it is a new year, and I am sending up a new collection of the
Commodore rivia for all to enjoy.  If you haven't seen this already, the
following is a collection of trivia questions that I post to various
networks every month.  I have collected Trivia Edition #8-13 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 new year rolls in, I am happy to report the following:

1) As I have gained access to FIDONet, the trivia is now posted to both
   the USENET newsgroup COMP.SYS.CBM on the Internet AND the FIDONet echo
   CBM every month.

2) A number of publications have started publishing the trivia, including
   Commodore World, and a variety of club newsletters.

3) I have moved into my new house (See new address at bottom).  While this
   may not seem important, the extra room I now have means I can now bring
   all of my old CBM machine to the new house and work with them.  Working
   with them gives me fodder for more trivia.

As always, I welcome any questions (with answers), and encourage people
to enter their responses to the trivia, now at #13.  Be sure you get the
responses to me by January 12th at noon.


The following article contains the answers to the July edition of trivia
($070 - $07F), the questions and answers for August ($080 - $08F), September
($090 - $09F), October ($0A0 - $0AF), November ($0B0 - $0BF), and the
questions for the December edition ($0C0 - $0CF).  Enjoy them!

Here are the answers to Commodore Trivia Edition #8 for July, 1994

Q $070) On a PET series computer, what visual power-on indication will tell
        the user whether the computer has Revision 2 or Revision 3 ROMs?

A $070) Revision Level 2 ROMS (the ones with more bugs) power up with:
        *** COMMODORE BASIC ***, with '*' in place of the more familiar
        '#' character.

Q $071) The IEEE-488 interface is sometimes called the GPIB interface.
        What does GPIB stand for?

A $071) General Purpose Interface Bus.  Another name is Hewlett Packard
        Interface Bus (HPIB), since HP developed this standard for its
        istrumentation device networking.

Q $072) Commodore manufactured at least two hard drives with IEEE-488
        interfaces.  Can you name them?

A $072) The Commodore D9060 and D9090. From the cbmmodel.txt file:

        * CBM D9060  5 MB Hard Drive, DOS3.0, Off-White, IEEE-488.       GP
        * CBM D9090  7.5 MB Hard Drive, DOS3.0, Off-White, IEEE-488.     GP

        The following model has been said to be in existence, though no one
        has one on hand to prove it:

        * CBM D9065  7.5 MB Hard Drive

        And this model may never have made it past the prototype stage:

          CBM D9062  Dual D9065.

Q $073) Why didn't buyers like the original PET-64?

A $073) It looked just like a old-style C-64.  It had a "home" computer
        look that the schools didn't care for.  They liked the "business"
        look of the PET series, so Commodore put refurbished and new 64
        motherboards in PET cases and sold them as PET 64s.  The repackaging
        suited the schools.

Q $074) On a PET Revision 2 ROM, what was the largest single array size that
        BASIC could handle?

A $074) An array can have a cumulative total of 256 elements.  For single
        dimension arrays, that means D(0) to D(255), but a 2D array can only
        go from DD(0,0) to DD(1,127) etc.  All types of arrays had this

Q $075) On the stock 1541, data is transmitted one bit at a time.  How many
        bits are transferred at a time on the Commodore 1551 disk drive?

A $075) 3 bits were transmitted at a time.  I assume that each byte had a
        parity bit tacked on for error detection, so it would have taken
        3 transfers to transmit a byte of information from the drives.

Q $076) On all Commodore floppy disk drives, how fast does the disk spin?

A $076) 300 RPM.

Q $077) Upon first reading the Commodore 1541 Error channel after turning
        on the disk drive, what error number and text is returned?

A $077) 73, CBM DOS V2.6 1541, 0, 0

Q $078) What error number and text is returned on a 1551?

A $078) 73, CBM DOS V2.6TDISK, 0, 0   Notice that the new text JUST fits!

Q $079) Commodore printers are normally assigned to device #4, but they
        can be also used as device #?

A $079) #5.  The Commodore 1525 has a switch to do this, but not all printers
        have such a switch.

Q $07A) What microprocessor is used in the Commodore 1551 disk drive?

A $07A) the 6510T.  It is a slight variant on the 6510 microprocessor used
        on the C64.  Some say it runs at 2 MHz, but the specs drives spec
        sheet doesn't say.

Q $07B) When the VIC-20 was designed, the serial port throughput was roughly
        equivalent to the throughput of the IEEE-488 bus?  Why isn't it
        very fast in production VICs?

A $07B) Let's go back to question $04F:

        <begin insert>
        Q $04F) What was the primary reason Commodore went to a serial bus
                with the introduction of the VIC-20?

        A $04F) Jim Butterfield supplied me with this one:

                As you know, the first Commodore computers used the IEEE bus
                to connect to peripherals such as disk and printer.  I
                understand that these were available only from one source:
                Belden cables.  A couple of years into Commodore's computer
                career, Belden went out of stock on such cables (military
                contract? who knows?).  In any case, Commodore were in quite
                a fix:  they made computers and disk drives, but couldn't
                hook 'em together! So Tramiel issued the order:  "On our next
                computer, get off that bus.  Make it a cable anyone can
                manufacture".  And so, starting with the VIC-20 the serial
                bus was born.  It was intended to be just as fast as the
                IEEE-488 it replaced.
        <end insert>

        And here is what Jim Butterfield followed up with:

        "Technically, the idea was sound:  the 6522 VIA chip has a "shift
        register" circuit that, if tickled with the right signals (data and
        clock) will cheerfully collect 8 bits of data without any help from
        the CPU.  At that time, it would signal that it had a byte to be
        collected, and the processor would do so, using an automatic
        handshake built into the 6522 to trigger the next incoming byte.
        Things worked in a similar way outgoing from the computer, too.
        We early PET/CBM freaks knew, from playing music, that there was
        something wrong with the 6522's shift register:  it interfered with
        other functions.  The rule was:  turn off the music before you start
        the tape!  (The shift register was a popular sound generator).  But
        the Commodore engineers, who only made the chip, didn't know this.
        Until they got into final checkout of the VIC-20.

        By this time, the VIC-20 board was in manufacture.  A new chip could
        be designed in a few months (yes, the silicon guys had application
        notes about the problem, long since), but it was TOO LATE!

        A major software rewrite had to take place that changed the VIC-20
        into a "bit-catcher" rather than a "character-catcher".  It called for
        eight times as much work on the part of the CPU; and unlike the shift
        register plan, there was no timing/handshake slack time.  The whole
        thing slowed down by a factor of approximately 5 to 6.

        When the 64 came out, the problem VIA 6522 chip had been
        replaced by the CIA 6526.  This did not have the shift register
        problem which had caused trouble on the VIC-20, and at that time it
        would have been possible to restore plan 1, a fast serial bus.  Note
        that this would have called for a redesign of the 1540 disk drive,
        which also used a VIA.  As best I can estimate - and an article in
        the IEEE Spectrum magazine supports this - the matter was discussed
        within Commodore, and it was decided that VIC-20 compatibility was
        more important than disk speed.  Perhaps the prospect of a 1541
        redesign was an important part of the decision, since current
        inventories needed to be taken into account.  But to keep the
        Commodore 64 as a "bit-banger", a new problem arose.

        The higher-resolution screen of the 64 (as compared to the VIC-20)
        could not be supported without stopping the CPU every once in a while.
        To be exact:  Every 8 screen raster lines (each line of text), the CPU
        had to be put into a WAIT condition for 42 microseconds, so as to
        allow the next line of screen text and color nybbles to be swept into
        the chip.(More time would be needed if sprites were being used).
        But the bits were coming in on the serial bus faster than that:
        a bit would come in about every 20 microseconds!  So the poor CPU,
        frozen for longer than that, would miss some serial bits completely!
        Commodore's solution was to slow down the serial bus even more.
        That's why the VIC-20 has a faster serial bus than the 64, even though
        the 64 was capable, technically, of running many times faster.

        Fast disk finally came into its own with the Commodore 128."


Q $07C) On Commodore computers, how much RAM is set aside as a tape buffer?

A $07C) 192 bytes is used as a tape buffer.  Blocks of data on tape are 192
        bytes long.

Q $07D) On Commodore computers, most every peripheral has a device number.
        What is the device number of the screen?

A $07D) #3

Q $07E) What is the device number of the keyboard?

A $07E) #0

Q $07F) Commodore computers use 2's-complement notation to represent integers.
        What is the 2's-complement hex representation of the signle byte -1?

A $07F) (This was not a Commodore specific question)  Commodore computers
        use this notation to represent integer quantities.  In 2's complement
        notation, a -1 looks like 11111111(binary) or $FF(hex).

Here are the answers to Commodore Trivia Edition #9 for August, 1994

Q $080) During the days of the Commodore 64 and the VIC-20, Commodore
        produced at least two Commodore magazines.  What were their names?

A $080) The magazines were originally called "Commodore Microcomputers" and
        "Power/Play: Commodore Home Computing". They never did seem to nail
        down the name of the latter as I see "Power/Play" and
        "Commodore: Power/Play" used as the original names as well. Anyway,
        Commodore Microcomputers started its life in 1979, whereas
        "Power/Play" started in 1981.  Both magazines were published until
        around 1987, when they were merged to form "Commodore Magazine".
        Then, around 1990, the magazine was sold to IDG Communications and
        was merged into RUN.  RUN was continued for a while, but was finally
        pulled out of circulation.  Creative Micro Designs purchased the
        rights to the magazine, and now Commodore World is being produced by
        CMD.  I am not sure how strong (if any) a link there is between
        RUN and CW, but some of the same authors write for the new
        publication.  Just for added info, here are the ISSN numbers:

        Commodore Microcomputers (Commodore Magazine)   0744-8724
        Power/Play:Commodore Home Computing             0739-8018
        RUN (Commodore/RUN)                             0741-4285

        "The Transactor" is also a correct answer, and info on it is below.

Q $081) Back in the PET heyday, another magazine was produced by Commodore
        Canada.  This magazine was later sold and showed up as a hardware
        journal.  Name the magazine.

A $081) The infamous "Tarnsactor".  One of the noted C64 hardware-hacking
        magazines, it was originally published by Commodore Canada, before
        being sold to an individual named Mr. Hilden.  Its ISSN number is
        0838-0163.  As far as I can tell, this magazine, died many deaths,
        but ceased to exist in 1989-90.  Its first issue is dated April 30,

Q $082) The Commodore 128 has a VIC-II compatible chip inside it.  Can this
        chips be switched for a VIC-II from a Commodore 64?

A $082) No!  The newer 128 compatible chip (VIC-IIe) has 8 extra pins to
        perform timing functions specific for the 128.  In addition, some of
        the registers have extra functions.  However, a suitable card
        to make it compatible can be made.

Q $083) What does the video encoding standard PAL expand to?

A $083) Phase Alternating Line is the answer I was looking for, which
        describes the video encoding used in Europe, but Programmable Array
        Logic is also correct, which describes the family of chips used as
        "glue" logic for the C64 I/O and processing chips.

Q $084) How many buttons were present on the earliest of Commodore tape decks?

A $084) 5: Play, Rewind, Fast-Forward, Record, and Stop/Eject.  Later models
        separated the stop and eject functions into two buttons.

Q $085) Earlier SID chips had a distinctive "clicking" sound that some demo
        coders used to an advantage.  Commodore subsequently removed the
        click, and then later reintroduced it.  When does the telltale click

A $085) When you change the volume of a voice.  The voice need not be
        outputting anything.

Q $086) What does CP/M stand for?

A $086) Take your pick:

        Control Program/Monitor
        Control Program for Microprocessors
        Control Program for Microcomputers.

        The last one is considered by many to be most correct.

Q $087) What is the highest line number allowed for a program line in
        Commodore BASIC V2?

A $087) Normally, the user cannot enter a line number higher than 63999.
        If you want to be tricky, however, the numbers can be made to go up
        to 65535.

Q $088) What symbol, clearly printed on the front of a key on the Commodore
        VIC, 64, and 128 keyboard, is not available when the lower case
        character set is switched in?

A $088) The PI symbol.  It is [SHFT-UPARROW] in uppercase mode, but becomes
        a checkerboard-like character when in lower-case mode.  Unlike the
        graphics characters printed on the fronts of the keys, this one is
        positioned in the middle of the keycap, and should probably be
        accessible in both character sets.

Q $089) How do you get the "checkmark" character ?

A $089) In lowercase mode, type a shift-@

Q $08A) On the PET computers, what memory location holds the Kernal ROM

A $08A) It is different from the 64/128.  It is 50003.  0 here indicates old
        ROMs, while 1 indicates new ROMs.

Q $08B) The Commodore computers have 2 interrupts, called IRQ and NMI.
        What does IRQ stand for?

A $08B) Interrupt ReQuest.  This interrupt is used for things that should
        usually be allowed to interrupt the processor.  This interrupt can
        be masked off by the SEI instruction.

Q $08C) What does NMI stand for?

A $08C) Non-Maskable Interrupt.  Unlike the IRQ, this interrupt cannot be
        masked by an instruction.  However, some tricks can be used to
        mask it.

Q $08D) The 6502 line of microprocessors has a number of flags that can be
        used to test for certain conditions.  One of then is the N flag.
        What does it stand for?

A $08D) 'N' stands for Negative.  On instructions that change this flag, it
        is set to be equal to bit 7 of the result of the instruction.

Q $08E) How about the D flag?

A $08E) It stands for decimal mode.  This mode causes certain instructions
        to treat a byte as 2 4 bit BCD-coded nybbles.

Q $08F) The shorthand for the BASIC keyword PRINT is '?'.  What is the
        shorthand equivalent for PRINT#?

A $08F) pR is the way to abbreviate PRINT#.  Note that ?# will fail.

Here are the answers to Commodore Trivia Edition #10 for September, 1994

Q $090) The 6502 has a rich history.  It is modeled after another 8-bit
        microprocessor.  Name the processor.

A $090) The 65XX series of processors was modeled after the Motorola 6800.
        Motorola hampered the design groups' efforts to pursue product
        developments using the 6800.  A core group of 8 designers left Motorola
        and went to MOS Technologies, which was the largest producer of
        calculator chips at the time.  MOS decided it was time to go into
        the CPU business.

Q $091) The 6502 has a older brother that was never produced.  Name its
        number designation and why it was not produced.

A $091) The older brother to the 6502 was the 6501.  The 6501 was
        pin-compatible with the 6800, which prompted a suit by Motorola.
        Eventually, MOS reached an agreement where they scrapped the 6501
        marketing, but were free to market the 6502.

Q $092) How many different opcodes are considered valid and "legal" on the
        MOS NMOS 6502 line?

A $092) 151 opcodes are documented in the NMOS 6502 data book.  The remaining
        105 opcodes were not implemented, and exist as "don't care" states
        in the opcode matrix.  That means that some seemingly invalid
        opcodes will actually perform pieces of two or more valid opcodes.
        Newer CPU systems trap all non-implemented opcode usages, but not
        the 6502.

Q $093) Every instruction takes at least __ cycles to complete.  Fill in
        the missing number.

A $093) 2.  The architecture assumes that each opcode has two bytes in it and
        one byte can be fetched per cycle.  For instructions that use only
        1 byte, the extra fetched byte (actually the next opcode), is thrown

Q $094) Which instructions take more time than necessary as a result of the
        answer to Q $093?

A $094) Although this is a subjective answer, One could nominate NOP on the
        basis that NOP is generally believed to waste one execution cycle on
        a particular processor, namely one cycle on the 65XX line.  However,
        one can argue that NOP simply means no operation, and has no ties to
        length of execution.  You be the judge.

        All other instructions must take at least two cycles: one for opcode
        fetch, one for operation.

Q $095) What did MOS Technologies manufacture befor introducing the 650X line
        of microprocessors?

A $095) As stated above, it was calculator chips.

Q $096) Three companies manufactured the 6502 under a cross-licensing
        agreement.  Name them.

A $096) Rockwell, MOS Technologies, and Synertek.

Q $097) In NTSC-land, how fast does the 1MHz 6510 in the C64 actually run?

A $097) 1.022727143 MHz.  It is derived by taking the main clock frequency
        (14.31818MHz) and diving it by 14.

Q $098) What about in PAL-land?

A $098) 985.248449 kHz.  It is derived by taking the main clock frequency
        (17.734472MHz) and dividing it by 18.  Thus the PAL 64 actually runs
        slower than the NTSC one.

Q $099) Data is latched into the 650X microprocessor on the (rising/falling)

A $099) Data is latched in to the 65XX on the falling edge of Phi0 (Phi1).
        The timing diagram in some books (64 PRG is one) is incorrect.

Q $09A) Through the years, the 650X line has changed family numbers, yet
        the part has not been changed.  (A family number is the upper 2
        digits in this case)  Name the other family numbers used by MOS to
        denote the 650X line.

A $09A) the 75XX line used in the 264 series (Plus/4 and C16), and the 85XX
        series used in the C64C and C128 series.

Q $09B) Consider the following code:

        ldx #10
        lda $ff,x

        what location does the accumulator get loaded with?

A $09B) The answer is location $ff+10 mod 256 = $09.
        The answer involves explaining a (mis)features of the NMOS 65XX CPU
        line.  The above code instructs the 65XX CPU to use zero-page
        addressing mode to load the accumulator.  In zero-page addressing, the
        address need only be one byte wide ($ff in this case), because the
        high byte is considered to be $00.  Now, as humans, we would expect
        the CPU would add 10 to 255 ($ff), giving 265 ($109) as the address
        to load the accumulator from.  However, the CPU designers decided
        that zero-page addressing means that the high byte will be $00 all the
        time, no exceptions.  If a situation like the above occurs, the
        low byte of the addition will be used as the low byte of the address
        (9 in this case), but the high-byte will be ZERO.  All zero page
        addressing modes work this way.  Note that the CMOS versions of the
        6502 do perform the high byte "fix-up", so this behavior is only seen
        on the NMOS parts.

Q $09C) What about the following?

          ldx #10
          lda ($ff),x

A $09C) This was a trick.  The code is trying to use INDIRECT INDEXED indexing
        mode using the x register, but that addressing mode can only be used
        with the y register.  If the code is changed to the following, legal

          ldx #10
          lda ($ff),y

        Then, the above discussion for zero-page addressing holds true here
        as well.  The effective address would have been (hi:lo) $100:$0ff, but
        is instead (hi:lo) $000:$0ff.  The simple rule is:  zero page means
        exactly that.  There is no way to address outside of zero-page with
        zero-page addressing.

Q $09D) How many CPU clock signal lines does the 650X require to run?

A $09D) 1.  The 6501 used two, as the 6800 used two, but the 6502 and
        successors only required Phi0 (Phi1).  Phi2 was generated on the CPU.

Q $09E) Where does the 650X line fetch its first byte from after reset?

A $09E) $fffc.  The address formed by reading $fffd and $fffc is stuffed into
        the IP, and the code is read starting there.  $fffc is read first,
        since the 65XX line stores addresses in low byte, high byte format.

Q $09F) One of the original designers on the NMOS 6502 CPU now heads up
        Western Design Center in Arizona, and makes the 65C02 and 65C816
        CPU chips.  Name him.  Hint: it is not Chuck Peddle!

A $09F) Bill Mensch.  He hand-designed these newer parts in the 65XX line
        in the same manner he and Chuck Peddle and others hand-designed the
        6501 and 6502.

Here are the answers to Commodore Trivia Edition #11 for October, 1994

Q $0A0) In the mid 1980's, Commodore introduced RAM Expansion Units for the
        Commodore 64, 64C, 128, and 128D.  There were three of them.  Give
        their model numbers, and what was different among them.

A $0A0) The 1700 (128kB), the 1764 (256kB), and the 1750 (512kB).  The
        1700 and the 1750 were marketed for the 128, while the 1764 was
        marketed from the 64 line.

Q $0A1) Some of the CIA integrated circuits used on the C64 and C128
        computers have a hardware defect.  What is the result of this
        defect, and when does it occur? (May be more than one, but I need
        only one)

A $0A1) The only one I have documented in front of me is the timer B
        interrupt bug, which is explained in the "Toward 2400" article
        by George Hug in Transactor 9.3. (1)  However, I had many people
        relate other bugs (2 and 3), which I haven't been able to test, so I
        add them as possibilities. (I encourage readers to confirm/deny the
        latter 2.)

        1) If timer B of the 6526 CIA times out at about the same time as a
           read of the interrupt register, the timer B flag may not be set at
           all, and no interrupt will occur if timer B interrupts were
           turned on.

        2) When the hour on the TOD clock is 12, the AM/PM must be reversed
           from its normal setting to set/reset the AM/PM flag.

        3) The TOD clock sometimes generates double interrupts for alarm

Q $0A2) Name the Commodore machine(s) on which a Intel 8088 was an OPTIONAL
        coprocessor.  (Hint, not the IBM clones)

A $0A2) I was looking for the B series computers, which contains the B
        computers (B128, B256), as well as the 600 series and the 700
        series.  These computers could be fitted with an optional 8088
        processor on a separate card.  However, another correct answer is
        the Amiga, which can have a 8088 attached via an expansion card or a
        SideCar(tm) unit.

Q $0A3) On Commodore computers beside the Plus/4 series, there are three
        frequencies used to record the data on the tape.  Name the
        frequencies used.

A $0A3) 1953.125Hz, 2840.909Hz, and 1488.095Hz.  These correspond to
        waveforms with periods: 512us, 352us, and 672us, respectively.

Q $0A4) Commodore Plus/4 series computers can not read any cassettes
        recorded on other Commodore computers.  Why?  (Hint: It has
        nothing to do with the nonstandard connecotr on the Plus/4)

A $0A4) The tones recorded on the Plus/4-C16 are exactly one-half the
        frequencies shown above.  This suggests to many that the Plus/4
        and C16 were supposed to run at twice its present frequency,
        but were downgraded at the last-minute, and the code to generate
        the tones was not updated to reflect the change.  This is just
        heresay, so you decide for yourself.

Q $0A5) During power-up, the Commodore 64 checks to see if it running
        in PAL-land or NTSC-land.  How does it determine its location?

A $0A5) It sets the raster compare interrupt to go off at scan line 311.
        If the interrupt occurs, we are on a PAL system, since NTSC will
        never get to line 311 (NTSC only has 262.5 lines per frame, every
        other frame shifted down a bit to create 525 lines).

Q $0A6) What is the 65XX ML opcode for BRK?

A $0A6) $00, or 00

Q $0A7) On the 65XX CPU, what gets pushed onto the stack when an interrupt

A $0A7) The program counter gets saved high byte first, then the processor
        status flags get saved.

Q $0A8) Speaking of the stack, where is the stack located in the 65XX address

A $0A8) $0100 to $01FF

Q $0A9) On the 65XX CPU line, it is possible to set and clear a number of
        processor status flags.  Examples include SEC and CLC to set and
        clear the carry flag.  What flag has a clear opcode, but no set

A $0A9) The overflow flag: V.  However, the V flag can be set via an external
        pin on some members of the 65XX line.  The 1541 uses this as an
        ingenious synchronization tool.

Q $0AA) When saving a text file to tape, the computer records 192 bytes of
        data, an inter-record gap, and then the same 192 bytes of data
        again.  How wide is this inter-record gap, and why is it there?

A $0AA) Some terminology:  "inter" means "between".  Most everyone knows
        that a tape block is recorded twice on the tape, but Commodore
        considers the two copies and the gap between them a single
        "record".  Thus, this question is referring to the gap in between
        two dissimilar records.  With that in mind,
        the interrecord gap is nominally 2 seconds long, (or 223.2 byte
        lengths, although the gap contains no data).  It is there to allow
        the tape motors to get up to speed before the next data comes under
        the read/write head.  The tape motors may need to stop between
        records if the program is not requesting any more data from the
        tape data file at this time.  If the program subsequently asks
        for data from the tape, the drive must get up to speed before the
        read can occur.  Note: on the first version of PET BASIC, the
        gap was too small, so programmers had problems retrieving data

        For completeness, the "intra-record" gap (The one between the two
        copies of the data) consists of 50+ short pulses, each of which is
        352us in length, giving a timing of .0176s+.  This time was used to
        copy important data to safe locations, reset pointers, and do error
        logging.  The entire "record" is recorded in 5.7 seconds.

Q $0AB) On an unexpanded VIC-20, where does the screen memory start?

A $0AB) $1e00, or 7680

Q $0AC) In Commodore BASIC, what is the abbreviated form of the "Load"

A $0AC) lO (L SHIFT-O)

Q $0AD) In Commodore BASIC, what is the abbreviated form of the "List"

A $0AD) lI (L SHIFT-I)

Q $0AE) On the Commodore 64, there is section of 4 kilobytes of RAM that
        cannot be used for BASIC programs.  It is the favorite hiding
        places for many ML programs, however.  What is its address in

A $0AE) $c000, or 49152

Q $0AF) What is stored at locations $A004-$A00B, and why is it strange?

A $0AF) The text "CBMBASIC" is stored there.  It is strange because this
        text is not referenced by any routine.  It can also be called
        strange because the code is Microsoft's. Doesn't it make you wonder?

Here are the answers to Commodore Trivia Edition #12 for November, 1994

Q $0B0) What will happen if you type ?""+-0 into the CBM BASIC interpreter
        on the PET series, the 64 series, or the 128 series?

A $0B0) The BASIC interpreter has a bug in it that shows up while interpreting
        the above statement.  The interpreter leaves two bytes on the CPU
        stack prior to returning from a subroutines call.  At least on the
        C64, the two bytes are both zeros.  Since subroutines put the return
        address on the stack, the return retrieves the two bytes left on the
        stack and attempts to se that as the return address.  So, depending on
        what code it executes after the return, it can do a number of things.

        Most of the time after the bug occurs, the interpreter limps along
        for a while until it hits a BRK instruction, $00.  Then, that
        instruction causes the system to execute an interrupt.  On the C64,
        the system vectors through $316-$317 (BRK vector) and does a warm
        start.  On the C128 and PETs with Monitors, the system dumps into the
        internal machine language monitor.  If the machine under use did not
        do something with the BRK vector, the machine will hang.

        Now, note that the above is not the only result.  Since the
        interpreter is executing code from the wrong location, any result
        from no effect to hung machine is possible.

        Note that this is NOT normal behavior.  The system should report an
        error while interpreting the above statement.

Q $0B1) In the first CBM 64 units, what color was the screen color RAM
        changed to when you cleared the screen?

A $0B1) The screen color RAM was changed to value 1 when the screen was
        cleared.  Thus, when a byte was poked into screen RAM, the resulting
        character was white on the screen.  The white contrasted nicely
        with the normal blue background.

Q $0B2) Why was it changed in later versions of the 64?

A $0B2) Commodore found that this practice sometimes caused "light flashes"
        during screen scrolls.  I was going to leave this for another time,
        but ... The change was to make the color RAM equal to background
        color register #0.  Well, this got rid of the "light flashes", but
        then poking values to screen RAM  caused invisible characters, since
        the foreground color of the character was the same as the background
        color of the screen.

        Well, this broke a number of older programs that did not
        properly initialize the color RAM. Also, Commodore fixed the problem
        with the VIC-II that had caused these "light flashes" So, Commodore
        changed the KERNAL a third time.  Since the above change caused
        invisible characters, Commodore made a third revision that changed
        the color RAM to the value in location 646 (the current cursor
        foreground color).

Q $0B3) What is "special" about the text that displays the "illegal quantity
        error" in CBM BASIC?

A $0B3) The text is actually "?ILLEGAL QUANTITY  ERROR".  Notice the two
        spaces between "QUANTITY" and "ERROR".  John West supplies the

        "The vector at $0300 points to a routine at $A43A, which is the
        general error message printing routine.  Load .X with the number of
        the error, and it prints it.  it looks up the address of the error
        text from a table, then prints the text, which does not have any
        trailing spaces.  It then prints '  ERROR', with *2* spaces.  It
        does this for all errors."

        Historically, this effect is caused by the VIC-20, which only had 22
        columns.  When the VIC-20 BASIC was being ported from the PET BASIC
        code, someone noticed that the some of the error strings would
        span two VIC-20 lines.  So, the BASIC error messages were changed
        a little, so that they all printed neatly on two lines:  The PET
        error string:
           ?ILLEGAL QUANTITY ERROR (one space) became:
            ERROR                  (carriage return plus one space).
        When the C64 BASIC was being ported from the VIC-20, the carriage
        return was replaced with a space character.

        I admit this caught me by surprise.  I have used Commodore computers
        for years, and never noticed that "?SYNTAX  ERROR" had 2 spaces in it.

Q $0B4) On what Commodore machine was the operating system OS/9 available?

A $0B4) Since OS/9 was a real-time operating system for the 6809
        microprocessor, it was available on only one Commodore machine, which
        had two different names:  The Commodore SuperPET.  The machine was
        sold as the "MMF (Micro MainFrame) 9000 in Germany, and its model
        number was SP9000.

Q $0B5) Which Commodore machine(s) does not have a user port?

A $0B5) There were a number of answers to this question, and there may be

        The Commodore C16.  Commodore decided to cut out telecommunications,
        and thus designed the user port out of the computer, as the modem is
        the only use Commodore ever made of the user port.  This also
        includes the C116, a version of the C16 with a chicklet keyboard.

        The Commodore Ultimax/MAX machine.  This was the ill-fated game
        console produced in the early 80s.  It was basically a stripped down
        Commodore 64.

        The 64 GS (Game System).  This machine was another flop produced
        in the late 80s.

Q $0B6) How many pins are there in a Commodore Serial Connector?

A $0B6) 6.

Q $0B7) There are 13 addressing modes available on the 6502. Name them.

A $0B7) No# Name                 Description
        --- ------------         -----------
        01) accumulator          asl a
        02) immediate            lda #$00
        03) zero page            lda $00
        04) zero page,X          lda $00,X
        05) zero page,Y          lda $00,Y
        06) absolute             lda $1000
        07) absolute,X           lda $1000,X
        08) absolute,Y           lda $1000,Y
        09) implied              clc
        10) relative             bne
        11) (indirect,X)         lda ($00,X)
        12) (indirect),Y         lda ($00),Y
        13) (absolute indirect)  jmp ($1000)

Q $0B8) If you were to put one large sequential file onto an 8050 disk drive,
        how big could that file be?

A $0B8) According to the 8050 User Manual, a sequential file could be
        521208 bytes in size.

Q $0B9) How many characters can be present in a standard Commodore DOS

A $0B9) 16 characters.

Q $0BA) How many pins does a 6502 IC have on it?

A $0BA) 40 pins.

Q $0BB) How many pins does the standard IEEE-488 connector have on it?

A $0BB) 24 pins.

Q $0BC) On the IEEE-488 bus, what does the acronym for pin 7, NRFD, stand for?

A $0BC) Not Ready For Data.

Q $0BD) On the NMOS 6502, what is the ML opcode for SED, and what does this
        opcode do?

A $0BD) $f8, SEt Decimal mode.  Sets the D flag in the status flags byte.
        Although used rarely, this opcode switches on Binary Coded Decimal
        mode.  In BCD mode, the byte $10 is treated as 10, not 16.  The add
        and subtract instructions are the only legal ones affected by this
        mode, although some undocumented/illegal opcodes are also affected.
        For example, in this mode, adding the byte $15 (21) to the byte $25
        (37) yields $40 (64) not $3a (58).  emember that, in this mode,
        $40 = 40, not 64.

Q $0BE) Assuming a PET computer and a non-PET computer have access to a
        common disk drive or tape drive, there are two ways to load a PET
        BASIC program on the non PET CBM computer. Name them.

A $0BE) Most differing series of Commodore computers had different places
        for the start of BASIC programs.  For instance, on the C64, $0801
        (2049) is the start of BASIC memory, but most PET computers start
        BASIC memory at $0401 (1025).  This wouldn't matter, except that
        BASIC programs are stored on tape and disk with the start address,
        and the line links in a BASIC program have absolute addresses in them.
        To fix these problems, the Commodore VIC-20 and newer computers came
        out with a "relocatable load".  So, here are the two choices:

        1)  Save the program on the PET like so: save "name",X (X is device).
            Then, you could load the program into the non-PET machine
            by using a relocatable load: load "name",X.  This would load the
            program in at start of BASIC memory and refigure the line links.

        2)  Redefine start of BASIC memory on non-PET machine.  A couple
            of pokes to relevant BASIC pointers, and the start of BASIC
            was moved. Then, load the program non-relocatable.

        Now, from the above discussion, it looks like option 1 is the
        simplest route.  Well, it would be, exept for one small detail:
        Earlier PET computers saved the BASIC program from $0400, not
        $0401 as is expected.  The effect:  loading relocatable on a non-PET
        would have a zero byte as the first byte of the program.  The quick
        fix:  change BASIC pointer to itself-1, load PET program, reset
        BASIC pointer.  Commodore didn't make it easy!

Q $0BF) Only one of the ways detailed in $0BE works the other way around.
        Which one?

A $0BF) Since the earlier PET computers did not have a "relocatable load",
        the only way to load a program from, say, a C64 into an 2001 was to
        use option #2 above and move the start of BASIC memory to $0801

Commodore Trivia Edition #13

Q $0C0) The early 1541 drives used a mechanism developed by ______.  Name
        the company.

Q $0C1) On later models, Commodore subsequently changed manufacturers
        for the 1541 drive mechanism.  Name the new manufacturer.

Q $0C2) What is the most obvious difference(s).  (Only one difference is

Q $0C3) On Commodore BASIC V2.0, what answer does the following give:
        PRINT (SQR(9)=3)

Q $0C4) In Commodore BASIC (Any version) what does B equal after the following
        runs: C=0:B=C=0

Q $0C5) The first PET cassette decks were actually _______ brand cassette
        players, modified for the PET computers.  Name the comapny.

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"

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?

Q $0C8) On the Commodore user port connector, how many edge contacts are

Q $0C9) On most Commodore computers, a logical BASIC line can contain up to
        80 characters.  On what Commodore computer(s) is this not true?

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?

Q $0CB) What is the maximum length (in characters) of a CBM datasette

Q $0CC) How many keys are on a stock Commodore 64 keyboard?

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?

Q $0CE) There are 6 wires in the Commodore serial bus.  Name the 6 wires.

Q $0CF) On the Commodore datasette connector, how many logical connections are

Some are easy, some are hard, try your hand at:

      Commodore Trivia Edition #13!

Jim Brain
602 North Lemen (New address)
Fenton, MI  48430
(810) 737-7300 x8528


A Different Perspective, part II

by George Taylor ( and Stephen Judd (

We... are... VR Troopers!  Okay Troopers, once again we need to make an
excursion out of the three dimensional world and into our own little virtual
world inside the C64.  So sit back in your virtual chair, put on your virtual
thinking helmet, maybe grab a virtual beer, and prepare for a virtually
useful experience with another virtually humongous article.

Last time we laid down the foundations of 3D graphics: rotations and
projections.  In this article we will build upon this foundation with a look
at hidden surfaces as well as filled surfaces.  In addition we will snaz up
the old program so that it is a little more efficient by for instance
introducing a much faster multiplication routine and moving all variables
into zero-page.

To get us in the mood let's review from last time.  We are in a
three-dimensional space; in particular, a right-handed three-dimensional
space, so that the x-axis comes towards you, the y-axis increases to the
right, and the z-axis increases "up".  Now we have some object, centered at
the origin.

To rotate the object we derived a 3x3 matrix for each axis which describes a
rotation about that axis.  After rotating we translate the object along the
z-axis and then project it through the origin onto a plane z=constant.

As you recall the projection of a point is done by drawing a line from the
point through the origin, and then figuring out where this line intersects
our plane z=constant.  You can think of this line as a ray of light bouncing
off the object and through our little pinhole camera lens.

Clearly for any solid object some parts of the object will remain hidden,
though, i.e. when you look at your monitor you can't see the back of it, and
you probably can't see the sides.  How do we capture this behavior

Hidden Surfaces

Imagine our object with some light shining on it -- when will a part of the
object be hidden?  Clearly it is hidden when the light reflected off of it
never reaches our eyes, which happens whenever a part of the object is
"turned away" from us.  How do we express this mathematically? Consider a
flat plate, like your hand (you also might think of a cube).  Now imagine a
rod sticking out of the plate, exactly perpendicular to the plate (take your
index finger from your other hand, and touch it to your palm at a
ninety-degree angle).  Now rotate the plate around, and imagine the light
bouncing off and heading towards your eyes.

No matter where you place your hand in space, the very last point at which it
is visible is when it is exactly parallel to the light rays coming from it to
your eyes; or, to put it another way, when the light rays are exactly
perpendicular to a normal vector to the surface (in the above case this
vector is either a rod or your finger).  If the angle between the normal and
a light ray is less than ninety degrees, then the surface is visible.  If
greater, then the surface is invisible.

At this point you may be wondering how to figure out the angle between two
vectors.  It turns out we really don't have to calculate it at all: instead
we use a very important tool in our mathematical toolbox, the dot product.

If we have two vectors v1=(x1,y1,z1) and v2=(x2,y2,z2) then the dot product
is defined to be

		v1 dot v2 = x1*y1 + x2*y2 + x3*y3

note that this is a _scalar_ (i.e. a number), and not a vector.  You can also
show that

		v1 dot v2 = |v1|*|v2|*cos(theta)

where | | denotes length and theta is the angle between the two vectors.
Since cos(theta) is positive or negative depending on whether or not theta is
less than or greater than ninety degrees, all we have to do is take the dot
product and look at the sign.

But we need to understand something about the dot-product.  theta is the
angle between two vectors joined at their base; mathematically the way we are
going to draw the light ray is to draw a line FROM the origin TO a point on
the surface.  In our model above, we are going to draw a line from your eyes
to the palm of your hand and then slide the normal vector down this line
until the base of the normal vector touches your eye.

The whole point of this is that when we look at the dot product we need to
keep in mind that if the dot product is negative, the face is visible.

All that remains is to write down an equation: let's say that we've rotated
the surface and know a point P=(x,y,z) on the rotated surface, and we have a
normal vector to the surface vn=(vx,vy,vz).  First we need to translate down
the z-axis so that P -> (x,y,z-z0) = P - (0,0,z0).  If we then take the dot
product we find that

	P' dot vn = (P dot vn) - z0*vz

But (P dot vn) is simply a constant: because these are rigid rotations the
length of P never changes, presumably the length of vn never changes, and the
angle between the two never changes.  So introduce a constant K where

	K = (P dot vn)/z0

so that all we need to do is subtract the z-component of the normal vector
from K and check if it is positive or negative: if negative, the face is
visible.  Note that if we translate by an amount P + (0,0,z0) (instead of -
(0,0,z0)) we simply add the two together.

We seem to have left something out here: how do we calculate the normal
vector vn?  One way to do it is by using another vector operator, the
cross-product.  The dot product of two vectors is just a scalar, but the
cross product of two vectors is another vector, perpendicular to the first

The most common way to visualize the cross-product is by using your right
hand: imagine two vectors in space, and place your right hand along one of
them, with your thumb sticking out.  Now curl your fingers towards the other
vector.  Your thumb points in the direction of the vector formed from the
cross-product of the first two.  You can easily convince yourself then that
(A x B) = -(B x A), that is, if you reverse the order of the cross product,
you get a vector pointing in the opposite direction.

Therefore, if we take any two vectors in the face (in particular, we know the
edge of the face), and then take their cross-product, we have a normal

But because we are dealing with a cube, we have an even easier method!  We
can use the fact that the faces on a cube are perpendicular to each other: if
we take two points and subtract them we get a vector going between the two
points.  On a cube, this will give us a normal vector if we use two
"opposite" points.  Therefore all we need to do is rotate the cube, subtract
two z-coordinates, add to K, and check if it is positive or negative.

This is how the program does it, and the specifics will be explained later.
Right now I want to show you a second method of hidden surface detection.
Instead of using the three-dimensional rotate vectors, what if we use the
two-dimensional _projected_ vectors?  If we take the cross-product of two of
these vectors we get a vector which either points into the screen or out of
it, which corresponds to a positive or a negative result.

The cross-product is usually done by taking the determinant of a matrix.  I
am not going to explain that here -- you can look in any decent calculus book
for the full cross-product.  All we really care about is the z-coordinate of
the vector, and the z-coordinate of v1 x v2 is:

	v1x*v2y - v1y*v2x

Whether or not the face is visible depends on how you define v1 and v2!
Always remember that (v1 x v2) = -(v2 x v1).

What is this quantity anyways?  Consider a parallelogram made up of our two
vectors v1 and v2.  The magnitude of the cross-product just happens to be


which you can easily see is the area of a parallelogram with sides v1 and v2.
For this reason the second method apparently goes by the name SAM -- Signed
Area Method.  (Now you need to think about the interpretation of the dot
product in a similar way).

Note that the second method is quite general, while the first method only
works for objects which have perpendicular surfaces (at least, in it's
current form presented here).  On the other hand, the first method is
significantly faster.

Now that we've hidden the faces, it's time to fill them:

Filled Faces

	Q: How do you make a statue of an elephant?
	A: Start with a block of granite and carve away everything
	   that doesn't look like elephant!

The first method of filling faces is very simple in concept.  Let's say we
want a cube with white faces and black edges.  Before, the program would make
the buffer black and then draw in the white edges.  The idea here is to make
the entire buffer white, draw the edges in black, and then make everything
outside of the edges black.  Quite simply, we start with a solid block and
then take away everything that doesn't look like a cube!  You can also think
of it like a cookie cutter: we press our cube-shaped cutter down and remove
all the dough outside of the cutter.

This simplistic method actually has some advantages.  If the object is very
large, we spend very little time doing the actual un-filling. We don't care
about how complicated the object is, because we just trace out the edge.
Finally, this gives us an extremely easy way of implementing a rudimentary
texture-mapping in multicolor mode.  For instance, instead of coloring the
block white, what if we used a changing pattern of colors?  As long as the
edge is a specific color, the pattern can be any combination of the other
three colors.  An example program which does just this is included -- note
that the inititalization program needs to be changed slightly to run this

In other words, we roll the dough, draw a pattern into it, press our cutter
down and remove the outside dough.  We are left with a cube with patterns all
over it.

On the downside it's not quite so easy to do things like have each face a
separate color (but who wants wimpy separate colors when you can have
evolving texture patterns, eh? :).

The program makes a few refinements to this technique.  For instance, instead
of coloring the entire buffer white, it calculates ahead of time the minimum
and maximum values for y, and only colors that part of the drawing area

For the sake of completeness, here is another method of filling:
exclusive-or. A clever way of filling faces is to use some EOR magic.  Let's
say we want to fill everything between two points A and B.  We want to start
filling at point A and stop at point B, and since EOR is a bit flipper this
gives us a means of filling.  Consider the following situation in memory:

	00010000  <-- Point A
	00010000  <-- Point B

Now consider the following little piece of code:

	LDA #00
	EOR A+1
	STA A+1
	EOR A+2
	STA A+2
	EOR A+3	  ;point B

The result is:


This is the conceptual idea behind an EOR-buffer.  Pretty neat, eh?  But we
can't just implement this as-is.  In fact we have a whole slew of things to
worry about now.  Try EORing a vertical line.  What about when two lines
share a single pixel at their intersection?  What happens in color?

Ah reckon y'all will just have to wait until next time tuh see :).

Da Program

Let's review the code.  We check to see if we should increase or decrease the
rotation rate (or quit), and then update the angles. Next we calculate the
rotation matrix using a table of sines and cosines. Then we rotate and
project the points by using a table of d/(z/64-z0) values. Somewhere in there
we clear a working buffer, draw all of the lines, swap the buffers, pass Go,
collect $200 (actually, considering where the buffers are located we either
collect $300 or $380 :), and go around the loop again.

First, some bugs.  There were two places in the line drawing routine where an
SBC was performed with the carry clear when it should have been set, so we
need to add some SECs in there.  Somewhere there is a strange bug related to
y-rotations, but I didn't track it down.

Although not a bug, there is something to think about.  On the computer, x
increases to the right, and y-increases downwards, with z coming out of the
screen.  But this is a left-handed coordinate system, and all our
calculations were performed in a right-handed coordinate system.  What this
means is that one of our coordinates is actually a mirror-image of what it
should be, while the other coordinate is where it is supposed to be.
Remember that a projection generates a negative mirror-image of the object --
the computer coordinate system mirrors a single axis of the image again!

Because of the symmetry of a cube, this makes no difference. A smart way to
fix this is to translate the object in _front_ of the projection plane, i.e.
to use the translation z=z+c instead of the currently used z=z-c, but still
project through the origin and into the plane z=1.  Since I am not
particularly smart though, not to mention lazy and unmotivated, I didn't
bother to fix this.
Before we start adding the new stuff like hidden surfaces into the code, why
don't we think about doing some simple optimizations to the old code?  One
really easy thing to fix is in the projection routine.  You will recall that
the earlier program rotated z and then added 128 to it to use as an index.
Why bother to add 128 at all?  I dunno -- sometimes things seem like a good
idea at the time.  So that's something to fix. It's not that it's a big waste
of time, it's just one of those annoying things that there's no reason for.

How about the variables?  They're all just sitting at the end of the program
-- why not move them all into zero page?  Sounds good to me!  We just need to
make sure we don't use any sensitive locations in zero page that will hose
the whole computer.  So now that's fixed.

On the C64 an interrupt is performed every 60th of a second which scans the
keyboard and things like that -- why in the world do we want that running in
the middle of all our calculations?  I dunno -- let's turn it off (but turn
it back on before checking to see if F1 etc. was pressed!).

A footnote observation: when the rotation matrix is calculated, two macros
are used (MUL2 and DIV2) which multiply and divide a signed number by two.
It never ceases to amaze me what happens when you simply sit down and think
about something, and in this case these two macros can be made much simpler:

	MUL2	ASL 	;That's all, folks


These two routines will multiply/divide a signed 2's complement number
by two (note that the source included with this article uses the old method).
There's the easy stuff to fix.  What about the calculations themselves? The
rotation is pretty straightforward -- nah, skip that.  The line drawing
routine takes up an awful lot of time -- maybe we can speed that up?  That's
for a future article :).  Clearing the buffer takes a lot of time, but now
that we're going to have filled faces there isn't too much we can do about
that.  In fact, so much more time is spent in those two areas than is spent
in other parts of the code that any other optimizations we make really aren't
going to make a very big difference... BUT...

How about multiplications?

Fast Signed Multiply

Ah, now here is something we can fix.  Consider the following function:

	f(x) = x*x/4

Now notice that

	f(a+b) - f(a-b) = a*b

Wowsers!  All we need to do is have a table of squares, and we can do a
multiplication in no time!

Whoa there, wait a minute, all of our calculations are using signed numbers.
Won't that make a difference?  Well, the above calculation is completely
general -- I never said what the sign of a and b are.  The fact that we are
using two's complement notation makes this even simpler!

Recall that our multiplication is

	x -> x * [d/(z0-z/64)]

where x is a signed floating point number multiplied by 64 (that is,  instead
of going from -1 to 1 x would go from -64 to 64).  Previously we made d
large, so that the table of d/(z-z0) would be more accurate. Then we
multiplied the numbers together and divided by 64, a procedure which took
between 150 and 180 cycles, leaving us with a signed, 8-bit result.

Well, that's easy to duplicate.  From our first equation above, we see that

	(a*b)/64 = [ f(a+b) - f(a-b) ]/64
		 = g(a+b) - g(a-b)
	g(x) = x*x/256.

In other words, if we modify our table slightly, we get exactly the result we
want.  So here is the code to multiply two numbers together:

* A*Y -> A  Signed, 8-bit result

	STA ZP1		;ZP1 -- zero page pointer to table of g(x)
	ADC #$01
	STA ZP2		;ZP2 also points to g(x)
	LDA (ZP1),Y	;g(Y+A)
	SBC (ZP2),Y	;g(Y-A)

And that's it -- we're done.  The above takes 24-26 cycles to execute -- not
bad at all!  Yes, with another table we could make it even faster, but this
is good enough for us.

At the moment we don't do very many multiplications, but in the future, when
we write a generalized routine to rotate and project an arbitrary object,
this will give us a humongous time savings.

Astute readers may be thinking ahead here: in the program, for each
projection we have two multiplications, x=x*c and y=y*c, where c is the same
in both cases.  So if we store c in ZP1 and ZP2, we can make the
multiplication even more efficient, right?  The answer is yes, but only by
being extremely careful, for reasons that will be detailed in exactly two
BUT WAIT!  We have to think about a few things here.  What happens when we
multiply, say, -1 * -1.  In two's complement notation -1 is equal to 255.  So
our above algorithm adds 255 to 255 to get an index into the table and
gets... oops!  Our table needs to be larger than 256 bytes!  In fact this is
very easy to fix, because of the way two's complement works.  All we need to
do is put an exact copy of the 256 byte table on top of itself, and the table
will work fine.  (If you look at the initialization program you will notice
that the statement is: q%=s*s:poke bm+j,q%:poke bm+j+256,q%).

BUT WAIT!!!  What kinds of numbers are we multiplying together here? Our
vertices start out at the points (+/-1,+/-1,+/-1).  Our rotations correspond
to moving these points around on a sphere, so it is easy to see that the
largest rotated value we can have is sqr(3), the radius of this sphere.
Since we are multiplying these numbers by 64, the largest value we can have
for these numbers is 64*sqr(3) = 111.  Okay, no big whoop, what are the
values for d/(z0-z/64) anyways?  Well, for z0=5 and d=150 say we get values
like 28...

ACK!  When we go to multiply we are going to add 111 to 28 and get 137, but
in two's complement notation this is equal to -119.

	f(b+a) = f(137) = f(-119) in two's-complement notation
	f(b-a) = f(83)

In our table lookup we really want 137*137 but we are going to get -119*-119!
One option is to never choose d very large so that we don't have this
problem, but the solution turns out to be much simpler, again due to the way
two's complementing works.

We can see that we can get numbers larger than 127 when we add the two
multiplicands together.  What is the _smallest_ number we will come up with?
Certainly the smallest x is going to get is -111. Ahhh... d/(z0-z/64) is
always positive, so when we add them together we will get something larger
than -111, which in two's complement notation is 145.  This means that we can
treat the table entries between 127 and at least 145 as positive numbers
instead of two's complement negative numbers, and everything will work out

Incidentally, fudging this table provides an easy way to add pretty cool
special effects.  The initialization program sets up the math table using the
following line:

	    [for j=0 to 255]
	290 S=J:IF S>150 THEN S=256-S
	    [poke bm+j,S*S]

Try changing the inequality from S>150 to S>120 (or S>127, where it would be
for a two's complement table), and see what happens!

And this is why we can't store d/(z0-z) in the pointers ZP1 and ZP2 -- if we
did, then for a given multiplication we could get numbers larger than 127 and
smaller than -128, and our clever table would no longer work right.  We can
still get around this -- all we need is two clever tables, one for the
positive d/(z0-z) and one for negative d/(z0-z).  For the first table, we
have the situation outlined above: no numbers smaller than -90 or so, and
numbers possible larger than 127.  For the second table we have the reverse
situation: no numbers larger than 90 or so, but possible numbers less than
-128. Since we are using two pointers anyways (ZP1 and ZP2), this is not
difficult to implement.

The end result is that you can do the entire projection in around 36 cycles
if you so desire.  36 cycles?  Note that for the second table the code does
something like EOR #$FF; CLC; ADC #$01. Well, if we set up the second table
as f(x)=(x+1)^2/4 then we have included the CLC and ADC #$01 into the table,
so the instructions can be removed.  The entire projection routine is then:

	... (rotate z)
	... (rotate x)
	LDA (ZP1),Y
	SBC (ZP2),Y    ;Now A contains projected X
	... (rotate y)
	LDA (ZP1),Y
	SBC (ZP2),Y

Looks like 36-40 cycles to me!  The program doesn't implement this -- it only
uses a single table, and repeats the STA ZP1 stuff at each step.  A few
cycles wasted won't kill us (there are plenty of cycles wasted in the code),
and it is probably tricky enough to follow as it is.

You might be asking, what is the true minimum value for a given z0 and d?
Well, I tried writing down a set of equations and minimizing according to
some constraints, and I ended up with a sixth-order polynomial which I had to
write little newton-iteration solver for.  In other words, I don't think you
can write down a simple function of d and z0 to give the table boundaries.  I
found 150 to be a perfectly reasonable number to use.

Incidentally, this is why the projection was not changed to z=z+c -- I didn't
want to go fiddling around with it again.  Maybe next time :).

ONE MORE THING!!!  This is very important.  The math table MUST be on an even
boundary for the above algorithm to work correctly.  This one is easy to get
bit by.

Logarithmic Multiplication

As long as we're talking about fast multiplication here, it is worthwhile to
mention another method for multiplying two numbers together.  To understand
it you need to understand two important properties of logarithms:

	log(x*y) = log(x) + log(y)
	log_b(x) = y  <=>  b^y = x

These are a reflection of the fact that logarithms are inverses of
exponentiation.  So you can see that another way to multiply two numbers
together is to take their logs, add, and then exponentiate the result.  So
you could have a table of log_2 (base 2 logarithms) and another table of 2^x,
and do a multiplication very quickly. (Actually, you'd want a table of
32*log_2(x), since log_2(256)=8). Why wasn't this method used?

First, dealing with signed numbers is much trickier -- the logarithm of a
negative number is a complex (i.e. real+imaginary) number, complete with
branch cuts.  You can get around this by setting up the tables in a special
way (for instance by letting log(-x)=-log(x)) and putting in some special
handling, but it isn't as efficient as the algorithm used in the program.

Second, accuracy decreases significantly as x and y get large, so that for an
eight-bit table of logarithms you will often get an answer that is off by one
or more.  You can in fact get around this problem by using some sneaky
manipulation -- if you are interested in seeing this, contact us!

But it is worthwhile to keep this method in mind if you need a really fast
multiplication and you aren't too worried about accuracy.

Christopher Jam ( has come up with an interesting
variation on this method.  It calculates 64+64*x/z and uses a slightly
different structure for the signed numbers, and runs almost as fast as the
method used by the program -- contact him for more information if you're

Hidden Surfaces

The remainder of this follows right from the discussion section. In the
program the cube vertices are labeled as

	P1 = 1,1,1
	P2 = 1,-1,1
	P3 = -1,-1,1
	P4 = -1,1,1
	P5 = 1,1,-1
	P6 = 1,-1,-1
	P7 = -1,-1,-1
	P8 = -1,1,-1

and the faces are chosen to be

	Face 1: P1 P2 P3 P4
	     6: P5 P6 P7 P8
	Face 2: P1 P2 P5 P6
	     5: P3 P4 P7 P8
	Face 3: P1 P4 P8 P5
	     4: P2 P3 P6 P7

(think of it as a six-sided dice, with six opposite of one, etc.).  The normal
vectors are then

	Face 1: P1-P5
	Face 2: P1-P4
	Face 3: P1-P2

This means that we need to store the z-coordinates for points 1,2,4, and 5.
Note that the opposite faces have exactly opposite normal vectors, so that
for instance the normal vector for face 6 is P5-P1, the negative of face 1.

Here is something to consider: when one face is visible, the opposite face
cannot be visible!  Because of the way projections work, though, the converse
is not true; it is entirely possible to have two opposite faces invisible.
To prove this to yourself just look at your favorite box, like your monitor,
straight-on, and notice that you can't see the sides!

All that the program does is subtract z-coordinates and add them to the
constant K, and check the sign.  Unfortunately we can have a positive
overflow while adding stuff together (since these are signed numbers), and if
we don't catch the positive overflow we will calculate a negative result when
the result is actually positive!  This will of course wreck the hidden
surface removal.

Filled Faces

The program currently uses the first algorithm to fill faces, i.e. the
cookie-cutter elephant-carving method.  During the projections the program
checks each value of y to find the minimum and maximum values for this plot,
ymin and ymax.  The program then clears the buffer up to and including ymin,
fills the buffer from ymin+1 to ymax-1, and then clears the rest of the
buffer.  Why does it clear ymin and ymax?  Because the only thing that can
happen on those lines is an edge -- there is no point in filling these lines
and then clearing them, since they will always be clear.  By only filling the
buffer between ymin and ymax, we save some time in removing the junk from the
edges of the cube.

Next, the cube is drawn.  The background is black and the faces are white,
i.e. our fill color is white.  Clearly then we want to draw our lines in
black.  I could have reversed background and foreground colors and left the
line routine as-is, but of course being the lazy programmer I am I decided
instead to change the table BITP.  You may recall that the earlier table had
entries like %10000000 %01000000 etc. Now it has entries like %01111111
%10111111 etc., and instead of ORAing the values into the buffer, they are
ANDed into the buffer.  This then draws lines of zeroes into our buffer which
is solid ones.

Finally, to un-fill the outside of the cube the program simply goes through
the buffer from ymin to ymax, coloring everything black until it hits a zero,
i.e. an edge.  At this point it calculates the appropriate pattern to clear
up to the edge, and then does the same thing starting from the right hand
side of the buffer.  In other words it runs along a specific y-value coloring
everything black until it hits the edge of the cube, and does this for all
the relevant y-values.

Texture Mapping

More of a fill-pattern really.  The program cube3d2.1.o does all of the above
but in multicolor mode.  Now instead of using a solid color to fill the
buffer the program uses a series of colored lines -- really a very simple
pattern.  A much neater thing would be to have a pattern drawn out in a
pattern buffer, and to copy that into the drawing buffer.  Other things to
try are colored squares which shift around.  cube3d2.1.o is just a really
quick hack, but at least it demonstrates the concept.

MAKE SURE that you change the value of D from 170 to 85 if you try this
program!  Pixels are doubled now, so that resolution is cut in half.  This is
located at line 240 in INIT3D2.0

Memory Map

The main program is located at $8000=32768 and is 3200 bytes long.

	$8000-$8C00 	- Program
	$8C00-$8C80	- Bit position table
	$8C80-$8D00	- Table of sines
	$8D00-$8D80	- Table of cosines.
	$8D80-$8E80	- Table of d/(z0-z/64)
	$8F00-$9100	- Two 256-byte tables of g(x)=x*x/256

	$3000		- First drawing buffer
	$3800		- Second drawing buffer

INIT3D is a simple basic program to set up the tables.  For INIT3D2.x the
important setup routines are:

	lines 100-150	- Set up the trigonometric tables
	lines 233-310	- Set up the projection and mult tables
		240	- Location of constants D and Z0
		290	- Set table boundary for multiplication

That's all -- until next time...

Steve Judd	George Taylor	12/2/95

This document is Copyright 1995 by Stephen Judd and George Taylor. Much like
the previous one.  It is also freely distributable.

And here is the source code:

*                              *
* Stephen Judd                 *
* George Taylor                *
* Started: 7/11/94             *
* Finished: 7/19/94            *
* v2.0 Completed: 12/17/94     *
*                              *
* Well, if all goes well this  *
* program will rotate a cube.  *
*                              *
* v2.0 + New and Improved!     *
* Now with faster routines,    *
* hidden surfaces, filled      *
* faces, and extra top secret  *
* text messages!               *
*                              *
* This program is intended to  *
* accompany the article in     *
* C=Hacking, Jan. 95 issue.    *
* For details on this program, *
* read the article!            *
*                              *
* Write to us!                 *
*                              *
* Myself when young did        *
* eagerly frequent             *
* Doctor and Saint, and heard  *
* great Argument               *
*  About it and about: but     *
*  evermore                    *
* Came out by the same Door    *
* as in I went.                *
*    - Rubaiyat                *
*                              *
* Though I speak with the      *
* tongues of men and of angles *
* and have not love, I am      *
* become as sounding brass, or *
* a tinkling cymbal.           *
*    - 1 Corinthians 13        *
*                              *
* P.S. This was written using  *
*      Merlin 128.             *

         ORG $8000

* Constants

BUFF1    EQU $3000        ;First character set
BUFF2    EQU $3800        ;Second character set
BUFFER   EQU $A3          ;Presumably the tape won't be running
X1       EQU $FB          ;Points for drawing a line
Y1       EQU $FC          ;These zero page addresses
X2       EQU $FD          ;don't conflict with BASIC
Y2       EQU $FE
DX       EQU $F9
DY       EQU $FA
TEMP1    EQU $FB          ;Of course, could conflict with x1
TEMP2    EQU $FC          ;Temporary variables
ZTEMP    EQU $02          ;Used for buffer swap.  Don't touch.
Z1       EQU $22          ;Used by math routine
Z2       EQU $24          ;Don't touch these either!
K        EQU $B6          ;Constant used for hidden
                          ;surface detection - don't touch
FACES    EQU $B5          ;Used in hidden surfaces.
YMIN     EQU $F7          ;Used in filled faces -- as
YMAX     EQU $F8          ;usual, don't touch
ANGMAX   EQU 120          ;There are 2*pi/angmax angles


VMCSB    EQU $D018
BKGND    EQU $D020
SSTART   EQU 1344         ;row 9 in screen memory at 1024

* Kernal


* Some variables

TX1      = $3F
TY1      = $40
TX2      = $41
TY2      = $42
P1X      = $92            ;These are temporary storage
P1Y      = $93            ;Used in plotting the projection
P2X      = $94
P2Y      = $95            ;They are here so that we
P3X      = $96            ;don't have to recalculate them.
P3Y      = $AE
P4X      = $AF            ;They make life easy.
P4Y      = $B0
P5X      = $B1            ;Why are you looking at me like that?
P5Y      = $B2            ;Don't you trust me?
P6X      = $B3
P6Y      = $B4            ;Having another child wasn't my idea.
P7X      = $71
P7Y      = $50
P8X      = $51
P8Y      = $52
P1Z      = $57            ;These are z-coordinates
P2Z      = $58            ;We only need these four to check
P4Z      = $59            ;for hidden faces
P5Z      = $60
DSX      = $61            ;DSX is the increment for
                          ;rotating around x
DSY      = $62            ;Similar for DSY, DSZ
DSZ      = $63
SX       = $64            ;These are the actual angles in x y and z
SY       = $65
SZ       = $66
T1       = $67            ;These are used in the rotation
T2       = $68
T3       = $69            ;See the article for more details
T4       = $6A
T5       = $6B
T6       = $6C
T7       = $6D
T8       = $6E
T9       = $6F
T10      = $70
A11      = $A5            ;These are the elements of the rotation matrix
B12      = $A6            ;XYZ
C13      = $A7
D21      = $A8            ;The number denotes (row,column)
E22      = $A9
F23      = $AA
G31      = $AB
H32      = $AC
I33      = $AD

*** Macros

         LDA ]1
         STA ]2

GETKEY   MAC              ;Wait for a keypress
         CMP #00
         BEQ WAIT


         LDA #$00
         STA BKGND
         STA BORDER
         LDA VMCSB
         AND #%00001111   ;Screen memory to 1024
         ORA #%00010000
         STA VMCSB

         LDY #00
         LDA #<TTEXT
         STA TEMP1
         LDA #>TTEXT
         STA TEMP2
         JMP TITLE
TTEXT    HEX 9305111111   ;clear screen, white, crsr dn
         TXT '             cube3d v2.0',0d,0d
         TXT '                  by',0d
         HEX 9F           ;cyan
         TXT '    stephen judd'
         HEX 99
         TXT '    george taylor',0d,0d
         HEX 9B
         TXT '  check out the jan. 95 issue of',0d
         HEX 96
         TXT '  c=hacking'
         HEX 9B
         TXT ' for more details!',0d
         HEX 0D1D1D9E12
         TXT 'f1/f2',92
         TXT ' - inc/dec x-rotation',0d
         HEX 1D1D12
         TXT 'f3/f4',92
         TXT ' - inc/dec y-rotation',0d
         HEX 1D1D12
         TXT 'f5/f6',92
         TXT ' - inc/dec z-rotation',0d
         HEX 1D1D12
         TXT 'f7',92
         TXT ' resets',0d
         TXT '  press q to quit',0d
         HEX 0D05
         TXT '      press any key to begin',0d
         HEX 00
         BEQ :CONT
         JSR CHROUT
         BNE TITLE
         INC TEMP2
         JMP TITLE
         TXT 'This is a secret text message!'

**** Set up tables(?)

* Tables are currently set up in BASIC
* and by the assembler.

         STA Z1+1
         STA Z2+1

**** Clear screen and set up "bitmap"
SETUP    LDA #$01         ;White
         STA $D021        ;This is done so that older
         LDA #147         ;machines will set up
         JSR CHROUT
         LDA #$00         ;correctly
         STA $D021
         LDA #<SSTART
         ADC #12          ;The goal is to center the graphics
         STA TEMP1        ;Column 12
         LDA #>SSTART     ;Row 9
         STA TEMP1+1      ;SSTART points to row 9
         LDA #00
         LDY #00
         LDX #00          ;x will count 16 rows for us

         ADC #16
         BCC :LOOP
         LDA TEMP1
         ADC #40          ;Need to add 40 to the base pointer
         STA TEMP1        ;To jump to the next row
         LDA TEMP1+1
         ADC #00          ;Take care of carries
         STA TEMP1+1
         LDY #00
         TXA              ;X is also an index into the character number
         CPX #16
         BNE :LOOP        ;Need to do it 16 times

**** Set up buffers

         LDA #<BUFF1
         STA BUFFER
         LDA #>BUFF1
         STA BUFFER+1
         STA ZTEMP        ;ztemp will make life simple for us
         LDA VMCSB
         AND #%11110001   ;Start here so that swap buffers will work right
         ORA #%00001110
         STA VMCSB

**** Set up initial values

INIT     LDA #00
         STA DSX
         STA DSY
         STA DSZ
         STA SX
         STA SY
         STA SZ

* Main loop

**** Get keypress

         CMP #133         ;F1?
         BNE :F2
         LDA DSX
         CMP #ANGMAX/2    ;No more than pi
         BEQ :CONT
         INC DSX          ;otherwise increase x-rotation
         JMP :CONT
:F2      CMP #137         ;F2?
         BNE :F3
         LDA DSX
         BEQ :CONT
         DEC DSX
         JMP :CONT
:F3      CMP #134
         BNE :F4
         LDA DSY
         CMP #ANGMAX/2
         BEQ :CONT
         INC DSY          ;Increase y-rotation
         JMP :CONT
:F4      CMP #138
         BNE :F5
         LDA DSY
         BEQ :CONT
         DEC DSY
         JMP :CONT
:F5      CMP #135
         BNE :F6
         LDA DSZ
         CMP #ANGMAX/2
         BEQ :CONT
         INC DSZ          ;z-rotation
         JMP :CONT
:F6      CMP #139
         BNE :F7
         LDA DSZ
         BEQ :CONT
         DEC DSZ
         JMP :CONT
:F7      CMP #136
         BNE :Q
         JMP INIT
:Q       CMP #'q'         ;q quits
         BNE :CONT
         JMP CLEANUP

:CONT    SEI              ;Speed things up a bit

**** Update angles

         LDA SX
         ADC DSX
         CMP #ANGMAX      ;Are we >= maximum angle?
         BCC :CONT1
         SBC #ANGMAX :If so, reset
         LDA SY
         ADC DSY
         CMP #ANGMAX
         BCC :CONT2
         SBC #ANGMAX      ;Same deal
         LDA SZ
         ADC DSZ
         CMP #ANGMAX
         BCC :CONT3
         SBC #ANGMAX

**** Rotate coordinates


*** First, calculate t1,t2,...,t10

** Two macros to simplify our life
ADDA     MAC              ;Add two angles together
         LDA ]1
         ADC ]2
* Use two trig tables to remove the below CMP etc. code
         CMP #ANGMAX      ;Is the sum > 2*pi?
         BCC DONE
         SBC #ANGMAX      ;If so, subtract 2*pi
DONE     <<<

SUBA     MAC              ;Subtract two angles
         LDA ]1
         SBC ]2
         BCS DONE
         ADC #ANGMAX      ;Oops, we need to add 2*pi
DONE     <<<

** Now calculate t1,t2,etc.

         >>> SUBA,SY      ;SZ
         STA T1           ;t1=sy-sz
         >>> ADDA,SY      ;SZ
         STA T2           ;t2=sy+sz
         >>> ADDA,SX      ;SZ
         STA T3           ;t3=sx+sz
         >>> SUBA,SX      ;SZ
         STA T4           ;t4=sx-sz
         >>> ADDA,SX      ;T2
         STA T5           ;t5=sx+t2
         >>> SUBA,SX      ;T1
         STA T6           ;t6=sx-t1
         >>> ADDA,SX      ;T1
         STA T7           ;t7=sx+t1
         >>> SUBA,T2      ;SX
         STA T8           ;t8=t2-sx
         >>> SUBA,SY      ;SX
         STA T9           ;t9=sy-sx
         >>> ADDA,SX      ;SY
         STA T10          ;t10=sx+sy

* Et voila!

*** Next, calculate A,B,C,...,I

** Another useful little macro
DIV2     MAC              ;Divide a signed number by 2
                          ;It is assumed that the number
         BPL POS          ;is in the accumulator
         EOR #$FF         ;We need to un-negative the number
         ADC #01          ;by taking it's complement
         LSR              ;divide by two
         EOR #$FF
         ADC #01          ;Make it negative again
         JMP DONEDIV
POS      LSR              ;Number is positive

MUL2     MAC              ;Multiply a signed number by 2
         BPL POSM
         EOR #$FF
         ADC #$01
         EOR #$FF
         ADC #$01
         JMP DONEMUL

** Note that we are currently making a minor leap
** of faith that no overflows will occur.

         LDX T1
         LDA COS,X
         LDX T2
         ADC COS,X
         STA A11          ;A=(cos(t1)+cos(t2))/2
         LDA SIN,X
         LDX T2
         SBC SIN,X
         STA B12          ;B=(sin(t1)-sin(t2))/2
         LDA SIN,X
         >>> MUL2
         STA C13          ;C=sin(sy)
         LDX T8
         LDA COS,X
         LDX T7
         SBC COS,X
         LDX T5
         SBC COS,X
         LDX T6
         ADC COS,X        ;Di=(cos(t8)-cos(t7)+cos(t6)-cos(t5))/2
         >>> DIV2
         LDX T3
         ADC SIN,X
         LDX T4
         SBC SIN,X
         STA D21          ;D=(sin(t3)-sin(t4)+Di)/2
         LDX T5
         LDA SIN,X
         LDX T6
         SBC SIN,X
         LDX T7
         SBC SIN,X
         LDX T8
         SBC SIN,X        ;Ei=(sin(t5)-sin(t6)-sin(t7)-sin(t8))/2
         >>> DIV2
         LDX T3
         ADC COS,X
         LDX T4
         ADC COS,X
         STA E22          ;E=(cos(t3)+cos(t4)+Ei)/2
         LDA SIN,X
         LDX T10
         SBC SIN,X
         STA F23          ;F=(sin(t9)-sin(t10))/2
         LDA SIN,X
         LDX T8
         SBC SIN,X
         LDX T7
         SBC SIN,X
         LDX T5
         SBC SIN,X        ;Gi=(sin(t6)-sin(t8)-sin(t7)-sin(t5))/2
         >>> DIV2
         LDX T4
         ADC COS,X
         LDX T3
         SBC COS,X
         STA G31          ;G=(cos(t4)-cos(t3)+Gi)/2
         LDX T6
         LDA COS,X
         LDX T7
         ADC COS,X
         LDX T5
         SBC COS,X
         LDX T8
         SBC COS,X        ;Hi=(cos(t6)+cos(t7)-cos(t5)-cos(t8))/2
         >>> DIV2
         LDX T3
         ADC SIN,X
         LDX T4
         ADC SIN,X
         STA H32          ;H=(sin(t3)+sin(t4)+Hi)/2
         LDX T9
         LDA COS,X
         LDX T10
         ADC COS,X
         STA I33          ;I=(cos(t9)+cos(t10))/2

** It's all downhill from here.
         TXT 'Gee Brain, what do you want to do '
         TXT 'tonight?'

** Rotate, project, and store the points

* A neat macro
NEG      MAC              ;Change the sign of a two's complement
         LDA ]1           ;number.
         EOR #$FF
         ADC #$01

* These macros replace the previous projection
* subroutine.

SMULT    MAC              ;Multiply two signed 8-bit
                          ;numbers: A*Y/64 -> A
         STA Z1
         EOR #$FF
         ADC #$01
         STA Z2
         LDA (Z1),Y
         SBC (Z2),Y
         <<<              ;All done :)

ADDSUB   MAC              ;Add or subtract two numbers
                          ;depending on first input
         IF -=]1          ;If subtract
         SEC              ;then use this code
         SBC ]2
         ELSE             ;otherwise use this code
         ADC ]2

PROJECT  MAC              ;The actual projection routine
                          ;two inputs are used (x,y)
                          ;corresponding to (+/-1,+/-1)
                          ;The third input is used to
                          ;determine if the rotated
                          ;z-coordinate should be
                          ;stored, and if so where.
                          ;The calling routine handles
                          ;changing the sign of z.

         LDA I33          ;Calculate rotated z:
         >>> ADDSUB,]1    ;G31 ;Add or subtract x
         >>> ADDSUB,]2    ;H32 ;Add or subtract y
         IF P,]3          ;Do we need to store the point?
         STA ]3           ;Then do so!
* EOR #128 ;We are going to take 128+z
         TAX              ;Now it is ready for indexing
         LDA ZDIV,X       ;Table of d/(z+z0)
         TAY              ;Y now contains projection

         LDA C13          ;Now calculate rotated x
         >>> ADDSUB,]1    ;A11
         >>> ADDSUB,]2    ;B12
         >>> SMULT        ;Signed multiply A*Y/64->A
         ADC #64          ;Offset the coordinate
         TAX              ;Now X is rotated x!

         LDA F23          ;Now it's y's turn
         >>> ADDSUB,]1    ;D21
         >>> ADDSUB,]2    ;E22
         >>> SMULT
         ADC #64          ;Offset
         CMP YMIN         ;Figure out if it is a
         BCS NOTMIN       ;min or max value for y
         STA YMIN
         BCC NOTMAX       ;This is used in calculating
NOTMIN   CMP YMAX         ;the filled faces
         BCC NOTMAX
         STA YMAX
NOTMAX   TAY              ;Not really necessary

         <<<              ;All done

         LDA #64          ;Reset Ymin and Ymax
         STA YMIN
         STA YMAX

* P1=[1 1 1]
         >>> PROJECT,1;1;P1Z ;Rotated z stored in P1Z
         STX P1X
         STY P1Y
* P2=[1 -1 1]
         >>> PROJECT,1    ;-1;P2Z
         STX P2X
         STY P2Y
* P3=[-1 -1 1]
         >>> PROJECT,-1;-1;NOPE ;Don't store z-value
         STX P3X
         STY P3Y
* P4=[-1 1 1]
         >>> PROJECT,-1;1;P4Z
         STX P4X
         STY P4Y
* P8=[-1 1 -1]
         >>> NEG,C13
         STA C13
         >>> NEG,F23
         STA F23
         >>> NEG,I33
         STA I33
         >>> PROJECT,-1;1;NOPE
         STX P8X
         STY P8Y
* P7=[-1 -1 -1]
         >>> PROJECT,-1;-1;NOPE
         STX P7X
         STY P7Y
* P6=[1 -1 -1]
         >>> PROJECT,1;-1;NOPE
         STX P6X
         STY P6Y
* P5=[1 1 -1]
         >>> PROJECT,1;1;P5Z
         STX P5X
         STY P5Y

* A little macro

SETBUF   MAC              ;Put buffers where they can be hurt
         LDA #00
         STA BUFFER
         LDA ZTEMP        ;ztemp contains the high byte here
         STA BUFFER+1

**** Clear buffer

* >>> SETBUF
*CLRBUF LDA #$00 ;Pretty straightforward,
* LDX #$08 ;I think
* LDY #$00

* This is the new and improved buffer clear
* routine for filled faces

         >>> SETBUF
         STA TEMP1+1      ;buffer2 will point to
         LDA #$80         ;buffer+128
         STA TEMP1        ;Makes life faster for us
         LDX #$08         ;We'll do it two at a time
         LDY #$00
         STA (TEMP1),Y
         CPY YMIN
         BNE :LOOP1
         LDA #$FF         ;Now load with fills
         STA (TEMP1),Y
         CPY YMAX
         BCC :LOOP2
         LDA #$00         ;Black out the rest
         STA (TEMP1),Y
         BPL :LOOP3       ;Until Y=128
         LDY #00
         INC BUFFER+1
         INC TEMP1+1
         BNE :LOOP1       ;Go all the way around

**** Now draw the lines.
**** But first check for hidden faces!
**** Remember: P1=[1 1 1] P2=[1 -1 1] P3=[-1 -1 1]
**** P4=[-1 1 1] P5=[1 1 -1] P6=[1 -1 -1] P7=[-1 -1 -1]
**** P8=[-1 1 -1]

LINES    LDA #00
         STA FACES        ;Hidden face counter
         SBC P1Z
         BVS :FACE6       ;Overflow already?
         ADC P5Z          ;Is k-v1z < 0?
                          ;If not, face is invisible
         BVC :DRAW1       ;But we might have overflow
         LDA P5Z          ;Was overflow pos or neg?
:DRAW1   BPL :FACE6       ;If pos then k-v1z > 0

         LDA #$01         ;Otherwise, draw the
         STA FACES        ;face!

         LDA P1X
         STA TX1
         LDA P1Y
         STA TY1
         LDA P2X
         STA TX2
         LDA P2Y
         STA TY2
         JSR DRAW         ;P1-P2

         LDA P3X
         STA TX1
         LDA P3Y
         STA TY1
         JSR DRAW         ;P2-P3

         LDA P4X
         STA TX2
         LDA P4Y
         STA TY2
         JSR DRAW         ;P3-P4

         LDA P1X
         STA TX1
         LDA P1Y
         STA TY1
         JSR DRAW         ;P4-P1  Face 1 done.
         JMP :FACE2       ;If one is visible, the other
         SBC P5Z
         BVS :FACE2
         ADC P1Z          ;Now check if K-v6z < 0
         BVC :DRAW6       ;Love that overflow
         LDA P1Z
:DRAW6   BPL :FACE2       ;If not, go on

         LDA #$20
         STA FACES        ;Otherwise, draw it

         LDA P5X
         STA TX2
         LDA P5Y
         STA TY2
         LDA P6X
         STA TX1
         LDA P6Y
         STA TY1
         JSR DRAW         ;P5-P6

         LDA P7X
         STA TX2
         LDA P7Y
         STA TY2
         JSR DRAW         ;P6-P7

         LDA P8X
         STA TX1
         LDA P8Y
         STA TY1
         JSR DRAW         ;P7-P8

         LDA P5X
         STA TX2
         LDA P5Y
         STA TY2
         JSR DRAW         ;P8-P5

         SBC P1Z
         BVS :FACE5
         ADC P4Z          ;K-v2z < 0?
         BVC :DRAW2
         LDA P4Z
         LDA #$02         ;If so, draw it!
         ORA FACES
         STA FACES

         LDX P1X          ;We're doing this this way
         STX TX1          ;to save a few cycles
         LDX P1Y
         STX TY1

         AND #$01         ;Shares an edge with face 1
         BNE :F2S2        ;Skip to next edge if present

         LDA P2X
         STA TX2
         LDA P2Y
         STA TY2
         JSR DRAW         ;P1-P2

:F2S2    LDX P5X
         STX TX2
         LDX P5Y
         STX TY2
         JSR DRAW         ;P1-P5

         LDX P6X
         STX TX1
         LDX P6Y
         STX TY1

         LDA FACES
         AND #$20         ;Also shares an edge with 6
         BNE :F2S4

         JSR DRAW         ;P5-P6

:F2S4    LDA P2X
         STA TX2
         LDA P2Y
         STA TY2          ;Such is face 2
         JSR DRAW         ;P6-P2
         JMP :FACE3       ;Skip 5

         SBC P4Z
         BVS :FACE3
         ADC P1Z          ;Same thing again...
         BVC :DRAW5
         LDA P1Z
         LDA #$10
         ORA FACES
         STA FACES

         LDX P3X
         STX TX1
         LDX P3Y
         STX TY1

         AND #$01         ;Shares with 1
         BNE :F5S2

         LDA P4X
         STA TX2
         LDA P4Y
         STA TY2
         JSR DRAW         ;P3-P4

:F5S2    LDA P7X
         STA TX2
         LDA P7Y
         STA TY2
         JSR DRAW         ;P3-P7

         LDA P8X
         STA TX1
         LDA P8Y
         STA TY1

         LDA FACES
         AND #$20         ;Shares with 6
         BNE :F5S4

         JSR DRAW         ;P7-P8
:F5S4    LDA P4X
         STA TX2
         LDA P4Y
         STA TY2          ;P8-P4
         JSR DRAW         ;Two more to go!

         SBC P1Z
         BVS :FACE4
         ADC P2Z
         BVC :DRAW3
         LDA P2Z
:DRAW3   BPL :FACE4       ;Ah reckon it's a'hidden, yup
         LDA #$04
         ORA FACES
         STA FACES

         LDX P1X
         STX TX1
         LDX P1Y
         STX TY1

         AND #$01         ;Shares with 1
         BNE :F3S2

         LDA P4X
         STA TX2
         LDA P4Y
         STA TY2
         JSR DRAW         ;P1-P4

:F3S2    LDX P5X
         STX TX2
         LDX P5Y
         STX TY2

         LDA FACES
         AND #$02         ;Shares with 2
         BNE :F3S3

         JSR DRAW         ;P1-P5
:F3S3    LDX P8X
         STX TX1
         LDX P8Y
         STX TY1

         LDA FACES
         AND #$20         ;Shares with 6
         BNE :F3S4

         JSR DRAW         ;P5-P8
:F3S4    LDX P4X
         STX TX2
         LDX P4Y
         STX TY2

         LDA FACES
         AND #$10         ;Shares with 5

         JSR DRAW         ;P8-P4

         SBC P2Z
         ADC P1Z
         BVC :DRAW4
         LDA P1Z

         LDA P2X
         STA TX1
         LDA P2Y
         STA TY1

         LDA FACES
         AND #$01         ;Shares with 1
         BNE :F4S2

         LDA P3X
         STA TX2
         LDA P3Y
         STA TY2
         JSR DRAW         ;P2-P3

:F4S2    LDA P6X
         STA TX2
         LDA P6Y
         STA TY2

         LDA FACES
         AND #$02         ;Shares with 2
         BNE :F4S3

         JSR DRAW         ;P2-P6
:F4S3    LDA P7X
         STA TX1
         LDA P7Y
         STA TY1

         LDA FACES
         AND #$20         ;Shares with 6
         BNE :F4S4

         JSR DRAW         ;P6-P7
:F4S4    LDA P3X
         STA TX2
         LDA P3Y
         STA TY2

         LDA FACES
         AND #$10         ;Shares with 5

         JSR DRAW         ;P7-P3
FACEDONE                  ;Whew!  Time for a beer.

**** Now we need to unfill the outside from the faces
         LDX #08
:L1      LDA (BUFFER),Y
         EOR #$FF         ;Go till we find a plotted
         BNE :GOTCHA      ;point (i.e. A <> $FF)
* LDA #00 ;Unfilling as we go...
         STA (BUFFER),Y
         LDA #$80
         STA BUFFER
         LDA (BUFFER),Y
         EOR #$FF
         BNE :GOTCHA
* LDA #00
         STA (BUFFER),Y
         STA BUFFER
         INC BUFFER+1
         DEX              ;This is our safety valve
         BNE :L1          ;Really shouldn't need it
         JSR CHOKE
         JMP SWAPBUF

:GOTCHA                   ;A contains the EOR plot value
         STA TEMP1        ;Now find the high bit
         LDA #00
:L2      SEC
         LSR TEMP1        ;Should really use a table
         BNE :L2          ;for this!
         AND (BUFFER),Y
         STA (BUFFER),Y

         LDA ZTEMP        ;Now go to the end
                          ;Carry is clear
                          ;Actually we add 7
         ADC #$06         ;16 columns of 128 bytes
         STA BUFFER+1
         LDA #$80
         STA BUFFER
:LOOP2   LDA (BUFFER),Y   ;And work backwards!
         EOR #$FF
         BNE :GOTCHA2
         STA (BUFFER),Y
         STA BUFFER       ;Stick a zero into buffer
         LDA (BUFFER),Y
         EOR #$FF
         BNE :GOTCHA2
         STA (BUFFER),Y
         LDA #$80
         STA BUFFER
         DEC BUFFER+1
         BNE :LOOP2

:GOTCHA2 STA TEMP1        ;Again find the high bit
         LDA #00
:L3      SEC
         ASL TEMP1
         BNE :L3
         AND (BUFFER),Y
         STA (BUFFER),Y

         INY              ;Now keep going
         CPY YMAX
         BCC :LOOP        ;Until we hit ymax!
         BEQ :LOOP        ;We need the last one too.

**** Swap buffers

         EOR #$02         ;Pretty tricky, eh?
         STA VMCSB
         LDA #$08
         EOR ZTEMP        ;ztemp=high byte just flips
         STA ZTEMP        ;between $30 and $38

         JMP MAIN         ;Around and around we go...

         TXT 'Same thing we do every night, Pinky: '
         TXT 'try to take over the world!'

* General questionable-value error procedure

CHOKE    LDX #00
         BEQ :DONE
         JSR CHROUT
         JMP :LOOP
:CTEXT   HEX 0D           ;CR
         TXT 'something choked :('
         HEX 0D00

         TXT 'Narf!'

* Drawin' a line.  A fahn lahn.

*** Some useful macros

PLOTPX   MAC              ;plot a point in x
         PHA              ;Use this one every time
         LDA BITP,X       ;X is increased
         BMI C1
         LDA #$80         ;Table has been rearranged
         EOR BUFFER       ;for filling faces
         STA BUFFER
         BMI C2
         INC BUFFER+1
C2       LDA #%01111111   ;Note that this is changed
C1       AND (BUFFER),Y   ;for plotting filled faces
         STA (BUFFER),Y
         PLA              ;Need to save A!

PLOTPY   MAC              ;Plot a point in y: simpler and necessary!
         PHA              ;Use this one when you just increase Y
         LDA BITP,X       ;but X doesn't change
         AND (BUFFER),Y
         STA (BUFFER),Y

CINIT    MAC              ;Macro to initialize the counter
         LDA ]1           ;dx or dy
         EOR #$FF         ;(Not really two's complement)
         ADC #$01         ;A = 256-dx/2 or 256-dy/2
         <<<              ;The dx/2 makes a nicer looking line

XSTEP    MAC              ;Macro to take a step in X
         ADC DY
         BCC L1
* Do we use INY or DEY here?
         IF I,]1          ;If the first character is an 'I'
         SBC DX
L1       >>> PLOTPX       ;Always take a step in X
         CPX X2
         BNE XLOOP

YSTEP    MAC              ;Same thing, but for Y
YLOOP    IF I,]1
         CLC              ;Very important!
         ADC DX
         BCC L2
         INX              ;Always increase X
         SBC DY
         >>> PLOTPX
         JMP L3
L2       >>> PLOTPY       ;We only increased Y
L3       CPY Y2
         BNE YLOOP

**** Initial line setup

DRAW     >>> MOVE,TX1     ;X1  ;Move stuff into zero page
         >>> MOVE,TX2     ;X2  ;Where it can be modified
         >>> MOVE,TY1     ;Y1
         >>> MOVE,TY2     ;Y2
         >>> SETBUF       ;Now we can clobber the buffer

         SEC              ;Make sure x1<x2
         LDA X2
         SBC X1
         BCS :CONT
         LDA Y2           ;If not, swap P1 and P2
         LDY Y1
         STA Y1
         STY Y2
         LDA X1
         LDY X2
         STY X1
         STA X2

         SBC X1           ;Now A=dx
         LDX X1           ;Put x1 into X, now we can trash X1

COLUMN   LDA X1           ;Find the first column for X
         LSR              ;(This can be made much faster!)
         LSR              ;There are x1/8 128 byte blocks
         LSR              ;Which means x1/16 256 byte blocks
         BCC :EVEN        ;With a possible extra 128 byte block
         LDY #$80         ;if so, set the high bit
         STY BUFFER
:EVEN    ADC BUFFER+1     ;Add in the number of 256 byte blocks
         STA BUFFER+1     ;And store it!

         LDA Y2           ;Calculate dy
         SBC Y1
         BCS :CONT2       ;Is y2>y1?
         EOR #$FF         ;Otherwise dy=y1-y2
         ADC #$01
         CMP DX           ;Who's bigger: dy or dx?
         BCS STEPINY      ;If dy, we need to take big steps in y

STEPINX  LDY Y1           ;X is already set to x1
         LDA BITP,X       ;Plot the first point
         AND (BUFFER),Y
         STA (BUFFER),Y
         >>> CINIT,DX     ;Initialize the counter
         CPY Y2
         BCS XDECY        ;Do we step forwards or backwards in Y?


STEPINY  LDY Y1           ;Well, a little repetition never hurt anyone
         LDA BITP,X
         AND (BUFFER),Y
         STA (BUFFER),Y
         >>> CINIT,DY
         CPY Y2
         BCS YDECY


XDECY    >>> XSTEP,DEY    ;This is put here so that
         RTS              ;Branches are legal


* Clean up

CLEANUP  LDA VMCSB        ;Switch char rom back in
         AND #%11110101   ;default
         STA VMCSB

         RTS              ;bye!

         TXT 'Happy Holidays! '
         TXT 'slj 12/94'

* Set up bit table

         DS ^             ;Clear to end of page
                          ;So that tables start on a page boundary
BITP     LUP 16           ;128 Entries for X
         DFB %01111111
         DFB %10111111
         DFB %11011111
         DFB %11101111
         DFB %11110111
         DFB %11111011
         DFB %11111101
         DFB %11111110

SIN                       ;Table of sines, 120 bytes
COS      EQU SIN+128      ;Table of cosines
                          ;Both of these trig tables are
                          ;currently set up from BASIC
ZDIV     EQU COS+128      ;Division table
TMATH    EQU ZDIV+384     ;Math table of f(x)=x*x/256

And here are the native C64 files:

begin 600 cube3d2.0.lnx.uu


2D Graphics Toolbox -- Circles

by Stephen Judd  (

3D is fun and interesting but in the end we must always display our work on a
two-dimensional surface, the CRT.  Not only are two-dimensional algorithms
the foundation of three-dimensional drawings, they are of course useful for
many other applications.  This new series of articles is intended to
complement (hah -- get it?) the 3D articles by George and myself.  Between
the two articles you should have at your disposal a powerful graphics toolbox
for all of your applications.

The foundation of all of our drawings is a single point, and a logical next
step would be a line.  Algorithms for doing both of these things were
discussed in depth in the first article of the 3D series, so you can look
those up in a back-issue of C=Hacking.  What is next after points and lines?
Curves!  So to start with, let's think about drawing a circle.

You can write the equation for a circle in many ways, depending on your
coordinate system.  In cartesian coordinates, the equation of a circle is:

		x^2 + y^2 = r^2			(1)

This is a circle centered at the origin (on a computer we can always
translate the points wherever we want), with radius r.  In polar coordinates
the equation is r=const.  We can write down trigonometric relations out the
wazoo, too, but no matter what it looks like we're going to have some
complicated math ahead of us involving multiplications and worse, i.e.
time-intensive computations.  Perhaps the simplest of these would be:


where theta runs from zero to 2*pi.  If we have a table of sines and cosines,
and use the fast multiplication algorithm given in this months 3D article, we
have a routine which gives a good circle in around 50 cycles or so per pixel,
sans plot.

But let's step back and consider: the above equations all give a "perfect"
circle.  Is there any way we can draw an "approximate" circle, with a large
speed increase?  The answer is of course yes, since this wouldn't be a very
interesting article otherwise!

Let's say we are standing at a point on the circle, and want to take a step
towards the next point.  In what direction do we take this step?  Consider a
line tangent to the circle, touching the point where we are standing.  If we
take a little step in that direction, we ought to get close to the next point
on the circle. The way to calculate the tangent line is to use the
derivative, which will give us the slope of the tangent.  Taking
differentials of equation (1) above we have

		2*x dx + 2*y dy = 0
		dy/dx = -x/y

Now, if I am at a point (x,y), I want to take a little step in x and a little
step in y:

		x = x+dx
		y = y+dy

but from the first equation we know that dx = -y/x * dy so that at each step
our iteration is

		y = y + dy
		x = x - dy*(y/x)

This is the basis of our algorithm.  You could also of course use

		x = x + dx
		y = y - dx*(x/y)

(this is called Euler's method for solving a differential equation, and these
concepts are fundamental to most algorithms for solving differential
equations numerically).

Let's start at the point x=r,y=0 i.e. the right-endpoint of the circle.
Since we are on a computer we want to take a step of length one in some
direction (i.e. step one pixel), and at this point on the circle y is clearly
increasing much faster than x. You may remember from the line drawing
algorithm that we viewed the process as "keep taking steps in x until it is
time to take a step in y". We are going to apply that same philosophy here:
keep taking steps in y until it is time to take a step (backwards) in x.  So
our iteration is now:

		y_{n+1} = y_n + 1
		x_{n+1} = x_n - y_n/x_n

Hmmm... we have this little problem now of y/x.  I certainly don't want to
deal with floating point.  How do we get around this?  The trick is to think
of x as a _discrete_ variable -- as far as a pixel is concerned x is just an
integer, and has no floating point part.  So we are going to treat x as a
constant, until it is time to decrease x by one! When does this happen?
Let's expand the x-iteration:

	x_{n+1} = x_n - y_n/x_n
		= (x_{n-1} - y_{n-1}/x_{n-1}) - y_n/x_n

This is where the magic of using a discrete x comes in.  If we make this
assumption of constant x, then x_{n-1} = x_n and the above iteration becomes

	x_{n+1} = x_{n-1} - (y_{n-1} + y_n)/x_{n-1}

If we continue this process over an interval where x is constant we get

	x_{n+1} = x_0 - (y_n + y_{n-1} + y_{n-2} + ...)/x_0

When is it time to decrease x?  When the sum of the y values at each
iteration exceeds the current x-value the fraction above will be greater than
one, and we will then decrease x.  Like I said, we keep x constant, until it
is time to decrease it! :)

How long do we do this for?  In the same way that at x=r y increases much
faster than x does, when y=r x increases much faster than y does.  Somehwere
in-between they have to be increasing at the same rate, which means the slope
of the tangent line is equal to +/-1, i.e. at the point x=y.  At this point
we have drawn one-eighth of the circle.  We can either draw all eight
segments of the circle independentally, or else we can use the symmetry of a
circle and do an 8-way plot.

TO SUMMARIZE: Here is the basic algorithm

	if a>x then x=x-1:a=a-x
	:until x<=y

Of course you can refine this in several ways, which will speed things up in
assembly.  For instance, if instead of a=0 we start with a=x, then the logic

	if a<0 then x=x-1:a=a+x

To do the a=a-x you could use a table where f(x)=x, or you might try
something else; here is some code in assembly:

	LDY #00
:loop	JSR PLOT8	;Eight-way symmetric plot
	SEC 		;Might not need this depending on PLOT8
	INC Y		;Y is in zero page
	BCS :loop
	STX X		;X is also in zero-page
	ADC X		;Carry is already clear
	BCS :loop
	JSR PLOT8	;Catch the last point

Starting at :loop we have 2+3+3+3=11 cycles in the best case and
2+3+3+2+2+3+3+3+3=24 cycles in the worst case, excluding PLOT8.  You can, of
course, change the program logic around; if PLOT8 returned with the carry
always clear it would be much smarter to start A as A=256-A and use A=A+Y at
each step instead of A=A-Y.  Christopher Jam (
suggested starting at X=Y(=R/sqrt(2)) so that the CPX Y instruction could be
removed (I don't know how this affects accuracy, though).

"Yeah, but how well does it work?"  Quite well, as a matter of fact.  It will
draw a perfect circle for circles with a radius greater than twelve or so.
For circles with a smaller radius the sides start to flatten out, and the
circle becomes squareish.  Interestingly, the "discrete x" approximation
improves the result over the straight floating-point calculation

So this is the best algorithm I was able to come up with for drawing a
circle.  If you have any suggestions for improvements or other ideas, please
feel free to share them :).  As always I must thank George Taylor and
Christopher Jam for their suggestions and for helping me work out some ideas.

If you have any particular 2D algortihms/calculations that you would like to
see, please feel free to suggest future topics for the 2D graphics toolbox.

Finally, here is a BASIC7.0 program which demonstrates the algorithm:

10 GRAPHIC 1,1
15 REM X=Radius
20 X=40:Y=0:TX=X:XO=160:YO=100
70 IF X<=Y THEN 100
80 Y=Y+1:TX=TX-Y
95 GOTO 30
100 END


AFLI-specs v1.0

by written by D'Arc/Topaz for Chief/Padua on 28.6.1994

Advanged FLI is name I came up with during the time I coded the
first version of AFLI editor. I have never claimed to be the one
who discovered this new graphics mode for 64. I myself give the
credit for COLORFUL/ORIGO but I am not sure if anyone did it
before him (splits have been done but in my eyes they don't count).

In AFLI we can get 120 colors in theory (counted like this
16!/(2!*14!)=120). When we put red and blue hires pixels close to
each other we get a vision of purple - thanks the television.

AFLI is just like FLI with $08-$0f (hires value) in $d016 and a
couple of sprites over the first three marks. With $d018 we
change the start of screen memory. And the good old $d011 for the
main work.

AFLI is the same as FLI but we don't use the $d800-$dc00 area
for the third color. Actually we can't. In normal hires pictures
the colors on the picture is ordered in a normal screen (normal
text screen is on $0400+). The upper 4 bits is the color for
bit 0 in picture bitmap and the lower 4 bits is the color for bit
1 in picture bitmap (or the other way...but let us think that was
the right way).

For example: a normal hires picture char (8x8 bits)

 01234567  in hires picture where  01234567
0 *****    the first spot of the  0bgggggbb
1*** ***   screen has a value of  1gggbgggb
2*** ***   $68 (blue&green) the   2gggbgggb
3*******   hires picture looks    3gggggggb
4*** ***   like this ---->        4gggbgggb
5*** ***   b=blue, g=green        5gggbgggb
6*** ***                          6gggbgggb
7                                 7bbbbbbbb

The bitmap is built just as in a hires picture bit 1 means the pixel
is on and 0 that the pixel is off.

In FLI we have built the screen to have badlines on every scanline of
the screen. This gives us the possibility to change the screenmemory
the picture uses on everyline. Now... when AFLI (and FLI) uses screen
memory for colors and we change the screenmemory start on everyline,
we can have new colors on everyline.

The screens are usually ordered like this.

screen memory used
   0   $4000-$43ff
   1   $4400-$47ff
   2   $4800-$4bff
   3   $4c00-$4fff
   4   $5000-$53ff
   5   $5400-$57ff
   6   $5800-$5bff
   7   $5c00-$5fff
       $6000-$7fff BITMAP (the actual picture data)

The number of the screen is considered as the number of the line in
8x8 pixel area.

An example... Here we have cut from the memory showing the first
bytes in every screen.

           00 01 02 03 04 05 06 07 08 09 10 11 12 13 ... 39
     $4000 ff ff ff 56 .. .. ..
     $4400 ff ff ff 67 .. ..
     $4800 ff ff ff 91 ..
     $4c00 ff ff ff b3
     $5000 ff ff ff 54
     $5400 ff ff ff 8f
     $5800 ff ff ff 54
     $5c00 ff ff ff 10

Actually the $ff won't have to be there. It will come to the screen
anyway. We have the same 'A' on the screen on the fourth mark ($6018-

  BITMAP               AFLI PICTURE (number is the color number)
 01234567 screenvalue    01234567
0 *****       $56      0 56666655   1=white, 0=black, 2=red ...
1*** ***      $67      1 77767776
2*** ***      $91      2 11191119
3*******      $b3      3 33333333
4*** ***      $54      4 44454445
5*** ***      $8f      5 fff8fff8
6*** ***      $54      6 44454445
7             $10      7 11111111

Now the 'A' surely has a lot of colors.

When we code a FLI routine we know that we have succeeded when we get
a 3 marks wide area filled with value $ff on the screen. In FLI the
thing is easily taken away; we just fill the three first bytes of a
line with empty bytes ($00). In AFLI the value $ff is a color. If we
try to clear the three first marks, we still have the gray area. WHY?
The $ff value comes to the screen.. so... the $ff is a color and
the upper four bits of the byte is the color for empty pixels. We can
not clear the first three marks to wipe the thing off. We have a new
lovely problem: we have to put black (or whatever) sprites over that
area. This is just timing.

This may look very complicated and I think you will still be asking
many questions from me - the text I have written surely ain't the
best novel ever written. I'm great in jumping from a thing to

Chief/Padua						  +44 (0) 757 706791
                        My opinions are not my employers

Coding Tricks

The following are messages posted to comp.sys.cbm that contain little "coding
tricks" that I thought were useful. If you've got any of your own that you want
to post feel free to email them to me and I'll post them to comp.sys.cbm -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
From: (Paul van Loon)
Date: 11 Oct 1994 14:28:49 GMT

Hello everyone,

I really would like to start a thread on your favorite
sequences of 6502 code. I hope much people will react
so we can build up a library of the most beautiful
6502 code ever seen.
I would suggest little code fragments, probably not
longer than 10 lines, doing some tiny function.

I propose the following standard:

<xxx> <yyyyy> "text"
      <aaa>   ; comment
      <bbb>   ; comment


where <xxx> is the symbolic name (3 letters?) you give
to your code sequence, <yyyyy> describes your arguments
"text" gives a full name to pronounce for the symbolic name
and <aaa> and on are the 6502 instructions you use. "description"
is a description of the functionality of your code. The lines
with "---" are seperators.

I will hereby start with my all-time favourite code,
I discovered it only recently and I will also
show some examples of how to use it!


B7C        "Bit 7 to Carry"
    cmp #$80

This instruction copies bit 7 of A to C

This is really a beauty! It works because
cmp clears the carry if A is below the immediate
value, and sets it if A is higher or same.
All values lower than $80 have bit 7 equal 0,
cmp will clear C for all values below $80, and
thus will 'copy' bit 7 into carry. All values
equal or above $80 will have bit 7 equal 1,
cmp will set C for all values above $80 and
thus for this case it will also 'copy' bit 7
into carry!

ASR        "Arithmetic Shift Right"

This instruction does a signed divide by 2

Again a beauty in my eyes! I have puzzled
many times who to write the on other CPUs
well-known ASR instruction, but I never
seemed to get it implemented without an
extra register to use like:
or even without a branch (figure that out

With these two beauties I want to give you
an idea of what I mean, and please follow me

BONUS. A little routine to clear the screen
without erasing the sprite pointers.

CLS        "Clear the screen"
    ldx #250
    lda #$20
clp sta $0400-1,x   ;  0-249
    sta $0400+249,x ;250-499
    sta $0400+499,x ;500-749
    sta $0400+749,x ;750-999
    bne clp

Clear only the screen

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
From: (Paul van Loon)
Subject: Re: Post your favourite little code too!
Date: 20 Oct 1994 12:17:40 GMT

RSL	"Rotate Straight Left"

This instruction does a rotation left
through 8 bits (rol does a rotation
left through 9 bits, 8 bits of A
and 1 bit C)

Another useful instruction, for
a wraparound rol, e.g. for rotating
bytes in characters to simulate
parallax scrolling.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

A reliable way to create a Straight IRQ?

Well, this is the way I've always used, because it's reliable, flexible
doesn't mess up, or set restictions on the display, unlike routines that
depend on D011 etc...
This one only observes the VIC chip:

It's some time since I actually coded a routine of this kind,
and I'm writing this off memory, so there might be some errors in the
following code.

   <irq is initiated in the usual way, I almost always use FFFE/FFFF instead
    of 0314/0315 for interrupt vectors, but the routine should work with
    0314/315, but delay timing will be different becouse of the time being
    up by the kernal>

   <Set up irq vector to label 'irq1' and program desired rasterline with
    d012 & $ d011 in the usual way>

    <other initiation of program>

                jmp mainPrg

;----------- Standard routines for all sharp irqs ----------------

sharp           inc $d012                 ;Set interrupt to the next line.
                inc $d019                 ;Ready for new interrupt
                                          ;same as lda #1 : sta $d019
                sta storeA
                lda #<sharpirq
                sta $fffe                 ;Alter interrupt vector
                lda #>sharpirq
                sta $ffff
                cli                       ;Allow new interrupt even if we
                                          ;still are in the previos irq call
                nop             ; We add nop's here so that the next interrupt
                                ; will interrupt while we are executing nop
                ...             ; commands. Since nop commands use two cycles
                ...             ; the interrupt will at most be delayed 1
                ...             ; cycle.
                ...             ; I think we needed about 11 nop's to be sure
                                ; that execution is interrupted while they are
                                ; run. (I'm not sure about this number)

                jmp noploop     ; Although we are sure that the interrupt will
                                ; interrupt before we reach this point, we
                                ; add this loop to be on the safe side.
                                ; Imagine if the program happened to be frosen
                                ; by a carterigde and later restarted while
                                ; executing the nop's.

sharpirq        pla             ; Delete data put to stack by the last
                pla             ; interrupt, we don't intend to return from
                pla             ; it.
                stx storeX
                sty storeY

                nop             ; Now we only have an uncertainty of 1 cycle
                nop             ; as to where the interrupt is.
                                ; By waiting until the edge of the current
                                ; rasterline we can determine if the interrupt
                ...             ; was delayed by one cycle or not.
                ...             ; (How many nop's that is required to reach the
                ...             ; edge of the rasterline i don't remember.
                                ; You'll just have to find it out yourself)
                                ; These nop's may of course be exchanged by
                                ; equivelent time consuming instructions.

                lda $d012       ; get current rasterline
                cmp $d012       ; still on same line = was delayed;
                bne addCycle    ; add 1 cycle if not delayed.
addCycle                        ; doing a branch jump takes 1 cycle more than
                                ; not doing one.

                                ; the rastertiming is now 'straight'/sharp
                rts             ; return to routine that cal led sharp

endIrq                          ; restore a x y
storeA          = * + 1
                lda #0          ; For those who don't like self modifying code
storeX          = * + 1         ; this can be changed easy.
                ldx #0
storeY          = * + 1
                ldy #0
                rti             ; Return from interrupt.

                stx $fffe
                sty $ffff
                sta $d012
                inc $d019       ;or lda #1 : sta $d019 if you prefere

;------------------ The actual interrupt ------------------------------

                jsr sharp       ;Make interrupt sharp and store A X Y regs.

        <here you put your sharp interrupt dependant code>

        <other code that you need executed this interrupt>

                ldx #<irq<?>    ;<?> is the next interrupt that is due
                ldy #>irq<?>    ;1 if irq1 is the only interrupt.
                lda #<rasterline for interrupt>
                jsr nextIrq

                jmp endIrq

;--------------- Main program ---------------------------------

                cli             ;allow interrupt

        <wait for space or something>


        <shut everything down and restore what needs to be restored>

                rts             ; Or jmp exitCode


By adding this routine:

saver           sta storeA
                stx storeX
                sty storeY
                rts can at any time turn on/off the sharping by exchanging 'jsr sharp'
and 'jsr saver'.

I hope this is what you were looking for. Sorry for not supplying a
complete source, but as I said this is all from memory.

PS. My native lanuage is not english and I typed this in a hurry, so sorry
for the lousy lanuage / source.

Furhermore, ways of getting an rnd.

* lda $d012, only in large complex programs with lots of variable execution
             times where rnd is only needed once in a while.
             (Never use in raster interrupts of obvious reasons)

* lda $d800     ; The 4 upper bits of mem at area $d800 - $dc00 are completly
  lsr           ; unpredictable...
  lda $d801
  lda $d802

* Read the values from the white noise generator to the SID.
  3rd voice set to whitenoise, and read the result.
  (sorry don't remember address to read, think it is around $d416-$d41c)

These give you _true_ random numbers. It's also possible to create
'random' routines that create 'random' numbers based on a seed value.

Anything unclear? Mail me at


 - Rolf Wilhelm Rasmussen

 Equal of Eternity


C.S. Bruce Interview

by Craig Taylor (

The following is an interview of Craig Bruce, author of numerous programs such
as ZED, ACE etc for the Commodore 64/128.

> What computer experience did you have before the Commodore computers?

Very little.  I was 14 at the time, in grade nine, and it was December 1982.
My Jr. High school had a few CBM 8032s, but I never actually got to touch
one.  I took a "mini course" on computers and learned a tiny little bit about
what computers were like and about BASIC.  To give you an idea of how little
I learned, I was entirely incapable at the time of figuring out how to
increment a variable (X=X+1).

> What was your first Commodore computer and why?

My first computer was a VIC-20.  I had the choice narrowed down to a VIC, a
TI99-4A, or a Timex/Sinclair 1000(?).  (I don't think I had heard of the
Ataris or the Apple).  I chose the VIC partly because it was related to the
computers at school but mostly because it had the most impressive brochure
and I had the most information about it.  It was theoretically a Christmas
present, but it didn't stay in the box very long.  I paid half of the $400.00
price tag and my parents paid the other half.

> How did you learn programming on the Commodore? Did experience from other
  PC's help?

Ahhh, those were the days.  I learned programming from a few sources.  The
user's guide that came with the VIC was quite helpful, and I read the
magazines of the day, mostly Compute!s.  I also had a friend who went to high
school and used the computers there who knew a thing or two, and two other
friends who got VIC-20s soon after me, so we learned from each other.

I also took a relatively informal night course that was offered for
programming VIC-20s.  By the time I took it, though, I had already learned
just about everything that the course tought: BASIC.  Then, in the last
class, the instuctor talked just a little bit about machine langauge, just
enough for me to understand what was in the VIC-20 Programmer's Reference
Guide.  I learned 6502 machine language shortly after that.

Experience from other PCs didn't really factor into things, since I had no
experience with any other PC.  However, when the time came to learn about
other computers and other programming languages (in high school and bachelor
university), I had an enormous advantage over the other students, since I
understood so thoroughly how computers worked because of my VIC-20

> What other interests, besides hacking on the Commodore, do you have?

Not many.  I don't get out much and I have only a handful of friends.  I
spend most of my time sleeping, watching TV, net surfing, doing school work,
and/or hacking on Commodores.  Hacking on Commodores is in my blood.  While
I'm doing these other things, I'm usually thinking about hacking on
Commodores.  You might say that I'm a sterotypical total computer geek.  I do
like biking, though.  I bike to school every day.  And music.

> What is your feelings on the demise of Commodore?

Losing Commodore was a little sad, but as someone said on the newsgroup when
Commodore went under, "What has Commodore done for you lately?".  We
8-bitters lost all support from Commodore long before its demise.  I
certainly don't blame them; 8-bit computers are a thing of the past and there
wasn't a big enough market to support a company the size of Commodore.

> Do you see anything in the future that signals anything that will extend the
  useful lifetime of the Commodore 8-bits?

IMHO, there are only two things that 8-bit computers need to survive in the
hands of hobbiests indefinitely: serious system software and modern hardware
peripherals.  Some serious system software has begun to surface recently :-),
and Creative Micro Designs has been providing modern peripherals for us to
use.  New application programs are needed too, but I'm assuming that
hobbiests + serious_system_software --> serious_new_application_programs.
(Perhaps this is a bit self-serving).

Eight-bit computers have two big advantages over bigger PCs: a much lower
price and a much higher understandability quotient.  Both of these are very
important to hobbiests.

> Do you feel that with the addition of newer periphials that are gradually
  superceeding the CPU's job (REU, RamLink, SwiftLink etc...) that the
  Commodore 8-bit standard machine is no longer standard??

Indeed, there are lots of options.  But I think that this is a good thing.
The original Commodores are quite limited, and this modern hardware is needed
to allow the Commodores to remain useful in the networked world.  An
important feature of all of these new products is that you can flip a switch
or pull out a cartidge and you're back to your little old standard Commodore.
Of course, who really wants to do this.

The reason that I have stayed with my little Commodore for all of these years
is that I believe that it still has quite enough power (using modern
peripherals and expanded memory) to do what I require of it.  For example,
it's quite possible to have a nice little 17K text editor that can edit huge
files and be very useful.  You don't need a multi-megabyte program with all
kinds of snazzy features that requires a monsterous machine to run on to do
this.  These multi-megabyte programs are simply bloated and poorly designed.

> Will there ever be an update to Zed? (a question asked on a lot of the
  commericial providers)

Yes.  I've been promising this for a long time, but the right time to do this
is finally near.

> What is the process that you use for writing your programs?

I'll start this answer with a Unix-fortune quotation:

"Real programmers don't draw flowcharts.  Flowcharts are, after all, the
 illiterate's form of documentation.  Cavemen drew flowcharts; look how
 much good it did them."

For complicated algorithms, I'll sit down write some pseudo-code, and I
always plan and write out complicated data structures.  But other than this,
I usually just sit down and write code, after kicking ideas around in my head
long enough for me to know what I have to do.

> It's been noticed that you have a "fanatacism" about speed in your programs.
  Can you elaborate on this?

Guilty as charged.  As I said above, I despise bloated software that needs a
mega-machine to run on fast enough.  I like software that is sleek and mean,
and I have an axe to grind that little 8-bit Commodore computers offer quite
enough computing power for most applications that most people would use them
for.  The exceptions are number-crunching, huge-data processing, and heavy
computation.  However, for most interactive programs, an 8-bit processor is
quite enough.  So, I grind my axe by producing fast programs.  Arguably, that
effort is sometimes misspent (like in the printing to the screen in ACE -- I
still have a few more tricks up my sleeve though...), but I like to go a
little too far sometimes to make people go, "Wow! I didn't know this little
machine could do that so fast!!"  I like to upset the notion that you need a
huge machine to get adequate performance.  (In fact, sometimes the opposite
is true, since programmers assume that they can be extra sloppy when
programming for huge machines).

As a user, I like crisp responsiveness.  This is a feature of personal
computers that can sometimes be absent on big multi-user virtual-memory

I also have a big thing against backwards compatibility ("hysterical
raisins"). This is a significant cause of software bloatedness.  This is one
reason that ACE, for example, was designed from scratch rather then with the
pre-set limitation that all BASIC-compatible programs should run with it (a
la CS-DOS).

> Is there anything that you find particularly useful / handy about the
  Commodore's architecture?

Yeah, it's simple.

> And the corallary: Is there anything particularly annoying?

Yeah, it's limited.

> What is currently in the works / planned??

My Commodore job queue looks like the following:

1. Update my Unix VBM file filter.  Make it produce a new format of VBM files
   with run-length encoding compression.  Investigate LZW(?) compression.

2. Work on ACE release #13: Internal cleanup.  Reorganize the internal memory
   usage, add features to the command shell, clean up memory and device
   management inside the kernel, update the VBM program, add a SwiftLink
   device driver, make a simple glass-tty terminal program.

3. Develop a new portable archiver format and write a C program for Unix,
   ".car" format.

4. Write my next article for C= Hacking, which will be about the detailed
   design of a distributed multitasking microkernel operating system for
   the C128.  This article will also include a minimal multitasking

5. Work on ACE release #14: Port Zed to ACE.  Get the basic editing features

6. Work on ACE release #15: Finish the ACE assembler.  Add the file-inclusion,
   conditional and macro assembly features.  Make it accept more dyadic
   operators in expressions, fix the label typing, make it generate
   relocatable executable code modules, and make it handle modular compilation
   (".o" files).

7. Work on ACE release #16: Archiving.  Update the "bcode" and "unbcode"
   programs to support uucode, nucode and hexcode formats.  Toss the old
   "uuencode" and "uudecode" programs.  Implement "car" and "uncar" programs.
   Look into "zip" format.

8. Start on BOS, the distributed multitasking microkernel operating system
   for the 128.

(Actually, some of these things will be done by the time that C= Hacking
 comes out).

Keep on Hackin'!


Aligning 1541 Drives

by Ward Shrake (taken from comp.sys.cbm)

A discussion regarding Commodore 1541 disk drive alignment procedures, with

Background information.

The best way I've ever seen to consistently and reliably get a 1541 disk
drive aligned perfectly, was caused by copy protection. It is sort of appropo
that copy protection, which usually causes the "head knock" problem that puts
drives out of alignment in the first place, should also be able to solve the
problem it created.

An older version of a disk utility program, ("Disector" v3.0, as I remember
it), had copy protection that would not let you load the disk up unless your
disk alignment was perfect. While initially loading itself, it would search
and search, never quitting, until it found what it was looking for, exactly
where it was looking for it. It would stay in an endless loop, searching
forever, never making it to so far as the first screen. This essentially
"locked up" the computer, if the program thought the disk it was on was an
illegal copy.

This quickly became the most hassle-free, no-worry alignment program I've
ever seen. I have seen and used most of the others; this method beat them
all, no contest, in my opinion.

The other programs, the ones made for aligning your drive,  never
consistently worked acceptably well, in my experience. Other technical users
apparently feel the same way about them, as the "General FAQ, v2.1" on
Commodores points out. They would work OK part of the time, or on part of the
drives you tried, but not all, I found. Or they would say you now had a
perfectly-aligned drive, but some difficult copy protection schemes would
still not load and run on the newly tuned-up drive friend. A friend of mine,
now deceased, once had a drive no alignment program could fix. We tried
everything we could find. After aligning it with a given method or program,
some programs would load that would not load before, but others would now no
longer work, that used to work before. All in all, it was very frustrating,
and the general feeling was that there has to be a better, easier, more
reliable way to do this.

All an alignment program has to do, is to make sure that when the disk drive
says it is precisely at a given track's physical location, that it is really
there, centered on that track.

There are other Commodore adjustments, but alignment seems to be, by far, the
most common problem. Disk drive rotational speed can be adjusted, but it
usually is not the problem. In fact, I've seen more than one drive, that when
adjusted to read a program-reported "perfect" 300 rpm rotation speed, they
quit reading disks; requiring speed to be set at a reported 310 rpm, to work
again. The end stop gap can also be adjusted, but I've never seen it be the
real culprit with a non-working disk drive. Your experience may vary, of
course, but I've always found that it is best to concentrate on alignment
first, then fool around with the other adjustments ONLY after alignment is
truly corrected, and only if it still refuses to work properly.

Once alignment is corrected, there are methods available to insure that it
stays that way. For instance, you can have the stepper motor's pulley
mechanically pinned to its shaft, instead of merely relying on the factory's
interference fit to hold it. Commodore 1541 drives were made to be self-
aligning, apparently, which would be fine if "head knocking" protection
schemes were not around. Since they are, the pulley should, ideally, not be
allowed to turn on its shaft, which is what causes misalignment problems.

How I used to align 1541 disk drives....

To precisely align a given 1541 disk drive, I used the old, unbroken copy I
had of Disector (v3.0, I think), and followed these steps.  With power to the
drive off an disconnected,  you first took off the upper and lower halves of
the outer plastic casing of the drive. This exposed the electronics inside.
You then found and loosened (but not removed!) the two stepper motor mounting
screws, which are on the underside of the disk drive's internal mechanisms.
After that, you hooked the power cable back up, and hooked the drive to the
computer like it normally is.

Once you've done this, you set the drive up on one side, so that you can
(carefully!) reach into the mechanism, to physically rotate the stepper
motor, which would normally be on the bottom of the drive. You type in the
program's loading instructions on the computer, and you then wait until the
screen went black (copy protection searching for certain info on the
diskette). This is where  the program  "locks up," with the unaligned drive.

Once the program is loading, but stuck and unable to find what it wants, you
reach into the mechanism, very slowly and carefully, turning the stepper
motor a slight bit in either direction, and stopping. Tiny adjustments are a
lot; don't overdo it. Be patient; don't go too fast, or move it too much! You
watch the screen carefully, and listen to the drive's sounds.

When you have rotated the stepper motor to the proper place, the sounds and
the screen will act a little different, perhaps only slightly so. Wait a
second, not moving the stepper motor at all. When you are right on,
alignment-wise, the program will find what it is looking for, and the
program's main menu will appear.

Once the main menu has come onto the screen, you have a perfectly aligned
drive. Then you have to retighten the stepper mounting screws, being very
careful not to accidentally move the motor in the process. Hold the motor
firmly while retightening both screws in small steps, alternating back and
forth between them until they are both tight. The rotational force of the
screws turning, forces the motor to move some, so watch for it.

With this method, using a specially-prepared disk, I always got perfect
results; everything would load, every time, from then on. (Assuming that the
disk was formatted with a good drive to begin with; any disks you made
recently, on your badly-aligned drive, may not load after the alignment
procedure.  Transfer the info on these disks, to a second, known-good drive,
before you do this procedure. This is normal, however, no matter what method
you use to align a bad drive.)

Here's the problem with this method...

This procedure only works with a special disk, one that is no longer
available. With the special disk, alignment is quick, hassle-free, and it
always gave excellent, reliable results the first time around. Without the
"perfect" disk, this procedure is worthless. This is obviously a problem,
since the method relies on a disk that is no longer available to the public.
You can't make your own, because you don't know if the disk drives you are
using, are truly perfect to start with! Disks made by users, on Commodore
equipment, never worked; they just matched your drive's alignment to that of
someone else's equipment, which may be borderline bad to start with.

Here's what I suggest to solve this...

Your mission, should some hot programmer out there choose to accept it, is to
create a program that will create a "special" disk, and a
Commodore-compatible program to try to read that special disk.

Ideally, the Commodore-compatible reading program would be short and simple
enough to fit inside 8k of memory, so it could fit on a cartridge. This would
allow it to work, even if a user's disk drive would not load programs
anymore. It could still be stored on a diskette, too, with a little planning.

Theoretically, once you had the specially-formatted diskette, and the program
on cartridge, you would only need a screwdriver to take the drive apart, and
a Commodore microcomputer to run the program on. No other special tools would
be needed, and very little technical knowledge would be required; just some
general safety tips, because you are working around sensitive electronic
parts, with wall current coming into the drive itself, at least on older

Why should a programmer go to all the trouble?

I'm sure there are a lot of people out there who could use this, if some hot
programmer should decide to write it, and make it available to the rest of
us. There are always programmers out there, somewhere, eager to show off
their computer skills, and their creativity. It is one of the things that
makes the computer community so great in the first place. (If people out
there can write IBM-to-Commodore disk file readers, this should be a breeze!)

Techies should appreciate it as a great, reliable and cheap way to align
troublesome disk drives, and those people with a C64 in a closet would sure
appreciate their technical buddies getting their dead systems going again!

Description of what I have in mind, as to how it should work.

You need a Commodore computer (the 64 is most popular), one 1541 disk drive
that needs alignment (or that you want to check), a screwdriver to open the
drive up, a specially-prepared disk used only for alignment purposes, and a
computer program that would run on the Commodore that would look at and
analyze the information that is on the specially-prepared diskette, as the
computer program tries to read it.

OK. Here's where it gets cute.

The problem with most disk alignment methods, as I see it, is that it relies
totally on technology that the Commodore has available; trying to create a
special disk on a 1541, I just don't see as being realistic, or the best way
to do it. The 1541 has many limitations, compared to some other disk drives
which operate on other computer platforms. Don't get me wrong; I love
Commodore computers, and have for years. But, realistically, the 5.25 inch
drives found in say an IBM machine, are just plain better in many ways than
the 1541. They are made to hold much more information, and to do that, they
have to be much more precise in doing so than the 1541 was ever designed to

If a person were to do this, I would suggest that they write an IBM program
that would use a high density, 1.2 megabyte capacity, 5.25-inch type of disk
drive to create the special diskettes, which the 1541 would later read.

Doing this would allow the creation of very thin tracks on the diskette's
surface, spaced closely together. This would, within the limitations of the
1541's read head, allow the Commodore to "see" precisely where it currently
was, to one side or the other of some "centered" position. The advantage of
thin tracks, widthwise, is that the read head won't see them at all,
reliably, unless you are exactly, perfectly right on top of them. Another
advantage to this, again within the limitations of the 1541's read head
(whatever that may be), is that left or right of center, the head would
likely pick up the next track over, letting you know you were off by a
certain amount automatically.

I hope I'm making myself clear, in my explanation of this. If I am not, Email
me with your questions, and I'll try to answer them better, and/or update
this file, to entice someone else to work on this. I really would like to see
it done. (Current Email address, as of Sep 94: on the
Internet, or just WardShrake on AOL. Will soon have a Compuserve Email
address, too: I'll be user 75207,1005 there, or on
the Internet.)

Anyway, let's continue. With the IBM creating a specially-made disk just for
this one purpose, you would not even have to worry about following any
standard formatting procedures. No user-stored data would ever be written to
the diskette, so standard sectoring could be safely ignored. You could create
any signal or sectoring scheme you like, as long as the IBM could create it,
and the Commodore could read it; and you'd be writing both programs, anyway,
making this easier to insure, right?

I can hear some die-hard Commodore users saying, "I hate IBM's" or "I don't
even have an IBM" or some such. Fine. Not a problem. If all the
IBM-compatible program did was to create a special floppy disk, once, then
quit, you would not even need to OWN an IBM, you'd just need to be able to
USE one for a few minutes.

Even if you don't have access to one at work, and don't know of anyone who
has one to lend you, I will stick with this suggestion, because I know that
some businesses that make photocopies often also rent IBM's and Mac's on an
hourly basis, for very little money. My local Kinko's copy center rents them
both at $10.00 an hour. You would only need it for a few minutes or so.

The diskette-creation program would only need a few minutes to run, to make
up a special disk, so you'd only be paying for a good quality, blank high
density floppy, and ten or fifteen minutes of rental time, tops. The copy
center person may even be able to start up the floppy-based IBM program for
you, if you don't know how to do it yourself. That should come to $5.00 or
less, even if you don't own or normally have access to an IBM compatible
computer! You can't beat that, for a utility to align equipment!

OK. In overview, you'd need to use an IBM-compatible computer, just long
enough to load an IBM-compatible program which would create one special,
5.25" diskette, perhaps on a high density floppy. You would then open up your
Commodore drive's case, and start up a special program on your Commodore 64,
to read the created diskette. (Again, an 8k Commodore program would fit very
easily on a cartridge, for easiest loading and running.)

While the computer and drive were running, you would (very carefully, and
observing safety precautions) loosen the stepper motor's screws, and slowly
turn the motor clockwise and counter-clockwise, until the Commodore program's
screen info told you that you were exactly where you should be, right over
the proper track. Not to the left or right of it, but in perfect alignment.

Because the Commodore disk-reading program would be "on" constantly, and
reporting any small changes to you via information on your screen, you would
only have to take a few minutes of fiddling, doing a simple, non-technical
turning of the stepper motor, to get the drive aligned. The two computer
programs that would make up this package would be doing most of the work.

I imagine a drive could be perfectly aligned, and back in running order, in
fifteen minutes or less. Five, if you paid attention to the process, and had
some practice before. Remember, this is based on an alignment procedure I
really used to do, using a heavily-protected diskette, so I am extrapolating
from my personal experiences, even though I'm talking about a theory here.

I don't see where there would be any easier, simpler method of doing a disk
alignment. The user wouldn't even have to know a thing about tracks and
sectors; they would just loosen two screws, following some instructions, and
turn the motor. What could be any easier?

The program could, if it was really creative and well-done, tell them to
rotate the motor clockwise or counter-clockwise (as they face it), to dial
the motor precisely in. Tracks to either side of an arbitrary (track 18?)
center position would say to go one way, tracks on the other would say the
reverse. When you turn it too far one way, it would reverse its instructions
to you; you would know you were very close then. When you were "right on,"
the program would tell you so. You'd lock the screws down, carefully, and as
long as you hadn't jiggled the motor when you tightened it back down, you
would be all done!

How much easier could it be, right? (On the final user, that is!)

If anyone is interested in doing this, or goes out and does it, please let me
know via Email. I'd like to hear about it. Again, it would be something
possible, useful, and a really neat trick. I know there are people out there
that program on both the IBM and the Commodore; the various cross-reading
programs attest to that, well enough!

Ward Shrake
Covina, California

magazines/chacking9.txt · Last modified: 2015-04-17 04:34 by