User Tools

Site Tools



This shows you the differences between two versions of the page.

Link to this comparison view

magazines:chacking18 [2015-04-17 04:34] (current)
Line 1: Line 1:
 +                   ########​
 +             ##################​
 +         ###### ​           ######
 +      #####
 +    #####  ####  ####      ##      #####   #### ​ ####  ####  ####  ####   #####
 +  #####    ##    ##      ####    ##   ## ​  ## ​ ###     ## ​   ####  ##   ## ​  ##
 + ##### ​   ######## ​    ## ​ ##   ## ​       #####       ## ​   ## ## ##   ##
 +#####    ##    ##    ######## ​ ##   ## ​  ## ​ ###     ## ​   ##  ####   ## ​  ##
 +#####  ####  ####  ####  ####  #####   #### ​ ####  ####  ####  ####   ######​
 +#####                                                                    ##
 + ###### ​           ###### ​          Issue #18
 +   ################## ​            July 3, 1999
 +       ########​
 + Time flies like the wind; fruit flies like a banana.
 + "Yet if the only form of tradition, of handing down, consisted in
 + following the ways of the immediate generation before us in a
 + blind or timid adherence to its successes, '​tradition'​ should be
 + positively discouraged. ​  We have seen many such simple currents
 + lost in the sand; and novelty is better than repetition. ​ [Tradition]
 + cannot be inherited, and if you want it you must obtain it by great
 + labor."​
 + T. S. Eliot, "​Tradition and the Individual Talent"​
 + Tradition. ​ C=Hacking is a magazine of considerable tradition, a
 +tradition that is not merely imitating the successes of previous generations.
 +C=Hacking has a tradition of true excellence, and once again, great people
 +have contributed their time and talents towards the 64 community, and another
 +fine issue of C=Hacking. ​ And once again, innovative ideas have grown and
 +borne delectable fruits for those seated at the C=Hacking table. ​ And once
 +again, in keeping with one of C=Hacking'​s most hallowed traditions, the
 +issue is really, really late.
 + A new job, a new move, a new house, a new puppy -- what can I say?
 +This real world stuff takes some getting used to, and so this issue is
 +even later than usual. ​ On the plus side, I think you'll find it worth
 +the wait (at least, I hope so!).
 + A lot has happened in the C= world since the last issue of C=Hacking.
 +At the personal level, all my moves and uphevals have led to a few changes.
 +Now that I'm fabulously wealthy (compared to my old graduate student salary,
 +I am fabulously wealthy), I splurged on a custom domain name.  The Fridge
 +is now located at http://​,​ and thus the Official Unofficial
 +C=Hacking Homepage is now located at
 + http://​​fridge/​chacking/​
 +And in the "well, it's news to me" department:
 + - SuperCPU acitivty seems to be picking up steam, at long last.
 +   The scpu mail list recently revived itself, and several
 +   new projects and web pages have appeared.  ​
 +   Moreover, 65816 assemblers have finally started to appear.
 +   El Cheapo Assembler (CheapAss), for SuperCPU-equipped 64s,
 +   is available in the Fridge. ​ A new cross-assembler,​ ACME, is
 +   being worked on.  And a *third* project, Virtualass816,​ is said
 +   to be underway.
 +   In addition to the assemblers, there is an 816 backend to LCC,
 +   some new operating system projects (e.g. JOS), rumors of games, ​
 +   game fixes (including a Flight Simulator patch by Maurice Randall),
 +   and more.
 +   http://​​supercpu is a good place to learn more about new SCPU
 +   developments.
 + - There will be an English version of GO64! magazine, starting with the 
 +   August issue, available by subscription. ​ For more information,​ visit
 +   http://​
 + - The annual Chicago Expo will take place in -- amazingly enough --
 +   Chicago, on September 25.  Last year was a hoot and hopefully ​
 +   our group will be there again this year.  For more details,
 +   http://​​~rgharris/​swrap.html will surely contain
 +   info as the date draws near.
 + - Justin Beck, a dj at station KDVS, in Davis, CA, runs a SID radio
 +   show every tuesday night at 8PM Pacific time.  You can tune in at
 +   90.3 in the Davis/​Sacramento area, or the webcast at
 +   http://​ ​ For more information,​ write;​
 +   actually, write him anyways, to tell him how cool the show is!
 + - CBM FIDO echos are available at several places on the web
 +   these days -- http://​ is a pretty popular source.
 + - Craig Bruce has been placing old issues of the Transactor online at
 + http://​​~csbruce/​commodore/​transactor/​
 +    Well worth a visit.
 + - Another unzip program has appeared, by Pasi Ojala. ​ Visit his homepage
 + http://​​~albert/​
 +   for more details.
 + - Richard Atkinson <​>​ has drawn up schematics
 +   of the Commodore Sound Expander, a Yamaha-chip-based cartridge.
 +   For more info, email Richard! ​ (And maybe look in a future issue
 +   of C=Hacking???​ :)
 +This weekend, here in the states, we are celebrating our independence
 +(I think the Canadians tried to invade us once, or something...). ​ I can't
 +help but reflect that the Commodore 8-bits also represent a kind of
 +independence. ​ As the above (and the below) amply demonstrates,​ that
 +independent spirit is alive and thriving, a fact that is not only remarkable,
 +but even perhaps worth celebrating.
 +Enjoy the issue!
 +.                                    C=H 18
 +:::::::::::::::::::::::::::::::::::​ Contents ::::::::::::::::::::::::::::::::::​
 + o Voluminous ruminations from your unfettered editor.
 + o Maybe next time!
 +The C=Hallenge
 + o Yet again, no C=Hallenge, sigh...
 +Side Hacking
 + o Data Structures 101: Linked Lists
 +   DS101 is a new article series. ​ The idea is to review different
 +   data structures, with examples of their use in 64 programming.
 +   This installment covers linked lists, and how to use them to
 +   make a zippy insertion sort with almost zero overhead.
 + o Counting Sort, by Pasi Ojala
 +   And, since we're on the subject of sorting algorithms, Pasi
 +   wrote up the counting sort.  What's a counting sort?  Well,
 +   read the article!
 +Main Articles
 + o "​VIC-20 Kernel ROM Disassembly Project",​ by Richard Cini
 +   <​>​
 +   This installment covers interrupts -- IRQ and NMI sources,
 +   the kernal handler code.
 + o "A Diehard Programmer'​s Introduction to GEOS, and geoWrite ​
 +    ​Disassembly Notes",​ by Todd S. Elliott <​>​
 +   As part of his effort to learn about GEOS programming,​ Todd
 +   disassembled geoWrite 128, and patched it to be more accepting
 +   of new devices and such.  This article summarizes that adventure --
 +   the results and the lessons learned.
 + o Masters Class: "​NTSC/​PAL fixing: FLI", by Russell Reed 
 +   <​>,​ Robin Harbron <​>,​ and S. Judd.
 +   This time around the subject is FLI and IFLI graphics, and
 +   a tutorial on fixing them.  Several pictures are provided for
 +   the reader to fix, in the included .zip file.
 + o "​Obj3d:​ The 3D object library",​ by S. Judd <​>​
 +   Obj3d is a set of routines for creating, manipulating,​ and displaying
 +   3D worlds on the 64.  It consists of routines to add and remove
 +   objects, move and rotate objects, render the display, and so on,
 +   and hence vastly simplifies the creation of 3D programs.
 +   This article actually consists of three articles. ​ The first article
 +   describes the library, and walks through a simple example program.
 +   The second article is the "​programmer'​s guide",​ containing memory
 +   maps and a list of all the routines.
 +   The third article discusses "​stroids",​ a more advanced example
 +   program in which a space ship can fly around a randomly drifting
 +   asteroid field, to demonstrate that sophisticated programs can be
 +   written with only a few hundred lines of code.  The program is
 +   purposely left incomplete -- it's up to you to finish it up!
 +   We had hoped to include a "3D Object Editor",​ written by Mark
 +   Seelye, but it wasn't quite done yet -- next issue!
 +   Source code and binaries are included at the end of the issue.
 +   ​
 +.................................. Credits ...................................
 +Editor, The Big Kahuna, The Car'​a'​carn..... Stephen L. Judd
 +C=Hacking logo by.......................... Mark Lawrence
 +For information on the mailing list, ftp and web sites, send some email
 +Legal disclaimer:
 + 1) If you screw it up it's your own fault!
 + 2) If you use someone'​s stuff without permission you're a serious dork!
 +About the authors:
 +Todd Elliot is 30 years old, working as a Community Client Advisor
 +for the Center On Deafness - Inland Empire, working with deaf people in
 +the local communities. ​ He got his first 64 in December 1983, using it
 +for games, BBS activities, and dabbling in programming. ​ Nowadays Todd
 +focuses on ML programming,​ and his latest project is an AVI movie player
 +for the SuperCPU. ​ Todd is a huge fan of sports and the Miami Dolphins in
 +particular, and enjoys writing articles and watching movies. ​ At the 1998
 +Chicago Expo Todd enjoyed meeting all those people who were previously
 +a name and an email address; according to Todd, "The Chicago Expo truly
 +embodies what is going on with today'​s CBM community."​
 +Richard Cini is a 31 year old vice president of Congress Financial
 +Corporation,​ and first became involved with Commodore 8-bits in 1981, when
 +his parents bought him a VIC-20 as a birthday present. ​ Mostly he used it
 +for general BASIC programming,​ with some ML later on, for projects such as
 +controlling the lawn sprinkler system, and for a text-to-speech synthesyzer.
 +All his CBM stuff is packed up right now, along with his other "​classic" ​
 +computers, including a PDP11/34 and a KIM-1. ​ In addition to collecting
 +old computers Richard enjoys gardening, golf, and recently has gotten
 +interested in robotics. ​ As to the C= community, he feels that it
 +is unique in being fiercely loyal without being evangelical,​ unlike
 +some other communities,​ while being extremely creative in making the 
 +best use out of the 64.
 +Pasi '​Albert'​ Ojala is a 29 year old software engineer, currently
 +working at a VLSI design company on a RISC DSP core C compiler.
 +Around 1984 a friend introduced him to the VIC-20, and a couple
 +of years later he bought a 64+1541 to replace a broken Spectrum48K.
 +He began writing his own BBS, using ML routines for speed, and
 +later wrote a series of demos under the Pu-239 label. ​ In addition
 +to pucrunch and his many C=Hacking articles, Pasi's most recent project
 +is an "​unzip"​ program for the 64.  Pasi is also a huge Babylon-5 fan, and
 +has a B5 quote page at http://​​~albert/​Quotes/​B5-quotes.html
 +Robin Harbron is a 26 year old internet tech support at a local
 +independent phone company. ​ He first got involved with C= 8-bits
 +in 1980, playing with school PETs, and in 1983 his parents convinced
 +him to spend the extra money on a C64 instead of getting a VIC-20.
 +Like most of us he played a lot of games, typed in games out of
 +magazines, and tried to write his own games. ​ Now he writes demos,
 +dabbles with Internet stuff, writes C= magazine articles, and, yes,
 +plays games. ​ He is currently working on a few demos and a few games,
 +as well as the "​in-progress-but-sometimes-stalled-for-a-real-long-time-
 +until-inspiration-hits-again Internet stuff"​. ​ He is also working on
 +raising a family, and enjoys music (particularly playing bass and guitar), ​
 +church, ice hockey and cricket, and classic video games.
 +.................................. Jiffies ...................................
 +............................... The C=Hallenge ...............................
 +................................ Side Hacking ................................
 +Data Structures 101 -- Linked lists and a nifty sorting algorithm
 + by S. Judd and Pasi Ojala --
 + Every computer science major has taken a class in data structures.
 +The 64 programming world, however, is full of non-CS majors. ​ Data structures
 +are such useful tools for programmers to have in their toolbox that I thought
 +it would be a worthwhile thing to have various people periodically review
 +different types of data structures, and common algorithms which make use 
 +of them, and their relevance to an 8-bit CBM.
 + What is a data structure? ​ Perhaps a reasonable definition is that
 +it is an abstract method of storing and retrieving data.  These abstractions
 +may then be implemented on the computer, to solve different types of problems.
 +Whereas no single data structure is ideal for all problems, often a given
 +problem has a data structure ideally suited for it.  The "​optimal"​ data
 +structure for a given problem depends heavily upon what kind of data is
 +stored, and how it is stored, retrieved, or otherwise manipulated.
 + (On a more humorous note, /dev/null is ideal for storing information
 +provided you don't need to retrieve it, ever -- this probably doesn'​t
 +count as a data structure, though).
 + A simple example of a data structure is an array: abstractly, it
 +stores and retrieves data by means of an index value, e.g. A$(I). ​ On the
 +computer it might be implemented in many different ways, depending on the
 +type of data stored (integers, strings, floating point, custom records, etc.),
 +the dimension of the array, the computer hardware, and even whether the index
 +is e.g. an integer or a string. ​ So it is worthwhile to distinguish the
 +abstract concept of an "​array"​ from its actual implementation.
 + Another type of useful data structure is a linked list.  Imagine ​
 +attaching a pointer to some chunk of data (usually called a "​node"​). ​ This
 +pointer can then point, or link, to another chunk of data -- the next node
 +in the list.  This can be schematically illustrated as:
 +  +---+     ​+---+ ​    ​+---+ ​    ​+---+ ​    +---+
 +  | 3 |---->| 1 |---->| 4 |---->| 5 |---->| 9 |----> ...
 +  +---+     ​+---+ ​    ​+---+ ​    ​+---+ ​    +---+
 +Here each node contains some data (a number) and a pointer to the next node.
 +Starting from the first node -- called the "​head"​ of the list -- a program
 +can reach every other node by following the pointers. ​ This is the basic
 +idea of a linked list.
 + Linked lists are used all over the place in the 64.  BASIC programs
 +are a type of linked list.  Each line of a basic program is stored as
 +a line link, then a line number and the data.  The link points to the next
 +line in the program. ​ Having links speeds up BASIC programs (imagine
 +finding and counting the ends of each line for every GOTO or GOSUB), and
 +links are useful because each program line can be of different size
 +(imagine storing a program in something like an array). ​ The end of the
 +list is specified with a line link of 00.
 + A better example is the DOS.  Every 256-byte sector on a disk
 +drive starts with a 2-byte track and sector pointer, pointing to the next
 +sector in the chain, and contains 254 bytes of data.  This is exactly a
 +linked list.  The directory tells where the first sector of a program is --
 +the head of the list -- and the program is loaded by simply traversing the
 +list -- following each link and reading the data.  The end of the list is
 +specified by using a special link value (track = $FF).
 + The reason it is "​better"​ is that whereas a BASIC program is
 +stored sequentially in memory, a program stored on disk might be strewn all
 +over the place. ​ This is an important feature of linked lists: they can join
 +together data that is spread all over memory. ​ If you think about it for 
 +a moment, you'll quickly realize that elements may be added into (or removed
 +from) the middle of a list simply by changing a pointer:
 +  +---+     ​+---+ ​    ​+---+ ​            ​+---+ ​    +---+
 +  | 3 |---->| 1 |---->| 4 |-+        +->| 5 |---->| 9 |----> ...
 +  +---+     ​+---+ ​    +---+ |        |  +---+     +---+
 +     |        |
 +     |  +---+ |
 +     +->| 1 |-+
 +        +---+
 +(Well, okay, two pointers :).  Inserting sectors into the middle of a 
 +program isn't exactly very useful for disk storage, but there are plenty of
 +applications where it is extremely useful; one such application,​ discussed
 +below, is sorting data.
 + Both BASIC and the disk drive are examples of a forward- or singly-
 +linked list.  But imagine attaching a second pointer to each disk sector,
 +pointing to the _previous_ track and sector:
 +  +---+     ​+---+ ​    ​+---+ ​    ​+---+ ​    +---+
 +  |   ​|---->​| ​  ​|---->​| ​  ​|---->​| ​  ​|---->​| ​  ​|---->​ ...
 +  |   ​| ​    ​| ​  ​| ​    ​| ​  ​| ​    ​| ​  ​| ​    ​| ​  |
 +  |   ​|<​----| ​  ​|<​----| ​  ​|<​----| ​  ​|<​----| ​  ​|<​---- ...
 +  +---+     ​+---+ ​    ​+---+ ​    ​+---+ ​    +---+
 +This would be a "​doubly-linked list"​. ​ The downside would be a loss of two
 +bytes per sector; the upside would be that if your directory track were
 +destroyed, you could recover all programs on a disk -- in a singly-linked
 +list like C= DOS, you have to know where the start of the list, i.e. the
 +first track and sector, is.  Moreover, with a doubly-linked list you
 +can _delete_ a node without even knowing where the node is in the list;
 +with a singly-linked list, you have to search through the list to find
 +the pointer to the node.
 + Of course, you could add even more pointers to a list, which is
 +done for certain types of trees, for example. ​ (Trees will perhaps be
 +covered some other time).
 +A fast sorting algorithm
 + Let's say you had a list of numbers that you wanted to sort from
 +largest to smallest. ​ For example, the obj3d library, discussed later
 +in this issue, needs to depth-sort objects, so that far-away objects
 +don't overlap nearby objects when drawn on the screen. ​ This amounts to
 +sorting a list of 16-bit numbers. ​ These numbers are stored in a simple
 +list -- not a linked list, but an array, like:
 + lo0
 + lo1
 + lo2
 + ...
 + hi0
 + hi1
 + hi2
 +A typical sorting algorithm would have to spend a lot of time swapping
 +numbers, moving stuff around, etc.  Even with 16-bits this is a fair
 +amount of overhead, and with even larger numbers it gets very time-
 + But, as mentioned earlier, a linked list is tailor-made for
 +rearranging data.  That is, if we start with a list of sorted numbers,
 +then inserting a new number into the list amounts to finding the right
 +spot in the list, 
 +  +---+     ​+---+ ​    +---+
 +  | 1 |---->| 2 |---->| 5 |---
 +  +---+     ​+---+ ​    +---+
 +changing the first part of the list to point to the new number,
 +  +---+     ​+---+ ​    ​+---+  ​
 +  | 1 |---->| 2 |---->| 5 |-+        ​
 +  +---+     ​+---+ ​    +---+ | 
 +     |       
 +     |  +---+ 
 +     +->| 6 |---
 +        +---+
 +and having that new number point to the rest of the list.
 +  +---+     ​+---+ ​    ​+---+ ​            ​+---+ ​    +---+
 +  | 1 |---->| 2 |---->| 5 |-+        +->| 8 |---->| 9 |----> ...
 +  +---+     ​+---+ ​    +---+ |        |  +---+     +---+
 +     |        |
 +     |  +---+ |
 +     +->| 6 |-+
 +        +---+
 +So sorting a list of numbers amounts to inserting these numbers into
 +the right spot in the list, one at a time.  Amazingly enough, this type
 +of sort is called an "​insertion sort".
 + Your first thought might be "​doesn'​t that mean a whole lot of
 +overhead in copying data and changing pointers?​!" ​ It might, if we were
 +lame PC programmers;​ but, of course, we are 64 coders, which means we
 +are handsome, witty, superior, humble -- and most of all, sneaky. ​ All
 +we _really_ need is
 + a) an index into the list of numbers
 + b) a pointer to the next number
 +The trick is simply to combine the two, by storing the linked list just
 +like the list of numbers, as a sequential array of *8-bit* links:
 + link0
 + link1
 + link2
 + ...
 +Now each link not only points to the next list element -- it exactly points
 +to the next number as well.  For example, let's say that the earlier list
 +of numbers was 23,16,513
 + 23
 + 01
 + 16
 + 00
 + 02
 + 00
 +Sorting from largest to smallest should give 1-0-2 -- that is, element 1
 +is the largest, followed by element 0, followed by element 2.  So the linked
 +list could have the form
 +head = 1
 + 2
 + 0
 + $80
 +To see how this works, start with the head of the list, which points to
 +list element 1.
 + LDX head
 +To get the next element in the list is easy:
 + LDA list,X
 +Now .X is the current element, and .A is a link to the next element. ​ To
 +traverse the entire list, then we simply need to TAX and loop:
 + LDX head
 +:loop LDA list,X
 + TAX
 + BPL :loop
 +This will traverse the list, until the $80 is reached. ​ The thing to
 +realize is that .X is *also* a list into the list of numbers, so for
 +example you could print out the numbers, in order, with some code like
 + LDX head
 +:loop LDA lobyte,X
 + LDY hibyte,X
 + JSR PrintAY
 + LDA list,X
 + TAX
 + BPL :loop
 +Now all we have to do is construct the list!
 + get next coordinate
 + traverse linked list
 +     compare to coordinates in linked list, to find correct spot
 + split linked list in half
 + make bottom half of list point to the new element
 + make the new element point to the rest of the list
 +So far it is just your basic insertion-sort;​ if you are familiar with
 +AmigaOS, it is similar to Enqueue(), which inserts nodes by priority.
 +The beauty here is that the index registers make all this really, really
 +easy; here's the sort code from obj3d, to sort positive numbers, stored
 +in CZ and HCZ = lo and hi bytes, from largest to smallest. ​ It starts
 +at the head of the list (at list+$80), traverses the list to find the
 +right spot, and inserts the "​node":​
 + :​loop [copy number to be inserted to TEMP, TEMP+1 for a little speed]
 +         LDY #$80         ;Head of list
 +:l1      LDA VISOBJS,​Y ​   ;Linked list of objects
 +         BMI :link
 +         STY TEMPY
 +         ​TAY ​             ;Next object
 +         LDA CZ,Y         ;If farther, then
 +         CMP TEMP         ;move down list
 +         LDA HCZ,Y
 +         SBC TEMP+1
 +         BCS :l1
 +                          ;Nearest objects last in list
 +         TYA
 +         LDY TEMPY        ;Insert into list
 +:link    STA VISOBJS,​X ​   ;X -> rest of list
 +         TXA
 +         STA VISOBJS,​Y ​   ;beginning of list -> X
 +         DEX
 +         BPL :loop
 +:rts     RTS
 +Here, VISOBJS is the linked list of sorted coordinates.
 + Personally,​ I think that's awfully nifty -- an insertion sort with
 +almost zero overhead.
 + In summary, a linked list is simply an abstract method of storing and
 +retrieving data.  For certain kinds of problems it is extremely useful, even
 +optimal, and for certain problems it can be implemented in a very efficient
 +and convenient way.  And as pointed out in the beginning of this article, that
 +is the essence of a data structure.
 + I suppose that about wraps things up.  So, who wants to volunteer the
 +next data structures article?
 +    ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::​
 +Counting sort
 +------------- by Pasi Ojala <​>​
 + Because we got ourselves tangled up with sorting algorithms, we may
 +as well take a look into another one called counting sort.  The idea is to
 +sort a list of symbols according not to the symbols themselves, but rather
 +according to a "​key"​ associated with the symbols. ​ This will become clear
 +shortly. ​ This sorting algorithm can be used with integer-valued data when
 +the range of sorting key values (the values by which the order is decided)
 +is small enough.
 + The foundation for counting sort is to make one pass through the
 +data and determine the sorted order of the data, then make another pass and
 +perform the actual shuffling of data.
 + Now, what kind of a data structure is used for counting sort?
 +Surprise, we only need a counter for every possible sorting key.  This is
 +why a limited range of values is required or the memory consumption would
 +simply be too large. ​ And we need an array of input data and an array for
 +the sorted output data, as the sorting can't be performed in place.
 + An example sorts the following data, which in fact represents a
 +Huffman tree.  The sorting keys are the code lengths, and the associated
 +data is the corresponding symbol. ​ The sorting of symbols is required in
 +some of the Huffman tree creation routines.
 + key: 4 3 1 5 3 6 3 6
 + sym: A B C D E F G H
 + The sorting keys can have value from 1 to 6, so we need 6 counters.
 +Their initial values are set to zero:
 + for(i=1;​i<​7;​i++)
 +     count[i-1] = 0;
 + count: 0 0 0 0 0 0
 + Then we perform the counting stage by going through the data and
 +incrementing the counter corresponding to the sorting key value. ​ In the
 +end the counters contain the number of each code length found in the data.
 + for(i=0;​i<#​ofvalues;​i++) {
 +     count[sort_key[i]-1] = count[sort_key[i]-1] + 1;
 + }
 + count: 1 0 3 1 1 2
 + Then cumulative counts are calculated for this array.
 + for(i=1;​i<​6;​i++) {
 +     count[i+1] = count[i+1] + count[i];
 + }
 + count: 1 1 4 5 6 8
 + If you take a close look into the meaning of the values now in the
 +count array, you might notice that the last count value gives us from which
 +element downward to stick the data with sorting key 6, the previous one
 +where to stuff data with key 5 and so on.
 + So, next we simply copy each element in the data into its final
 +place in the output array using the cumulative counts calculated
 +previously. ​ Note that in C the indexing starts from 0, but in the table
 +from 1 for the reader'​s convenience.
 + for(i=#​ofvalues-1;​i>​=0;​i--) {
 +     count[key[i]-1] = count[key[i]-1] - 1;
 +     outputkey[count[key[i]-1]] = key[i];
 +     outputsym[count[key[i]-1]] = sym[i];
 + }
 +            1 2 3 4 5 6   1 2 3 4 5 6 7 8
 + ---------------------------------
 + 6   1 1 4 5 6 8   x x x x x x x 6
 + H             ​7 ​  X X X X X x X H
 + 3   1 1 4 5 6 7   x x x 3 x x x 6
 + G       ​3 ​        X X X G X x X H
 + 6   1 1 3 5 6 7   x x x 3 x x 6 6
 + F             ​6 ​  X X X G X x F H
 + 3   1 1 3 5 6 6   x x 3 3 x x 6 6
 + E       ​2 ​        X X E G X x F H
 + 5   1 1 2 5 6 6   x x 3 3 x 5 6 6
 + D           ​5 ​    X X E G X D F H
 + 1   1 1 2 5 6 6   1 x 3 3 x 5 6 6
 + C   ​0 ​            C X E G X D F H
 + 3   0 1 2 5 6 6   1 3 3 3 x 5 6 6
 + B       ​1 ​        C B E G X D F H
 + 4   0 1 1 5 6 6   1 3 3 3 4 5 6 6
 + A         ​4 ​      C B E G A D F H
 + So, after the loop we have sorted the symbols according to the code
 +lengths. ​ We haven'​t talked about the O() notation, but the counting sort
 +is an O(n) process. ​ Counting sort only needs three fixed-sized loops, thus
 +if the amount of data increases linearly, the sorting time also increases
 +linearly. ​ With insertion sort the sorting time increases geometrically
 +because searching for the right spot to add a node takes longer and longer
 +when the number of already sorted nodes increases.
 + One extra bonus for counting sort is that it preserves the
 +so-called inside order. ​ The order of elements with identical keys is
 +preserved in the end result. ​ This is very important in many algorithms,
 +like the Huffman tree creation GZip uses (and which gunzip.c64 used
 +before the algorithm was changed to not require sorting).
 + On the minus side is the necessity of the output buffer. Other
 +sorting algorithms can sort the data in-place, although that also means
 +that the data must be copied a lot of times.
 + The preservation of inside-order also allows the sorting of data by
 +multiple keys:  first sort by secondary keys, then by primary keys.  After
 +this the data is sorted by the primary keys and all elements having
 +identical keys are sorted by the secondary keys.
 + And that's it!
 +.                                   C=H #18
 +   Main Articles
 +   -------------
 +VIC KERNAL Disassembly Project - Part II
 +Richard Cini
 +May 21, 1999
 + Last issue, we began by examining the VIC's start-up sequence. We 
 +began with the processor jump at power-on to location $FFFC upon power-up ​
 +and ended at the Kernal’s jump into the BASIC ROM.
 + This issue, we will look at the two other hard-coded processor ​
 +vectors, the IRQ and NMI vectors. Just like the RES* pin on the 6502 
 +processor, the IRQ* and NMI* pins (the hardware portion of the interrupt) ​
 +are hard-coded to two memory locations, $FFFE and $FFFA, respectively. When 
 +the processor senses a negative-going pulse on these pins of at least 20uS 
 +in duration, the processor will jump to the respective vector locations ​
 +(the software portion of the interrupt, also called an "​interrupt ​
 +FFFA ;​===============================
 +FFFA ; - Power-on Vectors
 +FFFA            ;
 +FFFA A9 FE .dw NMI ;​$FEA9
 +FFFC 22 FD .dw RESET ;​$FD22
 +FFFE 72 FF .dw IRQ ;​$FF72
 + Both the IRQ* and NMI* lines are available at the expansion connector
 +at the back of the VIC. They are also connected to the IRQ* output lines on
 +the VIC's two 6522 VIA chips (just like the 64); the IRQ* is connected to 
 +VIA2 and the NMI* to VIA1.
 + Hardware interrupts on the VIC-20 may be triggered by any one of 
 +several events which is recognized by either one of the VIC's two VIA 
 +chips, or by an external event which triggers the IRQ* or NMI* pins on the 
 +VIC's expansion connector. The software IRQ routine (at $FF72) is also 
 +entered by the execution of the BRK processor instruction.
 + The VIA chips are capable of generating a hardware interrupt based 
 +on the events as outlined in register $911D, as described in the section ​
 +titled "VIA Registers Used in Following Code." In general, interrupt events ​
 +result from the VIA's internal timers reaching 0 and active transitions in 
 +the logic level of the CA1/​CB1/​CA2/​CB2 handshaking pins. 
 + Some of the timers and handshaking pins are programmed or connected ​
 +to hardware within the VIC. For example, timer 1 on VIA2 is initialized to 
 +provide a periodic interrupt every 0.015 seconds, or an approximate rate of 
 +65 Hz. This interrupt is used to update the time-of-day clock and to 
 +provide timing pulses for cassette tape operations. This timer sharing is 
 +the main reason behind the jiffy clock losing time during tape operations. ​
 +Similarly, the processor NMI* line is triggered by the IRQ* output on VIA1. 
 + In summary, by default, the interrupt handler code is executed as a 
 +result of the following:
 + For the IRQ code: the 60Hz periodic interrupt from Timer 1 on VIA2 
 + and the processor BRK instruction.
 + For the NMI code: pressing of the RESTORE or Run/​Stop-RESTORE key 
 + combinations and RS232 operations.
 + The IRQ and NMI software routines could also be triggered if the 
 +programmer makes use of some of the other VIA pins for a home-brewed ​
 +project. For example, if a home-brew alarm system is configured to trigger ​
 +the CA2 pin when there is motion in the back yard. The user's program could
 +be set up to watch for an interrupt generated by the CA2 pin, to sound a
 +VIA Registers Used in the Following Code
 + The 6522 VIA has 16 byte-wide registers which control the 
 +functioning of the chip or enable the receipt or transmission of data on 
 +the I/O pins. For the sake of space, we'll only discuss the registers ​
 +directly applicable to this issue'​s code.
 + $9111 is the Port A output register, an 8-bit I/O port which 
 +automatically uses the CA1 and CA2 pins for handshaking. Port A is 
 +duplicated at $911F, but changes to the register do not affect CA1 and CA2 
 +(so, no handshaking). In the above code, only BIT7 and BIT6 are relevant. ​
 + The bitfields are:
 + BIT7 IEEE ATN out
 + BIT6 Cassette sense switch
 + BIT5 Joystick fire button
 + BIT4 Joystick left
 + BIT3 Joystick down
 + BIT2 Joystick up
 + BIT1 IEEE Serial Data In
 + BIT0 IEEE Serial Clock In
 + $911D is the register (the Interrupt Flag Register, "​IFR"​) that 
 +indicates which event triggered the interrupt. BIT3, BIT2, and BIT0 are 
 +left unprogrammed in the above code, but are shown below for completeness. ​
 +BIT4 manages the RS232 receive function, and BIT1 is connected to the 
 +RESTORE key.
 + BIT7 NMI status (set when any of the lower bits are set)
 + BIT6 Timer 1 time-out read T1 LW and wrt T1 HW latch
 + BIT5 Timer 2 time-out read T2 LW and wrt T1 HW latch
 + BIT4 CB1 transition R/​W Port B
 + BIT3 CB2 transition R/​W Port B
 + BIT2 Completion of 8 shifts R/W shift register
 + BIT1 CA1 transition R/​W Port A ($9111 only)
 + BIT0 CA2 transition R/​W Port A ($9111 only)
 + According to the 6522 data sheets, the shift register interrupt is 
 +an allowed interrupt, but there is a bug in early 6522s that prevented the 
 +shift register from working properly. In the VIC, the shift registers are 
 +disabled by default and remain unused in the KERNAL. It is possible for a 
 +user program to enable the shift registers and use them, but the quality of 
 +the results would be lacking because of the bug.
 + $911E (the Interrupt Enable Register, "​IER"​) is the register that 
 +enables or prevents the lower six bits in the IFR from triggering an 
 +interrupt. Writing a 0 to BIT7 clears the bits in the IER according to 
 +the bit pattern in the lower six bits. Writing a 1 to BIT7 sets the IER 
 +according to the bit pattern. For example, if one wanted to enable all of 
 +the above as sources of interrupts, one would write %11111111 to the IER. 
 +Disabling all of the above as interrupt sources would be accomplished by 
 +a write of %01111111 to the IER.
 + $9110 is the I/O register for Port B of VIA1. This 8-bit port is 
 +connected to pins PB0-PB7 of the VIC User Port. When the RS232 interface ​
 +module is connected to the User Port, Port B doubles as the RS232 port, 
 +with the following bitmap:
 + PB7 Data Set Ready (DSR) in
 + PB6 Clear To Send (CTS) in
 + PB5 [no connection]
 + PB4 Data Carrier Detect (DCD) in
 + PB3 Ring Indicator (RI) in
 + PB2 Data Terminal Ready (DTR) out
 + PB1 Request To Send (RTS) out
 + PB0 Receive Data
 + CB1 acts as the interrupt source for the NMI receive routine and is 
 +physically connected (externally) to PB0 so that a received bit of data 
 +triggers an interrupt. CB2 acts as the RS232 transmit line.
 + Register $9114 is an 8-bit register which holds the least-
 +significant byte of Timer 1's countdown value.
 + Registers $9118 and $9119 are the least-significant and most-
 +significant bytes of Timer 2's countdown value. Together, they provide a 
 +16-bit countdown capability for use as the baud rate clock.
 + Register $911C is called the Peripheral Control Register, the 
 +"​PCR"​. The PCR controls how the four handshaking lines (CA1/2 and CB1/​2) ​
 +act. On VIA1, CB2 is connected to the RS232 transmit line, and CA2 is 
 +connected to the cassette tape motor control circuitry. Both CA2 and 
 +CB2 have eight possible modes that can be manual or automatic, positively ​
 +or negatively triggered, input or output oriented. Input modes set flags 
 +in the IFR based on programmed transitions. When programmed as outputs, ​
 +CA2/CB2 lines are triggered based on writing to Port B.
 +NMI Processing
 +Let's look at the NMI routine first. The NMI is triggered through the 
 +use of the RESTORE key connected to the CA1 line, the CB1 RS232 receive ​
 +data line, and the expiration of Timer 1 and Timer 2, which manages framing ​
 +for RS232 transmit and receive operations, respectively.
 + The NMI code begins with some stub code that enables the redirection ​
 +of the NMI processing if so programmed by a user. The indirect jump at 
 +$FFEA is similar to chainable interrupt processing on MS-DOS based computer ​
 +systems -- save the old pointer, redirect pointer to your own code, and 
 +then chain to the system NMI processor. Several other Kernal vectors are 
 +handled in the same way: IRQ, Break, Open, Close, Load, Save, Set Input, ​
 +Set Output, Input, Output, Get Character, and Clear I/O Channel.
 +FEA9           ;​==============================================
 +FEA9           ; NMI - NMI transfer entry
 +FEA9           ;​==============================================
 +FEA9 78 SEI ; disable interrupts
 +FEAA 6C 18 03    JMP (NMIVP) ;​$FEAD allows redirection of NMI
 + The actual NMI processing code follows the redirect stub. The NMI 
 +routine is broken into three parts: RESTORE processing, RS232 transmit ​
 +management and RS232 receive management.
 +            ​
 +FEAD           ;​==============================================
 +FEAD           ; LNKNMI - Link to actual NMI code. The first
 +FEAD           ;​ part manages the R/S-R keys
 +FEAD           ​LNKNMI
 +FEAD 48          PHA ; save registers .A
 +FEAE 8A          TXA
 +FEAF 48          PHA ; .X
 +FEB0 98          TYA
 +FEB1 48          PHA ; .Y
 +FEB2 AD 1D 91    LDA D1IFR ;​check VIA1 interrupt flag register
 +FEB5 10 48        BPL WARMEOI ;no flag present, then issue EOI
 +FEB7 2D 1E 91    AND D1IER ;see if found interrupt is allowed
 + ; based on IER mask
 +FEBA AA          TAX ;save bitmask of enabled and active ​
 + ; interrupts
 +FEBB 29 02        AND #​%00000010 ;​VIA1/​CA1 RESTORE key?
 +FEBD F0 1F        BEQ WARM1 ;not RESTORE, so move on to RS232
 + ;Got here, so NMI must be RESTORE
 + ; key
 +FEBF 20 3F FD    JSR SCNROM ;​scan for A000 ROM (covered last
 + ; issue)
 +FEC2 D0 03        BNE LNKNMI1 ;no ROM at $A0, so skip ROM NMI 
 + ; routine
 +FEC4 6C 02 A0    JMP (A0BASE+2) ;​jump to ROM NMI routine
 +FEC7             ​LNKNMI1 ;​continue NMI processing.
 +FEC7 2C 11 91    BIT D1ORA ;test ATN IN(7)/cass switch (6)
 + ; bits
 +FECA 20 34 F7    JSR IUDTIM ;​update TOD clock
 +FECD 20 E1 FF    JSR STOP ;check for STOP key Z=1 if STOP
 + ; pressed
 +FED0 D0 2D        BNE WARMEOI ;no stop key, so skip vector ​
 + ; restore, VIC and I/O 
 + ; initialization. Go EOI.
 + We reach the following code block if Run/Stop is pressed along with 
 +the RESTORE key. The code results in a soft reset: it restores the default ​
 +system vectors, initializes the I/O chips to their default, and initializes ​
 +the screen editor. The routine then jumps into BASIC, bypassing BASIC'​s ​
 +normal initialization routines, leaving program RAM undisturbed.
 +FED2             ;​==============================================
 +FED2             ; WARMST - Default USER vector
 +FED2             ;​  ​   Also get here from BRK instruction (through IRQ
 +FED2 ;     vector) or RUN/​STOP-RESTORE key combination
 +FED2             ​WARMST
 +FED2 20 52 FD    JSR IRESTR ;​restore default vectors (covered ​
 + ; last issue)
 +FED5 20 F9 FD    JSR IOINIT ;​initialize I/O (covered last 
 + ; issue)
 +FED8 20 18 E5    JSR CINT1 ;​initialize screen editor (covered ​
 + ; last issue)
 +FEDB 6C 02 C0    JMP (BENTER+2) ;​jump to BASIC NMI routine
 + If program execution gets here without passing through the WARMST ​
 +code, then the NMI resulted from an RS232 event, such as when the transmit ​
 +or receive timers time-out (signaling that the VIC is done transmitting or 
 +receiving a character of information). For now, we'll skip the actual RS232 
 +transmit/​receive code; it'll be covered in a later issue.
 + Since the Kernal programmers could not reliably use the shift 
 +registers in the VIAs, it appears that they synthesized a shift register ​
 +in the NMI routine. This emulation enables the RS232 code to work properly. ​
 + The code continues with the NMI part of the RS232 transmit and 
 +receive character processing routine:
 +FEDE             ​WARM1
 +FEDE AD 1E 91    LDA D1IER ;get IER bitmap
 +FEE1 09 80        ORA #​%10000000 ;​set mask for enabling interrupts ​
 + ; according to existing bitmap
 +FEE3 48          PHA ;save "​enable"​ bitmap
 +FEE4 A9 7F        LDA #​%01111111 ;​mask-disable all interrupts on 
 + ; VIA1
 +FEE6 8D 1E 91    STA D1IER ;go do it
 +FEE9 8A          TXA ;​restore mask for active interrupts
 +FEEA 29 40        AND #​%01000000 ;​IFR bit6 =TIMER1 time-out (RS232
 + ; clk)
 +FEEC F0 14        BEQ WARM2 ;T1 done, go to RS232/RX chr
 +FEEE A9 CE        LDA #​%11001110 ;​set/​reset bit5 to xmit char
 +FEF0 05 B5        ORA NXTBIT ;​RS232 transmit - next bit to send;
 +FEF2 8D 1C 91    STA D1PCR ;CB2 manual L/H; CB1 neg trans for
 + ; IRQ; CA2 manual H; CA1 neg trans
 + ; for IRQ; CB2=TX, CA2=cass motor
 + ; control
 +FEF5 AD 14 91    LDA D1TM1L ;get VIA1/T1 count low byte
 +FEF8 68          PLA ;​restore IER bitmap...
 +FEF9 8D 1E 91    STA D1IER ; ...and save it
 +FEFC 20 A3 EF    JSR SSEND ;send RS232 char
 +FEFF             ​WARMEOI
 +FEFF 4C 56 FF    JMP EOI ;end of interrupt
 +FF02             ​WARM2 ;​RS232 receive NMI routine
 +FF02 8A          TXA ;​restore IFR mask from above
 +FF03 29 20        AND #​%00100000 ;​VIA1/​T2 time-out (done receiving
 + ; character from RS232 channel)?
 +FF05 F0 25        BEQ WARM3 ;yes, so move byte to buffer
 + ;​collect bits...
 +FF07 AD 10 91    LDA D1ORB ;get user port bitmap
 +FF0A 29 01        AND #​%00000001 ;​bit0=RS232/​RX
 +FF0C 85 A7        STA INBIT ;save received bit
 +FF0E AD 18 91    LDA D1TM2L ;get VIA1/T2L count
 +FF11 E9 16        SBC #$16 ; subtract 22d
 +FF13 6D 99 02    ADC BAUDOF ; add low word of bit transmit time
 +FF16 8D 18 91    STA D1TM2L ; save it
 +FF19 AD 19 91    LDA D1TM2L+1 ;​get VIA1/T2H count
 +FF1C 6D 9A 02    ADC BAUDOF+1 ; add high word of bit xmit time
 +FF1F 8D 19 91    STA D1TM2L+1 ; save it
 +FF22 68          PLA ;​restore old IFR bitmap...
 +FF23 8D 1E 91    STA D1IER ; ...and save it
 +FF26 20 36 F0    JSR SERRX ;​signal RS232 receive routine
 +FF29 4C 56 FF    JMP EOI ;end of interrupt
 +FF2C             ​WARM3 ;​received new char, so buffer it
 +FF2C 8A          TXA
 +FF2D 29 10        AND #​%00010000 ;​CB1 interrupt (RX data bit
 + ; transition)
 +FF2F F0 25        BEQ EOI ;no bit, exit
 + One interesting fact about the VIC is that originally it was supposed ​
 +to include a 6551 ACIA (RS-232) communications chip as standard equipment. ​
 + However, when MOS could not supply an adequate number of working ​
 +chips to support the anticipated VIC production volume, the VIC engineers ​
 +decided to emulate the 6551 in software. The VIC Programmer'​s Reference ​
 +Guide makes mention of the 6551 registers, but the VIC clearly does not 
 +contain a 6551. See Cameron Kaiser'​s Commodore Knowledge Base for more 
 +details. The URL is http://​​~spectre/​ckb/​
 + The 6551 pseudo-Control Register contains the stop bits, word length,
 +and baud rate parameters. The pseudo-Command Register contains the parity, ​
 +duplex, and handshaking parameters. The pseudo-Status Register contains a 
 +result code bitmap. After setting the baud rate divisor, the routine updates ​
 +the number of bits to transmit and exits.
 +FF31 AD 93 02    LDA M51CTR ;​pseudo 6551 control register
 +FF34 29 0F        AND #​%00001111 ;​pass the baud rate parameter only
 +FF36 D0 00        BNE $+2 ;I/O delay
 +FF38 0A          ASL A ;shift left
 +FF39 AA          TAX ;save shifted baud rate bitmask and
 + ; use as an index into a data table
 + ; with the receive timer values
 +FF3A BD 5A FF    LDA R232TB-2,​X ;​index into baud rate divisor table
 +FF3D 8D 18 91    STA D1TM2L ; and save the divisor into the VIA
 +FF40 BD 5B FF    LDA R232TB-1,​X ;​ timer count register
 +FF43 8D 19 91    STA D1TM2L+1
 +FF46 AD 10 91    LDA D1ORB ;read RS232 output register
 +FF49 68          PLA ;​restore IFR bitmap
 +FF4A 09 20        ORA #​%00100000 ;​T2 interrupt flag
 +FF4C 29 EF        AND #​%11101111 ;​pass T2 int. flag but not CB1
 +FF4E 8D 1E 91    STA D1IER ;save new interrupt bitmap
 +FF51 AE 98 02    LDX BITNUM ;get total number of bits to TX/RX
 +FF54 86 A8        STX BITCI ;save as receiver bit count
 +FF56             ;
 +FF56             ; EOI - End of Interrupt
 +FF56             ;
 +FF56             ​EOI
 +FF56 68          PLA ;​restore registers
 +FF57 A8          TAY
 +FF58 68          PLA
 +FF59 AA          TAX
 +FF5A 68          PLA
 +FF5B 40          RTI ;​return from interrupt
 +IRQ Processing
 + The IRQ routine is another very important routine for the VIC, as 
 +various housekeeping chores are performed during the interrupt. The 
 +processor BRK instruction also points to the IRQ vector, so there is some 
 +testing early in the routine to handle the BRK instruction. ​
 + The entry code is similar to the NMI entry code, and similarly, ​
 +allows function chaining. For example, one could write a small IRQ routine ​
 +to provide keyboard "​click"​ feedback, with the code activated during IRQ 
 +FF72             ;​==============================================
 +FF72             ; IRQ - IRQ transfer point
 +FF72             ;​==============================================
 +FF72             ​IRQ
 +FF72 48          PHA ; save .A
 +FF73 8A          TXA
 +FF74 48          PHA ; save .X
 +FF75 98          TYA
 +FF76 48          PHA ; save .Y
 +FF77 BA          TSX ; get stack pointer
 +FF78 BD 04 01    LDA FBUFFR+4,​X ;​look in stack for PSW BRK flag
 +FF7B 29 10        AND #​%00010000 ;​bit4 of PSW; breakpoint or IRQ?
 +FF7D F0 03        BEQ BRKSKIP ;​IRQ,​ branch
 +FF7F 6C 16 03    JMP (BRKVP) ;​jump to breakpoint processor,
 + ; which is the WARMST location.
 +FF82             ​BRKSKIP
 +FF82 6C 14 03    JMP (IRQVP) ;​jump to normal IRQ routine at 
 + ; $EABF
 + When the code is finished determining if the interrupt was 
 +triggered by a timer tick interrupt or through a BRK instruction,​ the IRQ 
 +code continues at $EABF. If it is a BRK instruction,​ code execution ​
 +continues at the WARMST location. The rest of the IRQ code calls the clock 
 +update routine, handles blinking the cursor, tape motor control, and 
 +scanning the keyboard -- all functions that could be considered "​user ​
 +interface"​ functions.
 +EABF             ;​=====================================================
 +EABF             ; IRQVEC - IRQ Vector
 +EABF             ;​
 +EABF             ​IRQVEC
 +EABF 20 EA FF    JSR UDTIM ;​update system clock FFEA=>​F734
 +EAC2 A5 CC        LDA BLNSW ;​cursor enable (0=enable)
 +EAC4 D0 29        BNE IRQVEC2 ;​non-zero,​ so skip blink code
 +EAC6 C6 CD        DEC BLNCT ;​decrement blink count
 +EAC8 D0 25        BNE IRQVEC2 ;​not reached 0, so move on to
 + ; cassette timing stuff
 +EACA A9 14        LDA #$14 ;reset blink timer to 20d (ms)
 +EACC 85 CD        STA BLNCT ;save new count
 +EACE A4 D3        LDY CSRIDX ;get cursor column
 +EAD0 46 CF        LSR BLNON ;get blink phase into C flag
 +EAD2 AE 87 02    LDX CSRCLR ;get color under cursor
 +EAD5 B1 D1        LDA (LINPTR),​Y ;​get character code of current char
 +EAD7 B0 11        BCS IRQVEC1 ;​blink already on, so continue
 +EAD9 E6 CF        INC BLNON ;​increment blink on flag
 +EADB 85 CE        STA GDBLN ;save char under cursor
 +EADD 20 B2 EA    JSR CCOLRAM ;​get pointer to color RAM
 +EAE0 B1 F3        LDA (COLRPT),​Y ;​get color code for cursor location
 +EAE2 8D 87 02    STA CSRCLR ;​save it
 +EAE5 AE 86 02    LDX CLCODE ;get color under cursor
 +EAE8 A5 CE        LDA GDBLN ;get char again
 +EAEA             ​IRQVEC1
 +EAEA 49 80        EOR #​%10000000 ;​update cursor with blink phase
 +EAEC 20 AA EA    JSR PRNSCR1 ;​set char and color
 +EAEF             ​IRQVEC2 ;​scan for tape switch pressed
 +EAEF AD 1F 91    LDA D1ORAH ;​read I/O bitmap
 +EAF2 29 40        AND #​%01000000 ;​cassette switch pressed?
 +EAF4 F0 0B        BEQ IRQVEC3 ;​no,​ turn motor off
 +EAF6 A0 00        LDY #$00 ;set motor "​on"​
 +EAF8 84 C0        STY CAS1 ;clear motor interlock flag
 +EAFA AD 1C 91    LDA D1PCR ;get PCR bitmap ​
 +EAFD 09 02        ORA #​%00000010 ;​set motor control bit to "​on"​
 +EAFF D0 09        BNE IRQVEC4 ;go to timer1 test
 +EB01             ​IRQVEC3 ;​set motor to "​off"​
 +EB01 A5 C0        LDA CAS1 ;get cassette interlock flag
 +EB03 D0 0D        BNE IRQVEC5 ;is flag 1, then exit-motor is off
 +EB05 AD 1C 91    LDA D1PCR ;get PCR bitmap
 +EB08 29 FD        AND #​%11111101 ;​set motor control bits to "​off"​
 +EB0A             ​IRQVEC4
 +EB0A 2C 1E 91    BIT D1IER ;IER bit7=IERs/c bit6=T1
 +EB0D 70 03        BVS IRQVEC5 ;is timer 1 still enabled? Yes, 
 + ; skip update
 +EB0F 8D 1C 91    STA D1PCR ;set motor status
 +EB12             ​IRQVEC5
 +EB12 20 1E EB    JSR ISCNKY ;​scan keyboard
 +EB15 2C 24 91    BIT D2TM1L ;​D2T1 latch LSB bits 7-6
 +EB18 68          PLA ;​restore registers and return from 
 +EB19 A8          TAY ; interrupt
 +EB1A 68          PLA
 +EB1B AA          TAX
 +EB1C 68          PLA
 +EB1D 40          RTI
 + The VIC's NMI and IRQ routines are important to the smooth operation ​
 +of the VIC, handling a few critical functions that make an impact on 
 +usability. The NMI handler deals with soft-reset processing and RS232 
 +communications timing, and the IRQ handler deals with the cursor, the 
 +keyboard, and cassette deck timing. The division of labor between the two 
 +routines is a sensible one. The routines are relatively compact, but allow 
 +expansion through chaining.
 + Next time, we'll examine more routines in the VIC's KERNAL, including
 +some of the routines called from NMI and IRQ. 
 +.                                   C=H #18
 +geoWrite Disassembly Notes
 +By Todd S. Elliott -
 +As part of an effort to learn GEOS programming and to improve an
 +application I was working on, I disassembled geoWrite 128, and along the
 +way, I made several modifications to make it work with modern day GEOS
 +systems. This article contains the fruits of my efforts and some of the
 +lessons I learned. The first part of the article describes largely the
 +territory that comes with GEOS programming. The second part discusses my
 +particular disassembly procedure, and the last part contains the actual
 +disassembly notes to geoWrite 128 v2.2.
 +When GEOS came out in mid-1980'​s,​ I tried it and it was cumbersome and slow.
 +I took an immediate dislike to it and sparingly used it, as if it were a
 +demo. But, CMD arrived with their powerful peripherals and I began to enjoy
 +using GEOS. Little did I realize it, GEOS insidously has made me a convert
 +and a believer in the fact that a c64/128 can do a GUI OS.
 +But, I was still a user all of this time, and I'll admit that I didn't think
 +about programming for it and was a little bit intimidated. But, the WWW craze
 +hit, and there were browsers popping up everywhere. All of a sudden, scarce
 +GEOS information was made immediately available, including programming
 +docs. I thought to myself, why not try GEOS programming?​ All I needed was an
 +idea. Naively, I thought I would try to create a graphical (mono) browser
 +that could read in HTML files off a local system, and thought that GEOS 128
 +was a natural fit. Thus, '​Constellation'​ was born, nutured and died an
 +untimely death, like so many of my other projects before and after then, and
 +they shall remain nameless. :(
 +First off, I had to use geoProgrammer and it was not an easy package to use
 +and master, and its manual was the heaviest in the business. Secondly, GEOS
 +uses an event driven nature of programming and that goes against my beliefs
 +in programming for the CBM 8-bit machines for years. Third, there were a lot
 +of system calls at my disposal and I really didn't know how to use them
 +effectively,​ or used wrong calls, and Berkeley Softwork'​s own programming
 +docs had inaccuracies and incompleteness. Fourth, GEOS 128, while a marvel
 +and a technological breakthrough,​ it was too limited, in my view, for a
 +serious application such as a mono graphical browser.
 +My main obstacle, as one would strangely call it, to GEOS programming is the
 +Berkeley Softwork'​s (BSW) own programming suite, geoProgrammer. One main
 +feature that set it apart from other assemblers was that it had a linker.
 +This was a new concept for me and took a while to warm up to the idea. (BTW,
 +Merlin is the only other assembler that I know of which uses a linker.) Next,
 +Berkeley Softworks added a host of features, directives, psuedo-ops and even
 +included a very powerful symbolic debugger in this programming suite. This
 +made programming for GEOS a complex task and I really had to keep tabs on my
 +projects and track my GEOS development. This complexity was only exacerbated
 +by several bugs that made GEOS programming an exactingly task.
 +But if used correctly, geoProgrammer does a very good job in creating GEOS
 +programs easily and quickly. Fortunately,​ Concept, Maurice Randall'​s integrated
 +development environment for Wheels, comes to my rescue by fixing most of the
 +problems plaguing geoProgrammer and boosting productivity in programming for 
 +GEOS.  Despite Concept'​s ease of use and its integrated development ​
 +environment,​ one still has to know geoProgrammer in order to use it 
 +effectively. This is because Concept has an integrated development environment
 +in which it acts as a desktop and allows the programmer to select geoWrite,
 +geoAssembler or geoLinker and work on source code files. Secondly, Concept
 +does not work in GEOS and is specific to the Wheels upgrade. For more
 +information,​ download Concept at:
 +There are other development packages available for GEOS programming,​ and I
 +won't go into them as I never have used them but will name them for the sake
 +of completeness. There was a program called geoCOPE, which was reviewed in
 +the Transactor and was billed as a simple alternative to geoProgrammer for
 +novices who desire to learn a little bit about GEOS programming. Then there'​s
 +the MegaAssembler,​ a german GEOS programming suite which is supposedly
 +powerful and complex as geoProgrammer. I do not know if it is in the public
 +domain or if it has an English version.
 +Once I got past the intricacies of geoProgrammer,​ I was stymied at first by
 +the event-driven nature underlying GEOS. I was conditioned to create a main
 +loop checking for user input and do various activities as necessary. The
 +opposite is used in GEOS. I had to GIVE up control of the application to the
 +GEOS main loop and let it do its job. That meant I had to set up the icons,
 +the menus, the screen interface, etc. and do an RTS. The GEOS main loop then
 +takes over and if an icon was clicked upon, for example, my application
 +finally takes control again and acts upon the icon click. As I got used to
 +it, I began to appreciate the event-driven nature of GEOS and how it has made
 +my job programming for GEOS that much easier.
 +The event-driven nature of GEOS is backed up by a phalanx of GEOS system
 +calls, of which most can be accessed by the programmer without the need to
 +ever use the KERNAL at all. Some system calls can be quite complex and can
 +basically take over the entire computer or can be very destructive if used
 +the wrong way. To make matters worse in trying to understand this entirely
 +new graphical OS, BSW's own programming docs were incomplete and had
 +inaccuracies. Despite that, both programming docs, the Official GEOS
 +Programmer'​s Reference Guide and the Hitchhiker'​s Guide to GEOS contains a
 +lot of useful information and when taken together, is truly the '​bible'​ on
 +GEOS programming. Anyone wishing to do serious GEOS programming needs both
 +Still, I managed to overcome these obstacles to GEOS programming and actually
 +created a user interface for Constellation. But I ran into a serious
 +limitation of GEOS 128; it had no memory management routines. I required that
 +Constellation be able to handle HTML files ranging from 254 bytes to 2Mb
 +monsters with equal aplomb. GEOS 128 has system calls for using REU memory,
 +but I was not very familiar with using them and there was no way of telling
 +how GEOS 128 used expansion memory. Secondly, I wasn't too sure on how to
 +display text and graphics with ease and whatever methods that I looked at, it
 +simply looked too cumbersome and difficult.
 +However, there was a '​browser'​ already in the GEOS environment;​ users simply
 +do not view it as that way. It's geoWrite, and it reads and mixes in text and
 +graphics with ease. Secondly, it is a full-blown BSW application that works
 +seamlessly with GEOS as opposed to Constellation'​s often mysterious crashes.
 +At that point, I decided to scuttle Constellation and open a dissection into
 +the inner workings of geoWrite 128 and use that knowledge to revive
 +Constellation. And as a bonus, I would learn a lot more about GEOS
 +programming far better than what BSW's own docs could provide. Only that it
 +exactly hasn't turned out that way so far...
 +As I progressed into disassembling geoWrite 128 v2.1, I came across several
 +sections of code and said to myself, 'I could change this or that'. There
 +were code that mostly dealt with file handling, and this stuff was obsolete
 +by the time high powered GEOS systems came into fruiton. For example, BSW put
 +in code that merely '​toggled'​ the data device, and at that time, it was
 +sensible as two drive systems were possible. But today'​s GEOS systems can
 +support up to four drives and this code is an annoyance and limits geoWrite
 +128's usability in modern day GEOS systems.
 +So, I disassembled geoWrite 128 with the full intention of porting such GEOS
 +knowledge to Constellation and wound up improving geoWrite 128 instead.
 +Here's what I improved so far in geoWrite 128 v2.2:
 +* Complete four drive support in its file requestors.
 +* Capable of booting up from any drive (A-D) when a datafile is clicked upon.
 +  Improved booting up sequence.
 +* Loads completely into expansion memory in Wheels equipped systems. In this
 +  case, geoWrite 128 no longer needs to access the disk device to retrieve
 +  its own modules, and simply fetches them from the faster RAM expansion. A
 +  very useful feature for users who only have 41's, 71's, 81's or FD's. This
 +  feature does not work in GEOS 128 because there is no reliable way of
 +  knowing how it manages RAM expansion.
 +* Semi-intelligent - Does not prompt the user to insert a new disk if
 +  non-removable media is used.
 +* Displays the DISK icon when a Native ramdisk is used. Previously, geoWrite
 +  128 would not display a DISK icon (used to change disks or partitions)
 +  when a ramdisk is accessed. But native ramdisks have their own
 +  subdirectories and the DISK icon is activated accordingly.
 +* Also displays the DISK icon if a ramlink 1581 partition is used.
 +* The DISK icon is also displayed in the disk device from which geoWrite 128
 +  was loaded from, because geoWrite 128 is now 100% ram resident.
 +* Autodetects whether it is running in GEOS 128 v2.0 or Wheels 128 systems.
 +The rest of this article mainly focuses on how geoWrite 128 v2.2 is
 +constructed,​ how to disassemble a GEOS program, and mostly tries to offer
 +inspiration for others to undertake GEOS programming endeavors. There is so
 +much that needs to be done and can be done in GEOS. Without further ado...
 +Disassembly For Dummies. :)
 +Before we begin with the disassembly notes, let's describe how I conducted
 +the disassembly of geoWrite 128 v2.1. In fact, a program called geoSourcer
 +(64/128 versions) was recently released into the public domain while I was
 +busy disassembling geoWrite 128 v2.1. Since I haven'​t disassembled seven
 +other VLIR modules, I plan to use geoSourcer to do the job and will post a
 +review, tutorial or critique of sorts either here in a future issue of
 +C=Hacking or on comp.sys.cbm.
 +First, I personally use the DarkStar suite of disk utilities (ZipCode,
 +etc.) and use its excellent disk editor. The reason is that I need to '​link'​
 +or '​de-link'​ the individual VLIR modules from the rest of the program for
 +later disassembly or reassembly. One must have knowledge of how VLIR files
 +are structured at this stage of disassembly.
 +There are two formats under GEOS; the sequential format (not to be confused
 +with SEQ files) and the VLIR format. The sequential format simply consists of
 +a file running in a contigious fashion and is loaded in at once. The VLIR
 +format is similar to CBM REL files in which there are 127 individual records,
 +and each record can basically be of any length. ​ VLIR files are broken into
 +records, or if in program files, '​modules'​. With respect to programs and not
 +datafiles, VLIR #0 will be the main module which is loaded and executed
 +first in GEOS. In turn, this module will load in other modules in its VLIR
 +file structure whenever necessary. This allows for much larger GEOS programs
 +to run and still have room to spare. For example, geoWrite 128 is 35K, and if
 +it were a sequential file, there would be no room left over for the actual
 +datafile to coexist. What about geoPublish at 99K? The VLIR file format is
 +the only way geoPublish can run in GEOS and on the CBM 8-bit platform. In
 +essence, the VLIR format allows GEOS to use the disk device as virtual
 +memory device by loading in modules as needed.
 +geoWrite 128 v2.1 consists of eight VLIR files. ​ VLIR #0 loads in at $0400,
 +and contains the main '​meat'​ of the program, with icons, menus, dialog box
 +info, etc.  VLIR #1 contains mostly screen setup routines, the geos kernal
 +ID check, checking the printer driver and other initialization routines.
 +VLIRs #2 through #4 are unknown at this point, while VLIR #5 contains the
 +main routines for datafile handling. VLIR #6 is unknown, while VLIRs #7 
 +and #8 contain the printing code.  VLIRs #1 through #7 all load in at $3244,
 +and VLIR #8 loads in at $0680. ​ I focused on VLIR #0 and #5.
 +To disassemble the VLIRs, I located the record on the disk and created a
 +separate directory entry pointing to the record as a PRG (non-GEOS) file. I
 +looked at the first two bytes of the file. They are actual pieces of code,
 +but outside of GEOS, they are mistaken for as load addresses. I wrote down
 +these two bytes somewhere so I could later retrieve them. At this point,
 +I have to know its true load address. Let's say that it's $3244. So I
 +replaced these first two bytes with the true load address plus two, in a
 +low/high byte order, as in $46 $32.
 +I'm done with the disk editor at this point and used a disassembler program.
 +The disassembler read in the load address -- my user supplied value of $3246 --
 +and went from there, disassembling that particular VLIR record. I used a
 +symbolic disassembler modified for LADS 128 by James Mudgett and it works in
 +128 mode. Theoretically,​ I could supply a symbol file containing GEOS equates
 +and it will produce some GEOS compliant source code. I haven'​t done this yet.
 +Next, I used the Make-ASCII program by the authors of EBUD to convert the PRG
 +disassembly to a SEQ petascii file.
 +I used EBUD to modify the source code file, add GEOS equates, fix some minor
 +addressing problems in the ML code, add comments, etc. In short, I
 +followed the program flow and logic and adding comments here and there,
 +fixing up stuff, etc. This is the true guts and grits of disassembling and
 +takes up quite a bit of time, reference to documentation,​ etc. At times,
 +this process was enlightening and at times, it was quite dull and boring.
 +(Ever try to translate codes into ASCII text that GEOS uses?)
 +My best friend during this disassembly process was dialog boxes, menus,
 +icons and prompts. Why? They shed light on the calling routines. Some
 +routines were so obscure at an initial glance that I couldn'​t figure them out.
 +But when it called a dialog box, presto! I understood now what the routine
 +was trying to do in the first place. This disassembly process goes on and on.
 +Maurice Randall also was very patient in answering my email correspondence
 +as I progressed through the disassembly. In case if I haven'​t said it, I'm
 +saying it now... THANKS! :)
 +Anyway, the reason why I decided to use non-GEOS tools is because I wanted to
 +produce an exact copy of the original code from my disassembled code. I
 +couldn'​t do that with geoProgrammer as it doesn'​t always produce an exact
 +copy. Maurice has fixed many problems in geoProgrammer with the release of
 +Concept and can be downloaded from his website. I've been using Concept to
 +assemble the patcher program that patches a user's copy of geoWrite 128 v2.1
 +and have full confidence in that programming package.
 +Upon assembling the source code, I ran a compare of the resulting code
 +against the original PRG file that I extracted earlier in a disk editor. I
 +used my Action Replay v6 ML monitor to do the comparisons. If the files did
 +not match, then I went back into the disassembly and figured out why, etc.,
 +and fixed them. Once I did have a match, then I knew my disassembly was
 +perfect and that I could add any changes to it I wanted, etc., and is what
 +I've done with geoWrite 128 v2.2.
 +Having a perfect copy of the VLIR #5 to work with, I went back into my disk
 +editor to patch it back into the VLIR application on a 5 1/4 disk. I did this
 +by modifying the VLIR table as to include that PRG file as a VLIR record and
 +remove the directory entry pointing to it as a PRG file. Remember the
 +modified load address of $3246? I removed it and added the original two
 +bytes of code, so the GEOS application would run fine within the GEOS 
 +As for serialization and the GEOS Kernal ID check, I don't want to get into
 +this area, but suffice it to say that I used the Action Replay v6 ML
 +monitor to remove these. You may have to remove these if the code you want to
 +disassemble is somehow blocked by serialization or serial number checks. This
 +is also an another reason why I decided to use non-GEOS tools. GEOS can't
 +remove the serialization by itself, obviously, and Action Replay v6 does not
 +run in 128 mode and may crash the computer when running in 64 mode running
 +GEOS. The GEOS Kernal ID check is a form of copy protection and is used often
 +by major BSW applications.
 +This is largely the process I've been using to patch geoWrite 128 v2.1 to a
 +v2.2 by modifying VLIR #0 and #5. I haven'​t touched other modules yet and
 +there are 7 more modules to go. :(  Hopefully I haven'​t missed anything and
 +that it has been helpful and instructive. I plan to finish disassembly of
 +geoWrite 128 v2.1 and will certainly post more disassembly notes in the
 +future, either in C=Hacking or wherever appropriate.
 +Disassembly Nota Bene
 +The rest of the article will mainly focus on routines that I've either
 +disassembled or revamped in the geoWrite 128 v2.2 upgrade. The labels as
 +used are largely from BSW itself, in the Official GEOS Programmer'​s Reference
 +Guide and the Hitchhiker'​s Guide to GEOS. The format for the article is as
 +* The routine name is displayed, along with its actual address and VLIR
 +  module number and then the category it is under. For illustration of the
 +  admittedly unorthodox addressing scheme, let's give an example: $3a41x5.
 +  The $3a41 is the actual address, and the x5 is the VLIR module number of
 +  where it is located. (VLIR modules #0 through 8)
 +* A brief description of the routine follows in the function field.
 +* Parameters, if any, are described.
 +* Variables or routines that the routine will use are listed.
 +* Listing of any variables or flags that are set or accessed when the routine
 +  is done.
 +* Posting of any psuedo-registers or registers that are used by the routine
 +  and not preserved.
 +* The description of the routine, with a sequence of events or branches.
 +* Finishes up with any proposed modifications that would be suitable for
 +  future work.
 +The disassembly comments pertaining to VLIR #5 of geoWrite 128 are only
 +applicable to the v2.2 version as patched, and not its original v2.1 version.
 +Traces of the original v2.1 version still remain, notably the lbxxxx labels
 +throughout the disassembly. The numbers following the '​lb'​ refers to the
 +actual locations in the v2.1 version. Any absolute addresses that do not
 +contain a VLIR number is presumed to be addresses for VLIR #0. Also, this is
 +not 100% complete, there are still several gaps of which I have not been able
 +to figure out and they are duly noted, or have been guesstimated as such.
 +The patcher program is now available and is being marketed by yours truly,
 +a multinational corporation. :) Ok, so I'm no corporation nor paper tiger,
 +but email me for more details at about acquiring the
 +patcher program.
 +There is a geos programming emailing list maintained at
 +One need not join, but can read past archives covering various topics of geos
 +programming. The URL for the geoProgramming:​The Millennium (GTM) emailing
 +list is: http://​​gtm/​
 +That all said, there are plenty of GEOS programs, especially those BSW
 +applications,​ that are ripe for disassembly for further exploration and
 +knowledge of GEOS programming. As a bonus, these programs really do need
 +modifications as to make them work all but seamlessly on modern day GEOS
 +systems with hulking CMD power.
 +I hope that the following disassembly notes will be instructive to people
 +wishing to get into GEOS programming or to upgrade other existing BSW
 +applications. At any rate, enjoy.
 +setProgDevice & - $0daax0 - Disk Routines
 +setDataDevice - $0daex0 -
 +Function:​ Sets the disk device from which the application was loaded
 +from. Sets the disk device for which the datafile activities take place.
 +Parameters:​ None.
 +Uses:​ progDrive
 + dataDrive
 + toggleZP
 + SetDevice
 +Returns:​ None.
 +Destroys:​ .A
 +Description:​ Depending on the entry point, it checks either progDrive or
 +dataDrive and issues a SetDevice call to change the active disk device.
 +Proposal:​ This needs to be overhauled, as the disk device driven model
 +is obsolete in modern day GEOS systems. This needs to be changed to a
 +directory driven model, where directories rule and not the disk device. Users
 +can change directories,​ subdirectories,​ partitions, etc. and the program
 +needs to keep track of all of this activity and the current routines fall
 +VLIROps - $0dddx0 - Disk Routines
 +Function:​ Loads in geoWrite 128's own individual VLIR modules.
 +Parameters:​ .A containing the VLIR number for the geoWrite 128 module to
 +                load.
 +Uses:​ PVLIROps
 +                CVLIRNum - Current geoWrite 128 VLIR module in memory
 + setProgDevice
 + RVLIRB2F - restores a geoWrite VLIR module from BackRAM
 +                $3172 - Address location of which starting tracks and sectors
 +                for the nine individual VLIR modules can be found and is used
 +                as an index.
 + $2798 - ReadFile
 + SVLIRF2B - saves a geoWrite VLIR module to BackRAM
 +                GotoFirstMenu
 + lb29dc - Error message
 + lb233a - Canned DB to display error message
 +Returns:​ None.
 +Destroys:​ r1,​ r2, r7, r10, .A, .X and .Y.
 +Description:​ First,​ it loads r7 with $3244 for the loading address for the
 +VLIR modules. Next, it copies r7 into r10 for the error handler. Next, it
 +checks the value passed in .A against CVLIRNum and if they match, then the
 +VLIR module in question is already in memory and there is no need to load it
 +in from the disk device, and it simply RTS's without any further action.
 +If the numbers do not match, then CVLIRNum will take on the number passed in
 +.A and opens the disk device housing the application through setProgDevice.
 +Next, it calls RVLIRB2F, and sees if a copy of the VLIR module is already
 +located in BackRAM. If so, the routine simply restores the VLIR module by
 +fetching it from BackRAM, and doesn'​t need to access the disk device at all.
 +If the VLIR module is not located in BackRAM, it then checks the index
 +located at $3172 and retrieves the starting track and sector of the VLIR
 +module and issues a ReadFile to load in the VLIR module. The maximum VLIR
 +module size is only 4,000 bytes. Then it calls SVLIRF2B, to determine if the
 +newly loaded in VLIR module should also be saved to BackRAM. If there is an
 +error, a DB is generated and the VLIR module in question tries to get
 +accessed again.
 +Note: There is an alternative entry point, PVLIROps, which is for the VLIR
 +#8, the printing code. The reason is that this module loads in at $0680 as
 +opposed to $3244 for VLIR modules #1 through 7. The entry point is located at
 +address $0de5, or just eight bytes beyond VLIROps, and requires passing r7
 +the $0680 address.
 +Proposal:​ This routine can be modified as to allow retrieval of VLIR
 +modules from expansion ram in Wheels or MP3 operating systems. Already
 +implemented in the patcher program for Wheels users.
 +StashGW128 - $2bdbx0 - Setup
 +Function:​ Stashes all eight VLIR modules of geoWrite 128 v2.2 into
 +expansion memory, and enables the ram-based VLIR code.
 +Parameters:​ a7L (Wheels flag) and $d4 (LoadOptFlag)
 +Uses:​ RAMOps - Core ram-based VLIR code
 +                setProgDevice
 + obtainRec - loads in an individual VLIR module from disk
 +                $3172 - Address location of which starting tracks and sectors
 +                for the nine individual VLIR modules can be found and is used
 +                as an index.
 + $2798 - ReadFile
 + standard - loads r7 with $3244
 +                deviate - loads r7 with $0680
 + checkDisk
 + i_MoveData
 + VLIRAMOps - Replacement routine for the following routine:
 + VLIROps - the routine being replaced by the previous routine.
 +                CVLIRNum - Current geoWrite 128 VLIR module in memory
 + toggleZP
 + DoRAMOp
 +                EnterDeskTop
 + lb29dc - Error message
 + lb233a - Canned DB to display error message
 +Returns:​ None.
 +Destroys:​ r0,​ r1, r2, r3L, r7, r15L, .A, .X and .Y.
 +Description:​ First,​ it checks a7L and determines if it is running under a
 +GEOS 128 or Wheels 128 system. If it is GEOS 128, then the routine simply
 +jumps to $3244 of geoWrite'​s VLIR #1 and goes from there. Secondly, it checks
 +LoadOptFlag to determine if the datafile was passed through the printer icon
 +for printing. If that is the case, then the routine would jump to $3244 of
 +geoWrite'​s VLIR #1. In either event, no RAM activities take place.
 +If the routine gets past the checks, then it stashes VLIR #1 into expansion ​
 +memory because it is already sitting there in FrontRAM memory. Next, it calls
 +setProgDevice and proceeds to stash VLIRs #2 through 7 into expansion memory.
 +Lastly, it stashes the memory region from $0680 to $0b80 into expansion
 +memory. Then it loads in VLIR #8, the printing code, into that region
 +beginning at $0680. Next, it performs a SWAP of memory located at $0680 and
 +that in expansion memory, putting the printing code into expansion memory and
 +restoring the original code located at $0680.
 +Next, it fetches VLIR #5 from expansion memory and modifies the checkDisk
 +code as to allow the DISK icon to be placed in the file requestor, even if it
 +is displaying the disk device from which geoWrite 128 originally loaded from.
 +Next, once the code is modified, VLIR #5 is stashed into expansion memory.
 +Lastly, VLIR #1 is fetched from expansion memory. Next, it replaces the code
 +as found in VLIROps with the ram-based VLIR code located at VLIRAMOps.
 +Finally, it jumps to $3244 in geoWrite'​s VLIR #1.
 +Proposal:​ None. Code may change to support ram expansion capabilities
 +of MP3 128 systems.
 +VLIRAMOps - $0dddx0 - RAM Routines
 +Function:​ Loads in geoWrite 128's own individual VLIR modules via
 +expansion RAM.
 +Parameters:​ .A containing the VLIR number for the geoWrite 128 module to
 +                load.
 +Uses:​ PVLIROps
 +                RAMOps
 +                CVLIRNum - Current geoWrite 128 VLIR module in memory
 + toggleZP
 + DoRAMOp
 + lb29dc - Error message
 + lb233a - Canned DB to display error message
 + EnterDeskTop
 +Returns:​ None.
 +Destroys:​ r0,​ r1, r2, r3L, r7, .A, .X and .Y.
 +Description:​ First,​ it loads r7 with $3244 for the loading address for the
 +VLIR modules. Next, it checks the value passed in .A against CVLIRNum and if
 +they match, then the VLIR module in question is already in memory and there
 +is no need to load it in from RAM expansion, and it simply RTS's without any
 +further action.
 +If the numbers do not match, then CVLIRNum will take on the number passed in
 +.A and checks to see if it is VLIR #8, the printing module. If that is the
 +case, then only 1,280 bytes gets fetched from expansion memory. Otherwise,
 +it's 4,000 bytes. Next, it calculates the RAM expansion address offset of
 +which the correct VLIR module can be found.
 +Lastly, when all registers are prepped, DoRAMOp does the job of fetching the
 +VLIR module into the correct memory location. If there is an error, a DB is
 +generated and the application aborts to deskTop.
 +Note: There is an alternative entry point, PVLIROps, which is for the VLIR
 +#8, the printing code. The reason is that this module loads in at $0680 as
 +opposed to $3244 for VLIR modules #1 through 7. The entry point is located at
 +address $0de5, or just eight bytes beyond VLIRAMOps, and requires passing r7
 +the $0680 address.
 +Proposal:​ This routine replaces the disk-based VLIR routines as found
 +in geoWrite 128 v2.1. This routine may need to be changed to support the MP3
 +128 platform.
 +checkDrives - $2b0dx0 - Disk Routines
 +Function:​ Checks available drives on a user's system.
 +Parameters:​ r0L via deskTop with LoadOptFlag
 + r2 via deskTop pointing to diskname of the disk containing
 +                the datafile.
 + r3 via deskTop pointing to datafile'​s filename.
 +Uses:​ DrACurDkNm
 + DrBCurDkNm
 + DrCCurDkNm
 + DrDCurDkNm
 + setDataDevice
 + setProgDevice
 + $27b0 - FindFile
 + curDrive
 + numDrives
 + toggleZP
 + prepDlgBox
 + enterDeskTop
 +Returns:​ progDrive - The drive geoWrite was loaded from.
 + dataDrive - The disk device that houses the datafile.
 +Destroys:​ r6,​ .A, .X and .Y
 +Description:​ First,​ it checks curDrive and stores the value there into
 +progDrive. Next, it stores the same value into dataDrive. It checks then the
 +LoadOptFlag to determine if a datafile was clicked on. If so, it checks the
 +name of disk A and compares it against the name of the disk that has the
 +datafile. If there is a match, then the datafile is on drive A or C. Next, a
 +value of eight is stored into dataDrive.
 +If they are different, then the routine checks for additional drives. If
 +there are no additional drives, then a dialog box is fetched, warning the
 +user that a datafile and geoWrite must be on the same disk on single drive
 +systems. After that dialog box is over, geoWrite quits to deskTop. If there
 +is an additional drive, then geoWrite will put a value of nine into
 +Proposal:​ This code can be modified to search drives A-D in sequence.
 +This way, a user can click on a datafile anywhere in his/her system and
 +geoWrite 128 can boot up correctly. Secondly, it should use FindFile to
 +nearly pinpoint the proper disk containing the datafile because disknames
 +can be identical. Already implemented in the patcher program.
 +checkVer - $2b75x0 - Misc. Routines
 +Function:​ Checks the current GEOS version.
 +Parameters:​ None.
 +Uses:​ Version
 + c128Flag
 + DoDlgBox
 + enterDesktop
 +Returns:​ None.
 +Destroys:​ r0 and .A
 +Description:​ First checks to see if it is running in GEOS v2.0 or higher
 +systems. Next, it checks to see if it is running on the 128 version of GEOS.
 +If both conditions are not met, then a dialog box is printed to that effect
 +and geoWrite 128 aborts to deskTop. Otherwise, the routine RTS's w/o further
 +Proposal:​ The routine can be modified to check for Wheels or MP3
 +systems and designate a variable for later routines to rely upon. Already
 +implemented in the patcher program, where it designates a7L as the Wheels
 +toggleZP - $251bx0 - Housekeeping
 +Function:​ Toggles the state of all zp variables, thereby preserving two
 +zp spaces, one for the actual application usage and the other for stock
 +GEOS/Kernal usage.
 +Parameters:​ None.
 +Uses:​ None.
 +Returns:​ None.
 +Destroys:​ None. All registers are preserved via the stack.
 +Description:​ It'​s a toggle routine. When called, it performs a swap of
 +zero page space located at $80 to $fb towards a buffer located in $2e22, and
 +the buffer contents are similarly swapped back. This way, geoWrite 128 can
 +use that zp region freely, and swap it out whenever calling GEOS Kernal
 +routines that rely on this area, and when these routines are done, the toggle
 +routine is called again to swap back in those values for use in the
 +application. This is the most heavily used routine in geoWrite 128.
 +Proposal:​ While this routine is nice as it allows the application more
 +zp space, this is unnecessary routine and should be eliminated. It slows down
 +the application somewhat, as this must be called twice whenever a GEOS Kernal
 +call is used.
 +prepDlgBox - $2314x0 - Dialog Boxes
 +Function:​ Preps the pointers for the actual DoDlgBox function call.
 +Parameters:​ .A has low byte and .Y has high byte pointing to the dialog
 +box text.
 +Uses:           ​DoDlgBox
 +                $2538 - Does something to the VDC
 +Returns:​ Carry flag is set.
 +Destroys:​ r5,​ r14, .A, .X and .Y
 +Description:​ The routine uses a `canned'​ dialog box with preset icons,
 +placement and size. The only thing that is controllable is the text. The
 +pointer passed on in .A and .Y registers points to the text. Commonly used to
 +create error dialog boxes with an OK icon.
 +Proposal:​ None.
 +layoutScreen - $32cex1 - Appearance
 +Function:​ Draws up the main menu bar and the overall screen layout for
 +geoWrite 128.
 +Parameters:​ None.
 +Uses:           ​DoMenu
 +                i_GraphicsString
 +Returns:​ Carry flag is set.
 +Destroys:​ r0,​ .A
 +Description:​ The routine draws the screen layout using various
 +GraphicsString parameters. Additionally,​ it builds the main menu bar at the
 +top, with its table located at $0bbcx0.
 +Proposal:​ This may need changing, especially if new menu items are
 +added or old ones deleted and maybe the screen layout should be changed or
 +left as is.
 +InitGW128 - $3c70x1 - Startup
 +Function:​ Initializes geoWrite 128 with variables, flags and vectors.
 +Parameters:​ None.
 +Uses:           ​toggleZP
 +                closeRecordFile
 +                dispBufferOn
 +                RecoverVector
 +                checkSerNbr
 +Returns:​ None.
 +Destroys:​ .A
 +Description:​ The routine sets the following locations to zero: $41e4-e5,
 +$0200, $021a, $2dfa, $db, $de, $e1, and $f7. It also closes geoWrite 128's
 +own VLIR record, and as well as activates the software mouse as sprite zero.
 +It sets the dispBufferOn flag as to allow only writes to foreground and sets
 +the RecoverVector to point at $2199. It sets $f1 to have a value of $c0 and
 +sets $023a & $023c to have a value of $ff.
 +Proposal:​ This may need changing, when new variables or flags need be
 +CheckPtrDrv - $33e7x1 - Startup
 +Function:​ Checks the status of the printer driver and loads it in, if
 +Parameters:​ None.
 +Uses:           ​loadPtrDrv
 +                getDimensions
 +                setProgDevice
 +                maxPtrHgt ($2dfb)
 +Returns:​ None.
 +Destroys:​ a9L,​ .A, .X
 +Description:​ The routine sets the maximum printer height ($02f0) in card
 +rows to maxPtrHgt. Then it loads in the printer driver, and if there is an
 +error, no harm is done as the maximum printer height is already established.
 +But, if a printer driver is successfully loaded in, then a call to
 +getDimensions will place the maximum printer height in card rows at
 +Proposal:​ Why not just call getDimensions when the printer driver is
 +always loaded into memory in GEOS 128 configuration?​
 +InitGW128 - $325cx5 - Dialog Boxes
 +Function:​ Prints the copyright message and calls the infamous Create,
 +Open or Quit DB.
 +Parameters:​ None.
 +Uses:           $1baf - Turns off text cursor.
 +                DrawStripes
 +                i_GraphicsString
 +                printCopyrightMsg
 +                $2538 - issues a dialog box
 +                createOpenQuitDB - DB table
 +Returns:​ None.
 +Destroys:​ all registers.
 +Description:​ It turns off the text cursor, draws the striped pattern you
 +see in the upper right corner of the screen, clears the screen with a default
 +pattern, prints out the copyright message, and issues the infamous
 +create/​open/​quit DB. The routine will then branch to routines that handle
 +creation of datafiles, opening of datafiles or quitting the application.
 +Proposal: ​      None I can think of right now. Maybe abolish this dialog box
 +in favor of allowing the user to boot geoWrite 128 to a blank screen and have
 +him/her to select from a menu an appropriate action to undertake.
 +printCopyrightMsg - $3375x5 - Text
 +Function:​ Prints the copyright message.
 +Parameters:​ None,​ but can only be used once.
 +Uses:           ​crflag
 +                $1f05 - calls system font
 +                currentMode
 +                i_GraphicsString
 +                i_PutString
 +Returns:​ None.
 +Destroys:​ r1,​ .A and possibly others.
 +Description:​ Sets the crflag as to reflect that the copyright message has
 +been printed. That's why this routine can only be used once. It calls the
 +system font, and then uses inline routines to draw and print the copyright
 +Proposal:​ No change. Unless someone patching this copy wants to add
 +their own message.
 +createDocRoutine - $33ddx5 - Disk Routines
 +Function:​ Creates a new geoWrite v2.1 datafile.
 +Parameters:​ none.
 +Uses:           ​queryFilename
 + nameBuffer
 +                $27b0 - FindFile
 +                lb3f67 - File exists text prompt
 +                $2314 - issues a DB
 +                createNewFile
 +                $de - currently unknown
 +                $d3 - currently unknown
 +                currentMode
 +                TitlePage
 +                HeaderHeight
 +                FooterHeight
 +                FirstPage
 +                PageHeight
 +                CPageHeight
 +                PageWidth
 +                gPFlag
 +                $2393 - Something to do with fonts
 +                SetScrollSprite
 +                printDataName
 +                lb404a - creating file error text string
 +                $233a - issues a DB
 +                InitGW128
 +Returns:​ None.
 +Destroys:​ r6,​ .A, .X and possibly others.
 +Description:​ First turns on the icons related to drive activity and then
 +calls the DB that prompts the user to enter the filename. If the user cancels
 +or no filename was inputted, the routine then goes back to the main DB in
 +InitGW128. r6 is then loaded with the inputted filename and the FindFile
 +function is called. If a file exists on disk, the routine informs the user
 +with a DB and prompts the user for a new filename. It then calls
 +createNewFile and sets up variables relating to page length and width,
 +margins, headers and footers, etc. Next, it sets up the fonts and the
 +scrolling sprite. Last, it will print the datafile'​s filename in the upper
 +right corner of the screen.
 +Proposal:​ One thing could be changed; instead of requiring the user to
 +make a new filename upon notice that a file already exists, the user could
 +override it and use the same filename, but get rid of the old one. This could
 +be a `TEMP' file concept. If a new version of geoWrite is written with a new
 +version of a datafile, then this routine would need be revamped accordingly.
 +Setup4DB - $3449x5 - Disk Routines
 +Function:​ Preps the dialog boxes with the appropriate drive icons and
 +the DISK icon.
 +Parameters:​ None.
 +Uses:           ​nameBuffer
 +                setDiskIcon
 + checkDisk
 +                setDataDevice
 +                readDiskName
 +                iconLTable
 +                iconHTable
 +                driveType
 +                curDrive
 +                onLTable
 +                onHTable
 +                offLTable
 +                offHTable
 +Returns:​ None.
 +Destroys:​ r1,​ r7H, r0, r5, .A, .X & .Y
 +Description:​ First,​ it delimits the nameBuffer. It then checks for a
 +ramdisk and application disk in order to turn on/off the DISK icon. It then
 +reads in the disk name so that it will appear in the requestor DB. Next, it
 +checks all available drives and activates the drive icons accordingly. Last,
 +it loads in r5 with nameBuffer.
 +Proposal:​ No change.
 +fileRequestor - $34b2x5 - Dialog Boxes
 +Function:​ Calls up the file requestor box w/ 4 drive support.
 +Parameters:​ None.
 +Uses:           ​setup4DB
 +                permName - (Write Image)
 + restoreDBTable
 +                dBTable - DB table w/ DBGETFILES
 +                $2538 - Issues a DB
 +                InitGW128
 +                curType
 +                lb3f45 - pointer to text (Insert New Disk)
 +                $2314 - Issues a DB
 +                $27ad - OpenDisk
 +                openServiceRoutine
 +                dataDrive
 +                setDataDevice
 +                toggleZP
 +Returns:​ None.
 +Destroys:​ r7L,​ r10, r0L, .A, .Y
 +Description:​ When called, the routine first preps the drives via the
 +setup4DB routine. Since the dBTable would be shared by other requestors, some
 +modifying code was used. The routine modifies the dBTable w/ restoreDBTable
 +data. It sets up DBGETFILES to search for only APPL_DATA files containing the 
 +permanent name string of `Write Image'​. Finally, the DB is issued, complete ​
 +w/ 4 drive support.
 +If the user cancelled, then it aborts back to InitGW128. If the user opened a
 +file, then openServiceRoutine is run. If the user clicked upon the disk 
 +selection, it checks to see if non-removable media is present. If it is
 +removable media, then an another DB is issued to prompt the user to enter the
 +new disk. Otherwise, it displays the file requestor again w/ new contents of
 +the newly selected partition. If a drive icon is clicked, it will issue a
 +setDataDevice command and OpenDisk the new drive and repeats the file
 +requestor w/ new contents of the newly selected drive.
 +Proposal:​ No change.
 +openServiceRoutine - $364dx5 - Disk Routines
 +Function:​ Opens a geoWrite v2.1 datafile.
 +Parameters:​ None.
 +Uses:           ​setDataDevice
 +                nameBuffer
 + InitGW128
 +                $27b0 - FindFile
 +                TSDFHdr
 +                dirEntryBuf
 +                lb35fc - pointer to DB data (File Write Protected)
 +                $2538 - Issues a DB
 +                PageWidth
 +                CheckDFVer
 +                lb3f86 - text pointer (version higher than v2.1)
 +                $2314 - Issues a DB
 +                convertDataFile
 +                lb356e - extracts global variables stored in a datafile'​s file
 +                header
 +                $260c - r0 points to filename
 +                OpenRecordFile
 +                toggleZP
 +                AdjPageWidths
 +                windowBottom
 +                $d3 - currently unknown
 +                $de - currently unknown
 +                SetScrollSprite
 +                printDataName
 +                lb403d - text pointer (opening file error)
 +                $233a - Issues a DB
 +Returns:​ None.
 +Destroys:​ r5,​ r6, r0L, .A, .X & .Y
 +Description:​ First,​ it calls setDataDevice,​ and checks nameBuffer to see
 +if a filename was selected. If no name is selected, it quits to InitGW128,
 +otherwise, it will find it by FindFile. It then stores the T/S for the
 +datafile'​s fileheader, and checks to see if it is write-protected. Next, it
 +stores in a max width of 639 into PageWidth. Next, it checks the datafile'​s
 +version and if necessary, converts it to a v2.1 format. Next, it gets global
 +variables stored in the datafile'​s fileheader and stashes it into zero page.
 +Last, it finally opens the datafile with OpenRecordFile. It will also read in
 +geoPublish data and adjust page widths. It stores a $00 in $d3 and $de and as
 +well as a $c7 (scanline 199) in windowBottom,​ Last, it will set the scrolling
 +sprite and prints the datafile'​s filename on the upper right corner of the
 +Proposal:​ Changes may be necessary if new formats are to be supported
 +in addition to regular geoWrite v2.1 datafiles.
 +checkDisk - $371bx5 - Disk Routines
 +Function:​ Checks to see if the data drive is a ramdisk or holds the
 +application and sets the DISK icon accordingly.
 +Parameters:​ None.
 +Uses:           a7L
 +                dataDrive
 + progDrive
 + driveType
 + cableType
 +Returns:​ Carry clear indicating the existence of a ramdisk or that the
 +application is on that same disk as designated as a data device. Carry set
 +means that the DISK icon can be placed in a requestor DB.
 +Destroys: ​      .A, .Y
 +Description:​ First,​ it checks to see if the application is on the same
 +disk as being designated as a data device housing the datafile. If that is
 +the case, then the DISK icon cannot be placed. Next, it checks the
 +appropriate driveType entry to check the existence of a ramdisk. If a 41/71
 +ramdisk is being used, then the carry flag is cleared as to prevent the DISK
 +icon. If a 81 ramdisk is being used, then it checks the Wheels flag at a7L to
 +determine if it's a 81 RL ramdisk as shown in cableType. If that is the case,
 +then the carry flag is set as to allow the DISK icon, otherwise it is cleared
 +as to prevent the DISK icon because it's just a regular 81 ramdisk. The carry
 +flag is set if a native ramdisk is being used, as to allow the DISK icon to
 +Proposal:​ If geoWrite 128 can fully load itself into RAM, then the DISK
 +icon can be placed in the requestor DB even when the disk device houses both
 +the application and the datafiles. As it stands, the DISK icon must be
 +removed because the user might select a different disk, partition or
 +subdirectory and the application then can't find its own VLIR modules.
 +Instant crash.
 +*UPDATE* I have added ram routines and geoWrite 128 is now 100% RAM resident,
 +and therefore, the DISK icon can be displayed in this case. But there is one
 +major caveat, among others, in using this approach. The fonts are keyed to
 +the disk device from which geoWrite 128 was loaded from. If the user changes
 +the disks or partitions or subdirectories from that same disk device, then
 +geoWrite 128 would not be able to find the font data and may lead to
 +unpredictable results. The same goes for Text Scraps and other features that
 +are tied to the disk device from which geoWrite 128 originally loaded from.
 +In the future, I would have to work on font routines and other routines as
 +necessary as to eliminate this dependence on the original disk device.
 +readDiskName - $3739x5 - Disk Routines
 +Function:​ Reads in a disk name housing the current datafile.
 +Parameters:​ None.
 +Uses:​ DrACurDkNm
 + DrBCurDkNm
 + DrCCurDkNm
 + DrDCurDkNm
 + diskName
 + dataDrive
 +Returns:​ None.
 +Destroys:​ r0,​ .A, .Y
 +Description:​ Depending on the value of dataDrive, r0 is loaded with the
 +appropriate current diskname and its contents are transferred to diskName.
 +This way, the file requestor can display the disk name along with other info.
 +Proposal:​ No change.
 +recoverFile - $3781x5 - Disk Routines
 +Function:​ It recovers a file.
 +Parameters:​ None.
 +Uses:​ GotoFirstMenu
 + $dd - some kind of unidentified flag
 + i_MoveData
 + CNameBuffer
 + NameBuffer
 + openServiceRoutine
 + lb4005 - text pointer (Cannot recover)
 + $2314 - Issues a DB
 +Returns:​ None.
 +Destroys:​ .A,​ .Y
 +Description:​ Calls GotoFirstMenu to close its menu selection, then tries
 +to recover the file by copying the current filename into the filename buffer
 +and calling openServiceRoutine. It does check the flag at $dd before
 +determining whether a file could be recovered. If it can't be recovered, a DB
 +is issued to that effect.
 +Proposal:​ No change.
 +RenamFile - $379bx5 - Disk Routines
 +Function:​ It renames a file.
 +Parameters:​ None.
 +Uses:​ GotoFirstMenu
 + queryFilename
 + CNameBuffer
 + NameBuffer
 + $27b0 - FindFile
 + toggleZP
 + RenameFile
 + printDataName
 + SetScrollSprite
 + lb40a7 - text pointer (File Exists error)
 + $2314 - Issues a DB
 + $260c - r0 points to NameBuffer
 +Returns:​ None.
 +Destroys:​ r6,​ r0, .A, .X, .Y
 +Description:​ First calls GotoFirstMenu before calling queryFilename. The
 +new name is then put into CNameBuffer and calls the RenameFile routine. Of
 +course, if a file exists, then the DB pops up, reporting the error. Last, it
 +prints the new filename onto the upper right corner of the screen and sets
 +the scrolling sprite.
 +Proposal:​ No change.
 +queryFilename - $37dbx5 - Dialog Boxes
 +Function:​ Prompts the user for a filename for a v2.1 datafile.
 +Parameters:​ .A must pass either a zero or a $12 to turn off/on the drive
 +icons and the system DISK icon from the DB.
 +Uses:​ renTable
 + setup4DB
 + NameBuffer
 + lb3f45 - text pointer (Insert New Disk)
 + $2314 - Issues a DB
 + replaceDBTable
 + dBTable
 + qDBTable - DB table data
 + $2538 - Issues a DB
 + dataDrive
 + setDataDevice
 + $27ad - OpenDisk
 + curType
 +Returns: .A containing the value of r0L.
 +Destroys:​ r0L,​ .A, .X & .Y
 +Description:​ First,​ it shuts off/on the drive icons and the DISK icon
 +before calling setup4DB to prep the DB with appropriate icons. Next, it
 +modifies the DB table to replace the DBGETFILES with DBGETSTRING,​ as this DB
 +table is also shared by the file requestor. If a drive icon was clicked upon,
 +the drive gets accessed and the DB is reissued with the updated icon data. If
 +a DISK icon was selected, it will prompt the user to insert a new disk, or
 +skips that process if it's on non-removable media. Last, upon exiting, it
 +loads .A with r0L so that the calling routine will know what the user
 +Proposal:​ No change.
 +createNewFile - $3839x5 - Disk Routines
 +Function:​ Creates a new geoWrite v2.1 datafile.
 +Parameters:​ None.
 +Uses:​ CPageHeight
 + NameBuffer
 + lb38c4 - (word) Page Height stored in a datafile'​s fileheader
 + dFileHeader
 + toggleZP
 + SaveFile
 + $27b0 - FindFile
 + CheckDFVer
 + dirEntryBuf
 + TSDFHdr
 + $260c - r0 points to filename
 + OpenRecordFile
 + $27a7 - AppendRecord
 + $27aa - UpdateRecord
 + PointRecord
 +Returns:​ None.
 +Destroys:​ r10L,​ r9, r6, .A, .X & .Y
 +Description:​ First,​ it stores the current page height into the global
 +variables as hidden in the datafile'​s fileheader. Next, it points the first
 +two bytes of the datafile'​s header (dFileHeader) to the filename and calls
 +SaveFile. Next, it calls FindFile to load in the newly created datafile'​s
 +fileheader into memory and preserves its t/s pointers. It also checks its
 +version identifier. Next, it opens the datafile and creates 127 blank VLIR
 +records and updates it and then finally points to VLIR #0 for further
 +Proposal:​ Changes may be necessary to support a newer format or support
 +other file formats.
 +convertDataFile - $393ex5 - Data Handling
 +Function:​ Converts a datafile to a v2.1 format.
 +Parameters:​ .Y contains $31 or higher to correspond with ascii values of
 +1 thru 9. .A contains $32 or lower to correspond to ascii values of 2 through
 +0, i.e., .A is the `2' in `v2.1' and the .Y is the `1' in the `v2.1'​.
 +Uses:​ lb3fbf - points to the version string of `v2.x' where x = 1
 + or higher
 + VerFlag
 + convertFileDBTable
 + $2538 - Issues a DB
 + toggleZP
 + sysDBData
 + fileHeader
 + lb38bd - global variables controlling document
 + lb356e - extracts these global variables from the file header
 + TSDFHdr
 + $27b6 - PutBlock
 + $260c - r0 points to filename
 + OpenRecordFile
 + PointRecord
 + lb3978 - modifies a VLIR record of the datafile
 + NextRecord
 + $279e - Closes the VLIR datafile
 + lb3fc1 - text pointer (Converting File Error)
 + $233a - Issues a DB
 +Returns: .A to indicate error status ($00 = no error)
 +Destroys:​ r4,​ r1, .A, .X & .Y
 +Description:​ First,​ it modifies the file header of the datafile as to make
 +it to show v2.1. Depending on the values passed, it will set VerFlag as to
 +control further file conversion to a v2.1 format. Next, it issues a DB
 +informing the user that it's converting the datafile to a v2.1 and asks
 +permission. Then it modifies the fileheader of the datafile as to incorporate
 +new global variables. With the fileheader modified, a PutBlock call is
 +Finally, it will read in all used VLIR records of the datafile and convert
 +them to a v2.1 format. Next, it will read in the header and footer VLIR
 +records and modify them as well. Last, it will then close the datafile.
 +Proposal:​ Changes may be necessary to support a newer format or support
 +other file formats.
 +lb3978 - $39f0x5 - Data Handling
 +Function:​ Converts an individual VLIR record of a datafile to a v2.1
 +Parameters:​ The VLIR record must have been already opened.
 +Uses:​ fileData
 + VerFlag
 + toggleZP
 + ReadRecord
 + lb39da - fixes v1.x ruler escapes to conform to the v2.1
 + standard
 + lb3fbf - the V2.X identifier string where X is accessed
 + lb3a09 - fixes the width of a v2.0 page to a v2.1 page
 + WriteRecord
 +Returns:​ None.
 +Destroys:​ r2,​ r7, .A, .X & .Y
 +Description:​ It will first read in a VLIR record of a datafile, and
 +depending on its original version (set by VerFlag), it will modify ruler
 +escapes as to make the current record conform to v2.1 specifications. When all
 +of these modifications are done, the record is written back w/ WriteRecord.
 +Proposal:​ Changes may be necessary to support a newer format or support
 +other file formats.
 +lb39da - $3a52x5 - Data Handling
 +Function:​ Converts an individual VLIR record of a datafile from a v1.x
 +to a v2.1 format.
 +Parameters:​ The VLIR record must have been already opened and read w/
 +Uses:​ fileData
 +Returns:​ None.
 +Destroys:​ .A & .Y
 +Description:​ It merely moves the first 20 bytes of the first ruler escape
 +on a VLIR record back by seven bytes and zeroes out some parts of the ruler
 +Proposal:​ Changes may be necessary to support a newer format or support
 +other file formats.
 +lb3a09 - $3a81x5 - Data Handling
 +Function:​ Converts an individual VLIR record of a datafile from a v2.0
 +to a v2.1 format.
 +Parameters:​ The VLIR record must have been already opened and read w/
 +Uses: $d0 - word pointer to last byte of individual VLIR record of
 + a datafile
 + $25a1 - points r15 to start of fileData
 + toggleZP
 + $26d7 - compares r15 against $d0
 + $25e1 - increments r15
 + $25db - increments r15 three times
 + $25f7 - skips ruler escapes pointed to by r15
 +Returns:​ None.
 +Destroys:​ r7,​ r15, r1, .A & .Y
 +Description:​ The v2.0 page has a width of 1.2 to 7.2 inches, and the v2.1
 +page has a width of 0.2 to 8.2 inches, and this routine searches for every
 +ruler escape and converts them to a v2.1 format.
 +Proposal:​ Changes may be necessary to support a newer format or support
 +other file formats.
 +printDataName - $3af1x5 - Appearance
 +Function:​ Prints the datafile'​s filename in the upper right corner of
 +the screen.
 +Parameters:​ nameBuffer must have a filename.
 +Uses:​ nameBuffer
 + CNameBuffer
 + i_MoveData
 + $1fba - use system font
 + currentMode
 + DrawStripes
 + PutChar
 + PutString
 + rightMargin
 + GetCharWidth
 +Returns:​ None.
 +Destroys:​ r0,​ r1, r11, r13L, .A & .Y, r2L is specially preserved.
 +Description:​ It moves the contents at nameBuffer to CNameBuffer and then
 +calculates the width of the filename, ensuring that it does not exceed its
 +maximum width. Next, it will print a trailing space, then the filename, and
 +then a leading space, in that little striped box in the upper right corner
 +of the screen.
 +Proposal:​ No change.
 +runDA - $3bb1x5 - Desk Accessories
 +Function:​ Allows a DA to be run.
 +Parameters:​ .A passes the index number of the DA in an internal table.
 +Uses:​ GotoFirstMenu
 + $d3 - I think this variable holds the current VLIR # of a
 + datafile
 + lb406f - text pointer (Cannot run DA)
 + $2314 - Issues a DB
 + $07c5 - Inverts a rectangle and preserves zp space
 + $0d61 - Closes the current datafile
 + saveTScrap
 + $21ba - saves the portion of the screen
 + $01 - I/O port
 + $d017 - sprite horizontal expand register
 + $4c95 - Currently unknown word variable
 + setProgDevice
 + toggleZP
 + GetFile
 + $21bf - restores the portion of the screen
 + i_MoveData
 + lb3ecd - text pointer (Not enough disk space)
 + lb4058 - text pointer (Running DA error)
 + $233a - Issues a DB
 + $22e3 - Sets up the text cursor
 + loadTScrap
 + $0d6c - reloads the VLIR datafile and points to the last
 + accessed record
 + $851e - default screen color
 + FillRam
 + $2555 - Not sure yet what this routine does
 + $1512 - Displays the characters onscreen
 + $2575 - The opposite of $2555
 + SetScrollSprite
 + $1bbb - turns on the text prompt and draws a couple of
 + rectangles
 +Returns:​ None.
 +Destroys:​ r6,​ r15L, r0, r2L, r1, r10L, .A, .X & .Y, and since a DA is
 +run, pretty much everything else has been hosed, except that geoWrite 128
 +tries to preserve as much as possible.
 +Description:​ After calling GotoFirstMenu,​ it checks to see if it's in
 +header/​footer mode and if so, the DA can't be run. Next, it preserves much of
 +zero page and inverts a rectangle twice onscreen. It then closes the current
 +VLIR datafile, saves any Text Scraps, sprite data/​expansion registers and the
 +first 24 scanlines of the screen. Finally, it calls and runs the DA. When the
 +DA is done, geoWrite then will restore sprite data/​expansion registers, the
 +first 24 scan lines of the screen, color and video data and much of the zero
 +page area. Then it sets up the text cursor, loads in any Text Scrap, and
 +reloads the VLIR datafile and points to its current VLIR record. Next, it
 +will begin displaying the contents of the datafile, enable the text cursor
 +and draws a couple of rectangles, and returns control to MainLoop.
 +Proposal:​ The I/O calls seem redundant for I/O is always banked in and
 +color data is being restored as well, which is puzzling as geoWrite 128 does
 +not support color. Other than that, no changes.
 +saveTScrap - $3c97x5 - Disk Routines
 +Function:​ Saves a text scrap.
 +Parameters:​ None.
 +Uses:​ TSFlag
 + TSiZe
 + lb3dff - load address of text scrap
 + lb3e01 - size of text scrap
 + setProgDevice
 + lb40c1 - pointer to filename (Text  Scrap)
 + toggleZP
 + DeleteFile
 + lb3db8 - pointer to filename for the SaveFile call and also
 + doubles as its fileheader
 + SaveFile
 +Returns:​ TSFlag holds a zero value to indicate that a Text Scrap is
 +saved, otherwise $ff if something'​s wrong.
 +Destroys:​ r0,​ r9, r10L, .A & .X
 +Description:​ It checks TSFlag and TSiZe to determine existence of a text
 +scrap, and if so, then attempts to save it. It modifies the fileheader for
 +the text scrap as to include an appropriate load address ($41e4), the ending
 +address, etc. Next, it saves them to the same disk/​partition as where the
 +geoWrite 128 application resides. It will delete a prior text scrap if one
 +Proposal:​ The area, $41e4-$4310,​ seems too small of a buffer for a text
 +scrap. Maybe this buffer should be enlarged to accommodate large text scraps.
 +Also, this may need to be changed to accommodate future versions or simply ​
 +accommodate a text album file.
 +quitGeoWrite - $3cb1x5 - Disk Routines
 +Function:​ Quits the geoWrite 128 application and returns to deskTop.
 +Parameters:​ None.
 +Uses:​ SaveTScrap
 + setProgDevice
 + toggleZP
 + EnterDeskTop
 +Returns:​ None.
 +Destroys:​ None;​ the deskTop now takes control.
 +Description:​ First,​ it saves the current Text Scrap. Then it returns to
 +the disk/​partition that it was originally booted from, restored zero page
 +space and then quit, resuming control to the deskTop.
 +Proposal:​ No change.
 +SetScrollSprite - $3cbdx5 - User Interface
 +Function:​ Enables the scrolling sprite that one uses to scroll the
 +document onscreen, in the little box in the middle top of the screen.
 +Parameters:​ None.
 +Uses: $01 - i/o port
 + $d02d - Sprite 6 color register
 + i_FillRam
 + i_MoveData
 + lb3c73 - sprite data
 + $d01d - expand sprite 6 horizontally
 +Returns:​ None.
 +Destroys:​ .A and $01 is preserved.
 +Description:​ It enables I/O, colors sprite #6 black and expands it
 +horizontally,​ and defines its bitmap.
 +Proposal:​ No change. But is the I/O activity redundant because I/O is
 +mapped in already in a GEOS 128 configuration?​
 +AdjPageWidths - $3cf6x5 - Data Handling
 +Function:​ Reads in imprinted geoPublish data and adjusts page widths.
 +Parameters:​ None.
 +Uses:​ gPFlag
 + PointRecord
 + toggleZP
 + ReadRecord
 + $2c1d - Page Width High Byte
 + $2be0 - Page Width Low Byte
 + pageWidth
 +Returns:​ None.
 +Destroys:​ r2,​ r7, .A, .X & .Y
 +Description:​ It will check VLIR #63 of a geoWrite v2.1 datafile and
 +determine whether geoPublish has stashed its internal data there. If so, then
 +gPFlag is set, and geoWrite 128 will read in page widths and compensate page
 +Proposal:​ Maybe give the user a DB giving an option to remove
 +geoPublish data or leaving it alone. If a user wants to remove geoPublish
 +data, then the document would have to be reformatted.
 +printInfoBox - $3d4dx5 - Dialog Boxes
 +Function:​ Prints the copyright message and info box.
 +Parameters:​ None.
 +Uses:​ GotoFirstMenu
 + lb3cdf - DB table pointer
 + infoBoxText - text pointer to copyright message, etc.
 + $2538 - Issues a DB
 +Returns:​ value in r0L.
 +Destroys:​ .A & .X
 +Description:​ It issues a DB stating the copyright message, its authors,
 +Proposal:​ Maybe add in the patcher info here.
 +loadTScrap - $3d5fx5 - Disk Routines
 +Function:​ Loads in a text scrap.
 +Parameters:​ None.
 +Uses:​ setProgDevice
 + lb40c1 - points to filename
 + $27b0 - FindFile
 + CheckDFVer
 + lb40cd - text pointer (text scrap beyond v2.1)
 + $2314 - Issues a DB
 + $2625 - r4 points to $8000
 + $27b3 - GetBlock
 + lb40e7 - text pointer (reading Text Scrap error)
 + $233a - Issues a DB
 + TSiZe
 + TSFlag
 +Returns:​ TSiZe and TSFlag will have values reflecting the presence of
 +a Text Scrap.
 +Destroys:​ r1,​ r5, r6, .A, .Y & .X
 +Description:​ It will load in a text scrap from the same disk/​partition
 +that geoWrite 128 was launched from. Next, it will check its version, and
 +then gets info from the first datablock of the text scrap and copies down its
 +size, sets up the flags as appropriate,​ where TSiZe and TSFlag will have
 +non-zero values if a text scrap exists. It doesn'​t actually load in a text
 +scrap yet, save for its first datablock.
 +Proposal:​ Changes may be necessary to support a new format or to
 +support text albums.
 +CheckDFVer - $3eb9x5 - Disk Routines
 +Function:​ Checks a datafile'​s version against a v2.1 string identifier.
 +Parameters:​ r5 having the direntry of the datafile.
 +Uses:​ toggleZP
 + GetFHdrInfo
 + fileHeader
 +Returns: N and Z flags are set on whether the datafile equals v2.1
 +Destroys:​ r5,​ r9, .A, .Y & .X
 +Description:​ It simply gets the datafile'​s fileheader and compares its
 +version string against the standard, v2.1, and sets flags as appropriate.
 +Proposal:​ No change.
 +.                                   C=H #18
 +Masters Class: xFLI Fixing
 + by Russell Reed <​>, ​
 +    Robin Harbron <​>,​
 +    S. Judd <​>​
 +Last issue we covered the issues involved in NTSC/PAL fixing -- 63 cycles/line
 +versus 65 cycles/​line,​ total cycles per frame, etc. -- and fixed up some
 +raster routines in a demo.  One common raster routine that is usually quite
 +easy to fix is FLI, and in this article we'll cover fixing FLI and IFLI
 +This article is more "​interactive"​ than the previous article. ​ The .zip file
 +included in this issue contains an FLI picture (debris.panic),​ an IFLI
 +picture (underwater-pal and underwater-ntsc),​ and some FLI display code.
 +The FLI display code was downloaded from funet; "​Underwater World",​ by
 +Katon/Lepsi De/Arise, is an entry from the SymMek99 graphics competition;​
 +I'm not quite sure which competition the Debris picture is from.
 +Try loading and running one of the pictures, and observe what happens. ​ That's
 +typical of a lot, though not all, FLI-type routines.
 +FLI is discussed in detail in C=Hacking issue #4, among other places. ​ Briefly,
 +FLI is a software graphics mode that gives greater flexibiliby in color
 +selection than standard multicolor mode.  FLI works by changing VIC registers
 +to load new color information on every scan line.  It uses exactly ​
 +23 cycles/line in PAL mode, and 25 cycles/line on an NTSC machine. ​ IFLI is
 +similar, but "​interlaces"​ two FLI pictures, to gain even more apparent
 +resolution and colors, at the cost of a flickering display.
 +Full screen FLIs are pretty standard. ​ They have color data from $4000 to
 +about $4FE8, graphics data from $6000 to $7F40, and more color data at either
 +$3C00-$3FE8 or $8000-$83E8. ​ IFLIs are not nearly so standard; you'​ll ​
 +generally have to examine a routine to see where the graphics data and
 +the $D800 color nybbles are stored. ​ IFLIs can add a further wrinkle over
 +FLI by updating the $D800 color RAM on every frame, in the vertical borders,
 +which may require more cycles than an NTSC machine has to spare.
 +An FLI display routine is really quite straightforward,​ and looks something
 + initial delay
 +loop: LDA xxxx
 + STA $D011
 + LDA xxxx
 + STA $D018
 + INX
 + CPX #xx
 + BNE :loop
 +in PAL (this article will focus on fixing PAL code, but going the other way
 +is of course straightforward). ​ The STA $D011 forces a badline; the STA $D018
 +selects a new line of color data.  Sometimes this loop will be unrolled, and
 +sometimes the initial delay can be very strange. ​ It is this code that needs
 +to be either modified or replaced.
 +Displaying a picture
 +The easiest way to fix a picture is to simply replace the display code.
 +The FLI picutre "​Debris"​ has the color data at $3C00, which is more common.
 +Now, since FLI format is pretty standard, if you have some NTSC FLI code,
 +it'll work on this pic.  Escape the picture by either pressing the
 +stop/​restore combination,​ or a reset button if you have one.  In the zip
 +you'll also find "​FLIview NTSC"​. ​ Load and run this now, and you'll see the
 +picture as it's supposed to look (almost -- this code isn't quite perfect;
 +we'll get it better when we fix the code with the picture).
 +The next step is to save the picture in a way that can be distributed and
 +make you famous as an 3li+3 c0de f1x3r. ​ While the picture is still in memory,
 +fire up a machine language monitor (most any will do, the FLI data is an an
 +area rarely used by monitors). ​ The easiest thing to do is to save memory
 +from $0801 to $7F40, and compress it using ABCrunch, pucrunch, etc. -- although
 +there are a lot of unused bytes between the display code at $0810 and the
 +start of FLI data at $3B00, these get compressed down to just a few bytes.
 +Another option is to use a linker. ​ Save memory from $3B00 to $7F40 and name
 +it something that reminds you it's an FLI pic -- you can load this file with
 +most any of the FLI viewers out there, and you can load it into most of the
 +FLI editors. ​ Copy both "​FLIview"​ and the saved-off FLI data to a work disk.
 +Then boot up Sledgehammer,​ available at most FTP sites. ​ Sledgehammer is a
 +compacting linker (it actually uses run-length encoding, a.k.a. char-packing).
 +It takes a number of program and data files and "​links"​ them together into a
 +single file that you can load and run.  Once Sledgehammer is loaded, run it,
 +put in your work disk, and press F1 to start. ​ You'll then see a list of the
 +files on the disk.  Use the cursor keys to move and the return key to select
 +"​FLIview"​ and the FLI data.  Then press '​C'​. ​ Sledgehammer then asks if you
 +need to change load addresses. ​ You can ignore this for now, so push '​N'​.
 +Next we see a screen asking for three pieces of information. ​ For the name,
 +"​DEBRIS.SRF"​ (for self-running FLI) works. ​ If you listed "​FLIview"​ you saw
 +that it did an SYS 2064 to start. ​ 2064 is equal to $0810 in hex, so put 0810
 +for start, and then 37 for the $01 value. ​ From there Slegehammer takes over,
 +loading the files and compacting. ​ Press space when it prompts to save.  Once
 +it's done, it resets. ​ You can now load and run "​DEBRIS.SRF",​ and you should
 +see the picture. ​ If not, read back through the article and check your steps
 +Okay, we've successfully made this picture work on NTSC computers, but
 +this technique won't work in most cases (i.e. stuff besides standalone
 +pictures). ​ Next we're going to go back to the picture and see how to change
 +the PAL code so it works on our computers.
 +Fixing the PAL code
 +The first thing we need to do before we fix the code is find out where it is
 +in memory and how to start it.  Also, most demos, self-running pictures, etc.
 +are compressed, and we can't work with them until they'​re uncompressed. ​ How
 +you approach this depends on whether or not you have a freezer cartridge.
 +If you don't own a freezer, then fire up a monitor that sits fairly high
 +in memory, e.g. $C000; we're going to look at the decompression code to see
 +how to start the FLI code up.  If you list the program, you'll see it does 
 +a SYS 2061, which is hex $080D. ​ Start disassembling at $080D. ​ You'll
 +notice it sets border and background to black (stores color code 0 in $D020
 +and $D021), then calls $E544 which clears the screen. ​ Next we have a loop to
 +transfer a message to the top screen line - $0400. ​ Then another loop to
 +transfer the decompression code from $0858 to $04FF. ​ Finally, a third loop to
 +transfer the compressed program to high memory and a jump to $050B. ​ This is
 +pretty typical of most decompression code; some may skip the text/​message line
 +and/or transferring the program to high memory. ​ I usually replace the clear
 +screen call with three '​NOP'​ instructions. ​ You may also want to replace the
 +store to $0286, which changes the working character color to black.
 +Now into the decompression code we go.  Start disassembling at $0858.
 +There will be a few garbage instructions at the start. ​ Scroll down a page or
 +so until you see the sequence:
 +  LDA #$37
 +  STA $01
 +  CLI
 +  JMP $1000
 +This is where the decompression routine transfers control to the program. ​ The
 +instructions may vary a little on other files, but will usually be pretty
 +similar. ​ From this, we can tell that the program will start at $1000. ​ We can
 +also regain control here after decompression. ​ Replace JMP $1000 with JMP
 +$A7AE. ​ Sometimes an RTS will work, and if you have a cartridge based monitor
 +you can use a BRK.  Now return to BASIC and type "PRINT 3"​. ​ Some monitors
 +disturb some floating point work areas; that line will clear this up.  It may
 +print something other than 3, but if you try it again you'll notice it's back
 +to normal. ​ Now run the program. ​ After the usual depack effects, you'll notice
 +you have a READY prompt. ​ Reload the monitor, as the decompression probably
 +overwrote it.  Note: sometimes you won't be able to get back to BASIC, but
 +since you know the start address, you can load the program and once it's
 +running, use a reset button, then load your monitor.
 +If you have a freezer, then you can simply load and run the picture, and
 +freeze it after it finishes decompressing.
 +Enter your monitor again and disassemble at $1000 -- let's study this code to
 +see what's involved in displaying an FLI picture. ​ The first thing we want to
 +look at is down at $1035. ​ The interrupt vector at $0314 is changed so that
 +interrupts execute the code at $1043; sometimes the $FFFE vector is used
 +instead. ​ Now look at $1043; the part we're interested in is at $1060. ​ Here
 +is the main FLI display loop, as described above, that changes the vertical
 +scroll register ($D011) and the screen display pointer ($D018), the two
 +registers involved in FLI.  If you add the cycles the instructions use, the
 +loop takes 23 cycles. ​ We need to extend it to 25.  The easiest way here is
 +to add a NOP at $1071. ​ Change that part to be:
 +  INX
 +  CPX #...
 +  NOP
 +  BNE $1062
 +  RTS
 +Now jump to $1000 to re-run the program. ​ You'll notice you can see the
 +picture, but it doesn'​t quite look right; the problem is in the initial delay,
 +before the main display loop:
 +  LDY #xx
 +  DEY
 +  BNE *-2
 +Go back in and change the value at $105C until the picture looks right. ​ You
 +can hit stop/​restore to exit the picture, then restart your monitor. ​ Changing
 +a value like that is a dirty way to fix the picture, but is also usually the
 +easiest. ​ (By the way, the value at $105C which works is $0B).
 +Now save your code off -- it starts at $1000 and ends at $12E0; there'​s
 +tables at $1100 and $1200 as you'll notice in the FLI loop -- and crunch it
 +down.  That's all there is to it.
 +Now it's your turn, to fix "​Underwater"​. ​ This is an IFLI picture, but the
 +process is not much different than outlined above. ​ You need to locate
 +the interrupt routine, and fix up the main display loop and the initial
 +delay; unlike some IFLIs this one is pretty easy to fix, and you can
 +always peek at the fixed code if you really need a hint.
 +Things that can go wrong
 +Load up "​debris"​ again. ​ This time, instead of adding a NOP to the end of
 +the loop, add it to the _beginning_ of the loop (move the loop down one byte
 +and add a NOP), and change the BNE to branch to this NOP.  Now fix up the
 +initial delay, and -- it doesn'​t work!  And it doesn'​t really matter
 +what value you use in the LDY #xx.  This highlights the fact that FLI
 +involves somewhat delicate timing; the problem here is that DEY BNE *-2
 +takes five cycles, which is too "​coarse"​. ​ Some NOPs are needed instead,
 +which means a more involved code rewrite -- and all because the two cycles
 +were added to the display loop at the beginning instead of the end.
 +IFLIs bring on problems of their own.  Sometimes the display loop is
 +unrolled, and looks like
 + LDA #xx
 + STA $D011
 + LDA #xx
 + STA $D018
 + few cycles delay
 + LDA #xx
 + STA $D011
 + ...
 +Sometimes the loop is only unrolled over eight iterations (to cover a full
 +character row); sometimes, however, it is unrolled over the entire picture,
 +taking up thousands of bytes. ​ Sometimes the background is changed within
 +the loop (LDA #xx STA $D021) too.  Fortunately,​ these loops are usually
 +written using a code generator, as part of the initialization routine. ​ If
 +you can locate the generator, you only have to add two cycles in one place.
 +There are also different ways of generating the interrupt. ​ While VIC is
 +often used, the CIAs are also used -- if you set the CIA timer to $42C6
 +(NTSC -- PAL is $4CC7) it will count exactly one frame. ​ An IRQ typically
 +takes a variable number of cycles, since the CPU has to finish executing
 +the current instruction before the interrupt sequence takes place. ​ By
 +reading the low byte of the timer in the interrupt routine, the code can
 +deduce exactly how many cycles took place since the interrupt, and hence
 +exactly where it is on the raster line.  Fixing this type of routine ​
 +involves setting the timers correctly, as well as fixing up the display
 +loop and initial delay.
 +Nevertheless,​ with a little bit of thoughtfulness and persistance just
 +about any routine can be fixed up, and as usual becomes easier with practice.
 +As an application of the techniques outlined in this article, Robin and Steve
 +fixed up the graphics entries from the 1999 Mekka Symposium; you can find
 +them in the Fridge. ​ And if we can do it, then it must be pretty easy!
 +So good luck in your fixing endeavors, and see you next time!
 +begin 644
 +.                                   C=H #18
 +Obj3d trilogy
 + The rest of this issue is devoted to obj3d. ​ Obj3d is a set of
 +routines for creating and manipulating 3D worlds, and builds upon the
 +lib3d routines discussed in issue 16 (the lib3d routines have also been
 +upgraded, including a multicolor version).
 + The first article introduces the use of obj3d with a simple example
 +program which places two objects into the world (and lets you move one of
 +them around). ​ The second article is the programming reference guide, with
 +a memory map and a list of routines/​routine descriptions. ​ The third article
 +covers "​stroids",​ a more involved example program which demonstrates several
 +game-type algorithms and ideas.
 + Mark Seelye,,​ has written a really nice "​object ​
 +editor",​ i.e. a program for editing objects for use in obj3d programs.
 +Unfortunately it was not quite finished at the time of this issue, but
 +hopefully it will be featured in the next issue. ​ For more information,​
 +contact Mark!
 + All files are in a .zip files, included at the end of this
 + obj3d.o -- Obj3d library
 + lib3dv2.o -- v2.0 of lib3d library
 + lib3dv2mc.o -- Multicolor version
 + table13 -- $C000 table
 + rotmat.o -- $5E00 table
 + ptabgen -- program used to create above tables
 + bigtets.b.o -- Simple example program
 + bigtets.b.s -- Source code, in El Cheapo Assembler format
 +    (see the Fridge for Cheapass docs)
 + stroids.o -- Stroids
 + stroids.n.s -- Source code for stroids, cheapass format
 + loader -- Program to load in relevant files
 + stroids -- Single-file stroids executable (load and run)
 + obj3d.ref -- obj3d programmer'​s reference guide
 + lib3dv2.ref -- lib3d v2.0 programmer'​s reference guide
 + Binaries, source code, and documentation are also available in the
 +Fridge, at
 + http://​​fridge/​obj3d/​
 +Obj3d -- The 3D object library
 +--------------------------------->​ S. Judd 4/99
 + Obj3d is a library of routines for organizing, manipulating,​
 +and rendering 3d objects in both hires and multicolor mode, using 
 +version 2.0 of the lib3d library routines (described a little later).
 +Let's just dive right in and look at some example code, to get a feel for
 +the routines, and just how easy it is to get a 3D object up on the screen.
 +This example is taken from the example program "​bigtets",​ included in
 +the archive in this issue.
 +* Test code
 +* Nice pair of tets
 +         ORG $1000
 +OBS      = $0800          ;Object records
 +GETIN    = $FFE4
 +TestCode ​
 +         LDA $D011
 +         ORA #$20         ;​Bitmap mode
 +         STA $D011
 +         LDA #$08         ;​Bitmap -> $6000
 +         STA $D018
 +         LDA $DD00
 +         AND #$F8
 +         ORA #$02         ;Bank 1
 +         STA $DD00
 +         LDA #$80
 +         STA $028A        ;All keys repeat
 +         JSR VERSION ​     ;If multicolor...
 +         BPL :hires
 +         LDA $D016
 +         ORA #$10
 +         STA $D016
 +:​hires ​  
 +         JSR ClrColMap
 +         LDA #<​OBS ​       ;Object records
 +         LDY #>OBS
 +         JSR Init3D  ​ ;Initialize libraries
 +         LDA #<​TETDAT ​    ;​Object data
 +         LDY #>TETDAT
 +         LDX #01          ;ID
 +         JSR AddObj  ​ ;Add an object
 +         STX VOB          ;View object
 +         LDA #<​STARDAT
 +         LDY #>​STARDAT
 +         LDX #02
 +         JSR AddObj  ​ ;Add another object
 +         STX ROB          ;Remember this object for later
 +         STA POINT        ;Object pointer
 +         STY POINT+1
 +         LDY #5           ;Set object center
 +:l1      LDA OCEN,Y
 +         STA (POINT),Y
 +         DEY
 +         BPL :l1
 +:sec     ​SEC ​             ;Solid polygons
 +:setp    LDX #$60         ;​Bitmap at $6000
 +         LDA #<​PATS ​      ;​Pattern table
 +         LDY #>PATS
 +         JSR SetParms
 +:loop    ​
 +         JSR ClrBitmap
 +         LDX VOB          ;Calculate view
 +         JSR CalcView
 +         JSR SortVis ​     ;Sort objects
 +         JSR DrawAllVis ​  ;Draw objects
 + ...
 +Aside from the trivial screen clearing routine and some data tables, that's
 +the entire program -- initialize the 64 and the libraries, add some objects,
 +then call the three subroutines above to render the objects to the screen.
 +Is that easy or what?
 + Now let's have a more detailed look at the library, and the example
 +program above.
 +Obj3d overview
 + Lib3d, as given in C=Hacking #16, is a set of core routines for
 +doing 3D graphics and calculations. ​ Obj3d is an extension to lib3d -- it
 +is a set of routines which uses lib3d. ​ Its purpose is to take the tedium
 +out of managing and rendering 3d objects, and make 3d graphics accessible
 +to all those guys who never paid attention during high school math class.
 + I'd like to offer a bit of advice to any of those guys who might
 +happen to be reading, though. ​ If you intend to do any _serious_ 3D coding,
 +for example a game, you're going to need to understand some fundamental
 +things about 3D graphics, and about why the various library routines work
 +the way they do.  Amazingly enough, C=Hacking has a series of articles on
 +3D graphics, with a very comprehensive article in issue #16.  I read it,
 +several times, before doing obj3d, and suggest you do the same.
 + Knowing coders the way I do, I can see that you're going to ignore
 +my advice; that's fine.  For now, you can just jump in and play around with
 +the example programs a little bit, to get a feel for things. ​ But if you
 +later decide to attempt something more involved than displaying objects
 +on the screen, and are unwilling to take a little time to understand how
 +things work, then you should expect to fail miserably, or spend many
 +frustrating hours wondering why something isn't working the way you expect.
 + Therefore, before you begin work on your dream program, I suggest
 +spending an hour or so with C=Hacking #16, a pencil, a piece of paper,
 +and a brain. ​ The last is quite important, as you'll probably have to
 +do some thinking; if you're expecting to understand things with the flick
 +of some magical switch, you're probably reading the wrong magazine.
 + Finally, a word on distribution:​ both lib3d and obj3d are freely
 +distributable. ​ You are _supposed_ to use them in your own programs.
 +You can make money off those programs (good luck!). ​ You don't even have
 +to give me credit, if you really don't want to, as I figure anyone will
 +spot lib3d a mile away.  But please, *use* the thing! ​ That's why I
 +wrote it!
 + And, knowing coders the way I do, if I hear one guy sniff and
 +loudly exclaim to everyone within earshot that he never uses other people'​s
 +code, I'm going to take your tunes, charsets, sprites, editors, assemblers,
 +and all that hardware you didn't design, and bonk you over the head with it.
 + Alright, then.  Let's talk some code.
 +        The obj3d routines can essentially be split into three groups:
 +routines to manage objects globally, routines to manipulate individual
 +objects, and visualization/​rendering routines.
 + The global management routines are for adding/​deleting objects
 +to/from the world, setting up objects for use by other routines, and
 +retrieving information about objects (e.g. location) for use by the
 +        The manipulation routines are used to manipulate individual objects:
 +to move objects forwards and backwards and side to side, to rotate objects
 +about their roll, pitch, or yaw axis, etc.  In other words, these are
 +routines to change the position and orientation of a given object within
 +the world.
 +        The visualization routines are naturally used to visualize the world.
 +These are routines to compute the view from a specific object and render
 +that view to the screen, as well as how to render it -- what bitmap,
 +render it solid or wireframe, etc.  There are also routines for drawing
 +individual objects and even individual faces. ​ These routines are the main
 +interface into the 3d library.
 + In order to support the obj3d routines, lib3d has been upgraded
 +to version 2.0.  The rotation routines have been changed to ease a number
 +of calculations. ​ Three new routines have been added, for plotting points
 +and lines, and for determining which version of the library is in memory.
 +Finally, a multicolor version of the library is available, in addition
 +to the hires version, which includes screen aspect ratio coordinate-
 +corrections as well as multicolor rendering routines.
 + Let's now return to the example program.
 +Example program explained
 + The first part of the code simply sets up 64-related stuff,
 +turning on bitmap mode, etc.  JSR VERSION is used to determine whether
 +the hires or multicolor version of lib3d is being used; if multicolor,
 +then multicolor mode is enabled:
 +* Test code
 +* Nice pair of tets
 +OBS      = $0800          ;Object records
 +GETIN    = $FFE4
 +TestCode ​
 +         LDA $D011
 +         ORA #$20         ;​Bitmap mode
 +         STA $D011
 +         LDA #$08         ;​Bitmap -> $6000
 +         STA $D018
 +         LDA $DD00
 +         AND #$F8
 +         ORA #$02         ;Bank 1
 +         STA $DD00
 +         LDA #$80
 +         STA $028A        ;All keys repeat
 +         JSR VERSION ​     ;If multicolor...
 +         BPL :hires
 +         LDA $D016
 +         ORA #$10
 +         STA $D016
 +:​hires ​  
 +         JSR ClrColMap
 +ClrColMap is not part of the obj3d library; it's just a simple routine
 +to clear the color map.  The next piece of code initializes the obj3d
 +and lib3d libraries with some default values:
 +         LDA #<​OBS ​       ;Object records
 +         LDY #>OBS
 +         JSR Init3D
 +If you ever move some tables around (like the $C000 table), you'll need
 +to change table pointers _after_ calling this routine, as it initializes
 +the zero-page lib3d pointers to certain default values. ​ .AY points to a
 +free area of memory, whose size depends on the number of active objects
 +you intend to have in the world. ​ This will be explained in more detail
 +shortly. ​ The next section of code adds an object into the world, using
 +JSR AddObj:
 +         LDA #<​TETDAT ​    ;​Object data
 +         LDY #>TETDAT
 +         LDX #01          ;ID
 +         JSR AddObj
 +         STX VOB          ;View object
 +If you look at the obj3d memory map, you'll notice that of the 4k it
 +occupies only a measly 1.5k is used for code.  The rest is all used for
 +storage -- a whole bunch of lists, used to organize all the data.  One
 +of these lists is the "​active object list"​. ​ What JSR AddObj does is
 +to find the first empty spot in the active object list, and assign the
 +object to that spot.  This spot is returned in .X, and this number is
 +how obj3d will reference the object. ​ The STX VOB above is for later
 +use by the program; you do not generally need to store every object
 + A maximum of 128 active objects are allowed, so the object number
 +in .X will always be in the range 0-127. ​ Since AddObj always grabs the
 +first open spot, .X will in fact never be larger than the maximum number
 +of active objects in a given program. ​ You might want to use it for your
 +own purposes, too, for example as an index into a list of velocities.
 + AddObj also "​allocates"​ a chunk of memory to store the object ​
 +record in.  This area of memory is specified in the earlier call to 
 +JSR Init3D, above, by .AY.  Each object currently requires 32 bytes of
 +storage, so if there can be N active objects in the world at any given
 +time, this area of memory needs to be of at least size 32*N.  There is
 +a distinction here between "​active"​ and "​inactive"​ objects; an object is
 +"​active"​ if it has been added in with AddObj, i.e. is actively present in
 +the world. ​ It may help to understand this distinction by considering a
 +program like Elite -- although there are many different kinds of objects
 +(different ships, asteroids, etc.), there are never more than a certain
 +number present, i.e. active, at any given time.  So even if you have
 +50 different types of objects, if there are never more than 6 active at
 +any time only 6*32 bytes of RAM are needed.
 + So what is an "​object",​ anyways? ​ Three things are needed to 
 +completely specify an object: its position, its orientation,​ and its
 +structure. ​ The "​structure"​ is the vertices and faces that define what
 +the object looks like.  The position and orientation determine where the
 +object is, and in what direction it is pointing. ​ Position and orientation
 +are such important concepts that it is worthwhile to review them further.
 + Consider something like a chessboard. ​ Each piece is located on
 +a specific square, and when you move it, the piece moves to a different
 +square (as opposed to e.g. moving the board). ​ The square it sits on
 +is its position, and in chess this is denoted by C-4 or something
 +similar. ​ Now imagine that we are one of the knights -- what would we
 +see?  It would depend on the direction we were facing; in other words,
 +our orientation. ​ If we were to then compute the view from a different
 +knight, we would need to know its orientation.
 + In three dimensions, the position is specified by three signed
 +16-bit coordinates,​ the usual x/y/z Cartesian system. ​ The orientation
 +is specified by a 3x3 rotation matrix. ​ This orientation matrix essentially
 +tells you which direction is "​forwards",​ which direction is "​down",​ and
 +which direction is "​sideways",​ _from the object'​s perspective_. ​ That is,
 +if you're flying a plane, the "​forwards"​ direction is always straight
 +ahead. ​ If you're watching it from the ground, the forwards direction
 +changes if the plane turns, or dives, etc.
 + Thus, in obj3d, an object is defined by a 32-byte "​object record":​
 +        CenterX ​        2 bytes         ​Position,​ x-coordinate
 +        CenterY ​        2 bytes         ​Position,​ y-coordinate
 +        CenterZ ​        2 bytes         ​Position,​ z-coordinate
 +        Structure ​      2 bytes         ​Pointer to structure data
 +        ID              1 byte          Optional ID byte
 + User byte 1 byte Free data byte
 +        CenterPos ​      1 byte          Position in rotated center list
 +        CenXRem ​        1 byte          Remainder, x-position
 +        CenYRem ​        1 byte          (Used by MoveUp, etc.)
 +        CenZRem ​        1 byte
 +        Matrix ​         9 bytes         ​Viewpoint matrix, integer part
 +        MatRem ​         9 bytes         ​Fractional part
 +CenterX/Y/Z are the 16-bit signed coordinates specifying where the object
 +is located in the world. ​ "​Structure"​ is a pointer to the structure data
 +defining the vertices and faces which make up the object. ​ Using a pointer
 +means that objects may share the same basic shape, without wasting lots 
 +of memory; it also means that objects may be e.g. animated (beyond merely
 +rotating). ​ When AddObj is called, the contents of .AY are stored here.
 +The structure layout is discussed a little later.
 + The ID and user bytes are purely for the use of the programmer; they
 +are *not* used by the library routines. ​ For example, you might store what
 +type of object this is, and its current velocity. ​ When AddObj is called,
 +the content of .X is stored in ID.
 + CenterPos is an index into a list of relative object centers; this
 +list is generated when the viewpoint is calculated from a specific object.
 + CenX/​Y/​ZRem are used solely by the routines MoveForwards etc.  They
 +represent the fractional portion of the center coordinates,​ to ensure
 +accurate movement (especially when moving by small amounts).
 +        Matrix and Matrem are the "​viewpoint"​ matrix; the viewpoint matrix
 +is the transpose of the orientation matrix. ​ Values in Matrix range from
 +-64 to 64.  The remainder portion is used by the TurnLeft etc. routines
 +(in particular, by the lib3d routines ACCROTX/​Y/​Z);​ only the integer
 +portion is used for rotation/​projection.
 + The viewpoint matrix determines what the world looks like when
 +rotated about the object. ​ The orientation matrix determines how the object
 +looks from somewhere within the world, i.e. what direction it is pointing in,
 +etc.  It's the difference between being "​inside"​ the object or "​outside"​ of
 +the object.
 + Computing views was explained in C=Hacking #16.  Briefly, when
 +an airplane e.g. rolls, it rolls about its fuselage -- about a local
 +coordinate axis -- and when it rolls, the other coordinate axis change
 +direction, relative to the world (e.g. the wings move). ​ The problem is that
 +the usual rotation matrix really only rotates about a _fixed_ coordinate
 +system. ​ If the object starts pointing in a different direction, you would
 +need a way of rotating about the new coordinate axis, i.e. about an arbitrary ​
 +line.  It can be done, using quaternions (which are a generalization
 +of complex numbers), but it is cumbersome and time-consuming (and thus
 +popular among PC programmers). ​ The smart way to do it is to realize that
 +the inverse of a "​viewpoint matrix"​ is an "​orientation matrix",​ and to
 +further realize that the inverse of any rotation matrix is simply its 
 + By the way, it should go without saying that the object record values
 +need to not get screwed up.  If they do, all sorts of strange things can
 +happen. ​ So if you write a program, and it starts behaving or crashing
 +mysteriously,​ first check that the object record isn't hosed.
 + The bytes following the user bytes will probably never be used by
 +a programmer, except perhaps to copy orientation matrices. ​ The bytes
 +up through the user bytes, however, are meant to be modified by the
 +programmer. ​ For example, the next part of the example code sets
 +the center of the object, after AddObj.
 +         LDA #<​STARDAT
 +         LDY #>​STARDAT
 +         LDX #02
 +         JSR AddObj
 +         STX ROB          ;Rotate object
 +         STA POINT        ;Object pointer
 +         STY POINT+1
 +         LDY #5           ;Set center
 +:l1      LDA OCEN,Y
 +         STA (POINT),Y
 +         DEY
 +         BPL :l1
 +Not only does AddObj return the object number in .X, it returns a
 +pointer to the object in .AY.  This pointer is also returned by the
 +routines SetCurOb and GetCurOb. ​ The above code stores this pointer
 +in POINT, and then sets the location by copying from a little table
 +OCEN     DA 00            ;X-coord
 +         DA 00            ;Y-coord
 +         DA $0100         ;​Z-coord
 +At the very least, you will probably have to specify the location of
 +any object you create; otherwise, the default location is the center
 +of the world, at (0,0,0).
 + Next, the example program tells obj3d about how objects should
 +be rendered to the screen:
 +:sec     ​SEC ​             ;Solid polygons
 +:setp    LDX #$60         ;​Bitmap at $6000
 +         LDA #<​PATS ​      ;​Pattern table
 +         LDY #>PATS
 +         JSR SetParms
 +SetParms takes four parameters. ​ The carry flag determines whether solid
 +or wireframe polygons will be used.  (The wireframe routine is not
 +particularly fast, because it is not particularly smart -- it does not
 +compute hidden faces. ​ It does, however, skip lines that have already been
 +drawn.) ​ .X contains the location of the bitmap (high byte) to be rendered
 +to.  .AY contains a pointer to a table of 8x8 bit patterns, used by the
 +rendering routines:
 +PATS                      ;Pattern table
 +SOLID    = 0
 +DITHER1 ​ = 1
 +         HEX 55AA55AA55AA55AA
 +DITHER2 ​ = 2
 +         HEX AA55AA55AA55AA55
 + ...
 +These patterns will later be referenced by their position in the table.
 + Thus do we come to the main loop, which is just four routines:
 +:loop    ​
 +         JSR ClrBitmap
 +         LDX VOB          ;Calculate view
 +         JSR CalcView
 +         JSR SortVis ​     ;Sort objects
 +         JSR DrawAllVis ​  ;Draw objects
 +The first routine is not a part of obj3d; it just clears the bitmap.
 +JSR CalcView computes what the world looks like from object .X -- from
 +the object'​s position and orientation. ​ It does so by translating
 +and rotating each object'​s center (except the viewpoint object),
 +and storing this relative center in the center list; the value of
 +CenterPos, in the object record, is an index into this list.
 + JSR SortVis computes a sorted list of _visible_ objects,
 +from the center list.  An object is considered "​visible"​ if it lies
 +in a 45 degree pyramid in front of the viewpoint object, and if
 +its z-coordinate is between zmin and zmax.  The paramters zmin and zmax
 +may be set by calling JSR SetVisParms;​ I don't recall the default values
 +offhand -- probably 100 < CenterZ < 8192.  The purpose of the sorted
 +list is to ensure the objects are drawn from back to front -- so that
 +objects overlap each other correctly.
 + Finally, after calculating the viewpoint, and calculating the
 +visible objects, all that remains is to draw all the visible objects,
 +with JSR DrawAllVis. ​ Individual objects may be drawn one at a time
 +with JSR DrawNextVis. ​ When an object is drawn, it is rotated by the
 +orientation and viewpoint matrices, then added to its relative center
 +(M R P + M C), then projected, with the projected points stored in two
 +lists, PLISTX and PLISTY. ​ The appropriate rendering routine is then
 +called, using the points in these lists. ​ Also, before projecting, the
 +rotated z-coordinates are stored in PLISTZ, for use in drawing compound
 +objects (described later).
 + That takes care of displaying an object. ​ What about manipulating
 +an object, i.e. moving around? ​ The example program accepts the following
 + a/z s/d q/w -- Pitch, roll, and yaw object
 + @ / -- Move forwards/​backwards
 + space -- Switch viewpoints
 + = -- Draw using wireframe graphics
 + * -- Draw using solid graphics
 +You can look at the source code to see how all of this is done.  Here,
 +it will be enough to look at the code to pitch the object and the
 +code to move the object (and to swap viewpoints, for good measure).
 +First, the library must be told which object to operate on:
 +         LDX ROB          ;Set rotation object
 +         JSR SetCurOb
 +Recall that after adding in the second object, .X was stored in ROB.
 +In this program, movement always affects the second object, no matter
 +which object is the view object. ​ The program now waits for a keypress,
 +and branches accordingly:​
 +:wait    JSR GETIN
 +         BEQ :wait
 +         CMP #'​a'​
 +         BEQ :pitchdn
 +         CMP #'​z'​
 +         BEQ :pitchup
 +         CMP #'​@'​
 +         BEQ :movf
 +         CMP #'/'​
 +         BEQ :movb
 +         CMP #' '
 +         BNE :wait
 +         LDX VOB          ;Swap viewpoint
 +         JSR SetCurOb
 +         JSR GetNextOb
 +         STX VOB
 +         JMP :loop
 +:pitchdn CLC
 +         DFB $24
 +:pitchup SEC
 +         JSR Pitch
 +         JMP :loop
 +:movf    LDA #$07
 +         DFB $2C
 +:movb    LDA #$F9
 +         JSR MoveForwards
 +         JMP :loop
 +To change the viewpoint, the program simply sets the current object
 +to the current view object, then advances to the next object in the
 +active object list, and stores that as the new viewpoint object.
 + The routines Pitch, Roll, and Yaw all function similarly. ​ They
 +all rotate by a fixed amount (2*pi/128 radians, which is about 3 degrees),
 +and the carry flag specifies the direction of rotation. ​ The rotation
 +is performed about the _local_ coordinate axis of the object, which rotates
 +with the object; there is also a routine, SetMat, which can rotate about
 +the fixed world axis.
 + The MoveForwards,​ MoveDown, and MoveSide routines also function
 +similarly. ​ In these routines, .A contains the "​velocity"​ or length of
 +the move, as a signed 8-bit number; the actual distance moved is four
 +times .A.
 + And that more or less sums up the less-technical aspects of the obj3d
 +library routines. ​ For a complete list of routines, routine parameters,
 +and a memory map, see the files "​obj3d.ref"​ and "​lib3d.ref"​ in the .zip
 +archive (they'​re plain ASCII text files). ​ There'​s also some example source
 +code.  And now it is time for the more technical details: object structure
 +data, compound objects, some internals, and lib3d v2.0 updates.
 +The More Techincal Details
 + As you may recall, the object record contains a pointer to the
 +object structure data -- the data describing the points and faces that
 +define what the object looks like.  For a normal object, this data is
 +organized as follows:
 + TypeID = 0 1 byte Object type (normal, compound)
 +        Npoints ​        1 byte          Number of points (vertices)
 +        Nfaces ​         1 byte          Number of faces
 +        Xcoords ​        n bytes         ​Vertices,​ x-coordinates
 +        Ycoords ​        n bytes         ​Vertices,​ y-coordinates
 +        Zcoords ​        n bytes         ​Vertices,​ z-coordinates
 +        faces: ​                         List of faces
 +          nverts ​       1 byte          Number of vertices in face
 +          fillpat ​      1 byte          Fill pattern (index into pattern list)
 +          fpoints ​      m+1 bytes       ​Points (indices into X/Y/Z above)
 +TypeID identifies the object type, and will be discussed in more detail
 +shortly. ​ Npoints and Nfaces do what they say; there is no limit on the
 +number of faces, but an object may not have more than 128 points (which
 +is a HUGE object!).
 + X/​Y/​Zcoords are signed integers in the range -95..95, for reasons
 +described in C=Hacking #16.  Note that the _length_ of each of these points
 +must be less than 95.  For example, a point like (65,65,65) has length
 +sqrt(65^2 + 65^2 + 65^2) = 65*sqrt(3) = 112 or so, so if this point
 +were rotated down onto the x-axis it would have coordinates (112,0,0),
 +which is out of the -95..95 range. ​ Strange things will happen to your
 +rotating objects if you make this error, so if your objects start freaking
 +out upon rotation, check this first.
 + Note, moreover, that the vertices are defined about the center of
 +rotation of the object; this center is exactly the coordinates CenterX/Y/Z
 +in the object record, i.e. where the object is located in the world.
 + Following the coordinates is a list of faces, which are specified
 +by a fill pattern and a list of vertices. ​ The fill pattern is an index
 +into the pattern table, which you may recall is set using JSR SetParms;
 +the actual location of the pattern is PATTAB + 8*fillpat. ​ The vertex list
 +is simply a list of indices into the X/Y/Zcoords table; moreover, this
 +list must close upon itself, e.g. 0-1-2-0, so that it is of length nverts+1,
 +where nverts is the number vertices in the face.
 + With all that in mind, here is the data for the first object in
 +the example program, a simple tetrahedron:​
 +* Test object 1: simple tetrahedron
 +TETDAT ​  
 +         DFB 0            ;Normal object
 +         DFB 4            ;Number of points
 +         DFB 4            ;Number of faces
 +* Point list
 +TETX     DFB 45,​45,​0-45,​0-45
 +TETY     DFB 45,​0-45,​45,​0-45
 +TETZ     DFB 45,​0-45,​0-45,​45
 +* Face list
 +FACE1    DFB 3            ;Number of vertices
 +         DFB SOLID        ;Fill pattern
 +         DFB 0,​1,​2,​0 ​     ;Vertices
 +FACE2    DFB 3
 +         DFB ZIGS
 +         DFB 3,2,1,3
 +FACE3    DFB 3
 +         DFB CROSSSM
 +         DFB 3,0,2,3
 +FACE4    DFB 3
 +         DFB HOLES
 +         DFB 3,1,0,3
 +Pretty straightforward,​ really. ​ Note that faces can have any number of
 +points in them; triangles will have three points, squares (e.g. sides of
 +cubes) will have four points, and so on.  Thus the way to think about
 +creating an object is that some points are defined about some center, and
 +those points are then connected together to make faces.
 + This kind of definition works fine for simple objects -- that is,
 +for convex objects, where one part of the object won't obscure a different
 +part.  But what about more complicated objects -- a tank, say, or a cross --
 +where one piece of the object might be in front of another piece at one
 +orientation,​ and behind in a different orientation? ​ The general problem
 +of polygon clipping is fairly complicated and time consuming. ​ Being 64
 +programmers,​ though, we are of course a little sneakier about doing stuff,
 +and we can make suitably complicated objects by just modifying the above
 +structure a little bit.
 + Consider for a moment a cross, or the pointy stars from Cool World.
 +The basic problem is that a given chunk of the object might be either
 +in front of the rest of the object or behind it.  If it is behind,
 +then we want to draw it first; if it's in front, then we want to draw
 +it last.  The idea, then, is to divide a complex object up into a number
 +of smaller objects, and to draw them in the right order. ​ In the obj3d
 +lexicon, such an object is a _compound object_.
 +Compound Objects
 + To define a "​normal"​ object, we define a list of points and then
 +join points into faces. ​ A compound object takes this a step further,
 +joining faces into smaller "​oblets"​. ​ All that remains is to figure out
 +what order to draw each oblet in.
 + Recall that before rendering objects to the screen, JSR SortVis
 +is called. ​ SortVis sorts the visible objects based on their z-coordinates
 +and far-away objects are drawn first, so that objects will be rendered
 +in front of one another on the screen. ​ So we can do the exact same thing
 +with the oblets: associate a _reference point_ with each oblet, depth-sort
 +the reference points, and then draw the oblets from back to front. ​ The
 +reference point doesn'​t have to be connected to anything, or a part of
 +any face -- just some point which allows the oblets to be sorted correctly.
 + As an example, consider the pointy stars from Cool World. ​ These
 +are easy to create, by starting with a cube, and then pulling out the
 +centers of each face, creating a star with six tines. ​ The tips of each
 +tine are a convenient reference point, so the idea is to divide the
 +star up into six oblets -- the tines -- and use the tip of the tine as
 +the reference point for each oblet. ​ Once the tips are sorted, the tines
 +may be drawn in the correct order, and viola! ​ Violin! ​ Instant polygon
 +clipping, with very little computational effort. ​ Note that the key to
 +success with this method is choosing the reference points well.
 + The format for a compound object is similar to a normal object,
 +with TypeID set to $80 and Nfaces replaced by Noblets:
 +        TypeID = $80    1 byte          Object type (normal, compound)
 +        Npoints ​        1 byte          Number of points (vertices)
 +        Noblets ​        1 byte          Number of oblets
 +        Xcoords ​        n bytes         ​Vertices,​ x-coordinates
 +        Ycoords ​        n bytes         ​Vertices,​ y-coordinates
 +        Zcoords ​        n bytes         ​Vertices,​ z-coordinates
 + oblets:​ List of oblets
 +   ref points p bytes List of reference points (indices)
 +   oblet 1
 +     nbytes 1 byte Number of bytes in this oblet
 +     nfaces 1 byte Number of faces
 +     face list:
 + nverts ​ 1 byte          Number of vertices in face
 + fillpat 1 byte          Fill pattern (index into pattern list)
 + fpoints m+1 bytes       ​Points (indices into X/Y/Z above)
 +   oblet 2
 +     ...
 +The list of reference points is a list of indices into X/​Y/​Zcoords,​
 +and the first point in the list is the reference point for the first oblet,
 +etc.  Note the "​nbytes"​ field in each object; this is used to advance
 +through the oblet list (for example, to find oblet #4 quickly). ​ There
 +is a limit of 32 or so oblets allowed; chances are awfully good you'll
 +never reach this limit, consdering that an object can only have 128 points!
 + Here is the second object from the example program, which is just
 +a modified pointy star.  Note that the TypeID is set to $80; a normal
 +object has TypeID = 0.  It is also worth pointing out that the choice
 +of reference points -- the tip of each surface -- can lead to incorrect
 +clipping on occasion, because the tips are of different lengths; nevertheless,​
 +they work quite well.
 +* Test object 2: a compound object,
 +* spaceship-kind of thing (essentially
 +* cool world stars)
 +         DFB $80          ;Compound object
 +         DFB 14           ;​Number of points
 +         DFB 6            ;Number of oblets
 +* Point list
 +         DFB 50,​0-50,​0,​0,​0,​0,​15,​15,​15,​15,​0-15,​0-15,​0-15,​0-15
 +         DFB 0,​0,​16,​0-26,​0,​0,​10,​10,​0-10,​0-10,​10,​10,​0-10,​0-10
 +         DFB 0,​0,​0,​0,​94,​0-22,​15,​0-15,​0-15,​15,​15,​0-15,​0-15,​15
 +* Oblet list: reference points
 +         DFB 0            ;First 6 points
 +         DFB 1
 +         DFB 2
 +         DFB 3
 +         DFB 4
 +         DFB 5
 +* Oblet 1
 +         DFB 26           ;26 bytes
 +         DFB 4            ;4 faces
 +* faces
 +         DFB 3            ;4 points
 +         DFB SOLID        ;pattern
 +         DFB 0,​8,​7,​0 ​     ;Star 2, Tine 0, face 1
 +         DFB 3
 +         DFB ZIGS
 +         DFB 0,7,6,0
 +         DFB 3
 +         DFB ZAGS
 +         DFB 0,6,9,0
 +         DFB 3
 +         DFB DITHER1
 +         DFB 0,9,8,0
 +* Oblet 2
 +         DFB 26           ;26 bytes
 +         DFB 4            ;4 faces
 +         DFB 3            ;4 points
 +         DFB ZIGS
 +         DFB 1,11,12,1
 +         DFB 3
 +         DFB BRICK
 +         DFB 1,12,13,1
 +         DFB 3
 +         DFB DITHER2
 +         DFB 1,13,10,1
 +         DFB 3
 +         DFB ZAGS
 +         DFB 1,10,11,1
 +* Oblet 3
 + ...
 +The remaining oblets are all similar.
 +lib3d v2.0
 + Finally, lib3d has been updated, to support the obj3d library and
 +to be more cool in general.
 + The first thing to notice is that there are now two libraries:
 +one for hires, and another for multicolor. ​ The multicolor version not
 +only renders in multicolor, but corrects for the screen aspect ratio
 +by multiplying all y-coordinates by 5/4 after projection.
 + The second thing to notice is the addition of three new routines:
 +PLOT, DRAWLINE, and VERSION. ​ VERSION returns the lib3d version number;
 +the high bit set indicates a multicolor version. ​ Currently lib3d is
 +at v2.0, so this routine returns either 2 or $82.
 + PLOT and DRAWLINE are used to, well, plot points and draw lines.
 +The coordinates are 16-bit signed values, i.e. the routines understand
 +points that are off the screen. ​ To use them, you just store the coordinates
 +in zero page and JSR.  The line drawing routine is not the world'​s fastest,
 +but it is still zippy, especially for its (quite small) size and flexibility.
 + The third thing to notice is that the memory map has been altered
 +significantly,​ both in zero page and in RAM.  lib3d now starts at $8400,
 +instead of $8600, so not only has the jump table moved but there is
 +no longer room for sprites in bank 2.  Zero page has been shuffled around,
 +with perhaps the most significant result being that all of the $Fx locations
 +(everything above $C4 actually) are free for use by the programmer. ​ See
 +the memory map for more details.
 + What you might not have noticed is that several of the routines
 +have now changed. ​ The accumulation routines ACCROTX/Y/Z now require a
 +pointer to the matrix to be accumulated;​ they used to simply operate on
 +a matrix in zero page.  This way, object matrices may be directly
 +manipulated,​ instead of doing any wasteful copying to and from ZP.
 + ROTPROJ has also gone through a big change. ​ Recall that the
 +equation for a 3D world is
 + M R P + M C
 +where P is an object point, R is the local rotation (orientation),​ M is
 +the viewpoint rotation, and C is the object'​s center. ​ ROTPROJ used to
 +just calculate MP + MC; it now calculates MRP + MC, which is why zero
 +page now contains a viewpoint _and_ an orientation matrix. ​ Also, ROTPROJ
 +stores the z-coordinates before projecting, for use in drawing compound
 +objects, so there'​s now a pointer to PLISTZ. ​ Finally, I feel that rotation
 +without projection is now a pretty useless feature; I left it in, but
 +it now stores the rotated points to PLISTX/Y/Z, instead of using a special
 +zero-page pointer for the purpose.
 + There were also some miscellaneous recodings, to conserve memory
 +and such, but they'​re nothing special. ​ For descriptions of the
 +individual routines and their use, see the lib3d.ref document in the file.
 +The End
 + Since most of the obj3d routines are either really stupid or were
 +yanked out of Cool World, I don't think there'​s any reason to go over them
 +(except the cool sorting algorithm, discussed elsewhere in this issue).
 + What more is there to say?
 + Go home and code!
 +.                                   C=H #18
 +Obj3d programmer'​s reference
 +last update: 4/16/99
 +version 2.0
 +* Obj3d and lib3d memory map:
 +  |    |                 ​| ​          ​| ​            |
 +  $41  $0200             ​$4F00 ​      ​$8400 ​        $C000
 +$41-$49 ​        ​Orientation matrix
 +$4A-$52 ​        ​Viewpoint matrix
 +$53,​$54 ​        ​Xoffset,​ Yoffset (screen offsets -- where 0,0 is on screen)
 +$55-$72 ​        ​Various temporary pointers and variables
 +$60-$67 X1,​Y1,​X2,​Y2 -- signed 16-bit coordinates for PLOT and DRAWLINE
 +$8B-$8E PLISTZLO,​ PLISTZHI -- pointers to rotated z-coordaintes
 +$A3-$AE ​        CXLO, CXHI, CYLO, CYHI, CZLO, CZHI -- Pointers to rotated
 +                centers.
 +$AF-$B0 ​        ​ROTMATH -- pointer to rotation math table
 +$B1-$B8 ​        ​MULTLO1 LO2 HI1 HI2 -- pointers to multiplcation tables
 +$B9             ​Bitmap (high byte)
 +$BB-$BC ​        ​FILLPAT -- pointer to fill pattern (not table)
 +$BD-$C4 ​        ​PLISTXLO,​ XHI, YLO, YHI -- pointers to rotated/​projected points
 + (PLISTZLO = $8B, above)
 +$0200           Point queue
 +$4F00 LINELO -- Record drawn lines in wireframe
 +$4F68 LINEHI
 +$4FD0 OBLETS -- Sorted oblet list
 +$5000           obj3d
 +$5600-$5D00 ​    obj3d tables:
 + $5600 OBJLO ​ -- Object list (pointers)
 + $5680 OBJHI
 + $5700 VISOBJ -- Visible object list
 + $5781 OBCEN ​ -- Center object number
 + $5800 CX - Translated rotated centers
 + $5880 HCX - (high byte)
 + $5900 CY
 + $5980 HCY
 + $5A00 CZ
 + $5A80 HCZ
 + $5B00 PLISTX - Point list (projected)
 + $5B80 (high bytes)
 + $5C00 PLISTY
 + $5C80 (high byte PLISTY)
 + $5D00 PLISTZ
 + $5D80 high byte
 +$5E00-$5FFF ​    ​ROTMATH -- rotation table (relocatable,​ pointed to by $AF)
 +$8400-$9FFF ​    lib3d
 +$C000-$C2FF ​    ​MULTLO,​ pointed to by $F7-$F8 and $F9-$FA
 +$C300-$C5FF ​    ​MULTHI,​ pointed to by $FB-$FC and $FD-$FE
 +$C600-$CFFF ​    More tables (nonrelocatable,​ see lib3d.text)
 + ----------------
 +So, to put it another way, free areas of RAM are:
 +$55-$72 (temporary only; library work variables)
 +  |    |                 ​| ​          ​| ​            |
 +  $41  $0200             ​$4F00 ​      ​$8400 ​        $C000
 +Moreover, the range $C000-$C5FF can be made available by relocating the
 +tables there. ​ This way, $E000 may be used as a bitmap, with enough extra
 +room for 8 sprite definitions. ​ Thus, bitmaps are available in all banks.
 +* Object record
 +ObjCX    = 0 ;Center, X-coord (signed 16-bit)
 +ObjCY    = 2 ;Y-coord
 +ObjCZ    = 4 ;z-coord
 +ObjData ​ = 6 ;Pointer to structure data
 +ObjID    = 8 ;User bytes
 +ObjUser = 9
 +ObCenPos = 10 ;​Position in center list
 +ObjCXRem = 11 ;Center remainders
 +ObjCYRem = 12
 +ObjCZRem = 13
 +ObjMat ​  = 14 ;​Viewpoint matrix, int + rem
 +ObjSize ​ = 32           ;32 bytes total
 +* Jump table
 +Init3D ​     = $5000          ;​Initialize lib3d
 +AddObj ​     = Init3D+3 ​       ;Add object to object list
 +DelObj ​     = AddObj+3 ​      ​ ;​Delete object from list
 +SetCurOb ​   = DelObj+3 ​       ;Set current object
 +GetCurOb ​   = SetCurOb+3 ​     ;Get current object
 +GetNextOb ​  = GetCurOb+3 ​   ;Get next object in list
 +GetObj ​     = GetNextOb+3 ​   ;Get pointer to object
 +SetMat ​     = GetObj+3 ​      ​ ;​Calculate and set object matrix
 +Pitch       = SetMat+3 ​      ​ ;​Pitch - rotate object around x-axis
 +Yaw         = Pitch+3 ​       ;Yaw - rotate around y-axis
 +Roll        = Yaw+3          ;Roll - rotate around z-axis
 +MoveSide ​   = Roll+3 ​        ​ ;​Move object
 +MoveUp ​     = MoveSide+3
 +MoveForwards = MoveUp+3
 +GetSideVec ​ = MoveForwards+3 ;​Orientation vectors
 +GetUpVec ​   = GetSideVec+3 ​  ​ ;​(length=64)
 +GetFrontVec = GetUpVec+3
 +SetParms ​   = GetFrontVec+3 ​ ;Set rendering parameters
 +SetVisParms = SetParms+3 ;​Set visibility parameters
 +CalcView ​   = SetVisParms+3 ​ ;Set viewpoint = object
 +SortVis ​    = CalcView+3 ​    ​ ;​Compute and sort visible objects
 +DrawAllVis ​ = SortVis+3 ​   ;Draw all visible objects
 +GetNextVis ​ = DrawAllVis+3 ;Draw next objesible object list
 +RotDraw ​    = GetNextVis+3 ​ ;Rotate and draw object
 +DrawFace ​   = RotDraw+3 ;​Draw single face (polygon)
 +* lib3d stuff
 +CALCMAT ​ EQU $8800
 +ACCROTX ​ EQU $8803
 +ACCROTY ​ EQU $8806
 +ACCROTZ ​ EQU $8809
 +PLOT     EQU $8815
 +Routine descriptions
 +  On entry: .AY = pointer to object record storage area
 +  On exit:
 +  This routine is used to initialize various obj3d and lib3d pointers and
 +  variables, and should be called before any other routines are called.
 +  Note that if e.g. tables or the screen center are moved around, the
 +  corresponding variables should be set *after* calling Init3D.
 +  On entry: .AY = pointer to object structure data
 +            .X  = optional user ID byte
 +  On exit:  .AY = pointer to object
 +            .X  = object number
 +            C set indicates error (e.g. too many objects!)
 +  AddObj is used to add objects into the world. ​ It places the object at
 +  the first empty spot in the active object list, and allocates a 
 +  corresponding portion of memory in the object storage area, as passed
 +  to Init3D.
 +  On entry: .X  = object number
 +  On exit:  .AY = pointer to object
 +            .X  = object number
 +  SetCurOb is used to set the current object. ​ The "​object number"​ is
 +  a number returned by AddObj. ​ This routine is used before calling
 +  e.g. the movement routines, which act on the current object.
 +  On entry:
 +  On exit:  .AY = pointer to object
 +            .X  = object number
 +  GetCurOb is used to get the current object number and pointer.
 +  On entry: .X  = object number
 +  On exit:  .AY = pointer to object
 +  GetOb gets a pointer to an object without setting that object to
 +  be the current object.
 +  On entry: .X  = object number
 +  On exit:  C set means rather nasty error
 +  DelObj is used to delete an object from the active object list.
 +  On entry:
 +  On exit: .X  = object number
 +           .AY = object pointer
 +           C = 1 -> error
 +  GetNextOb gets the next object in the active object list, starting from
 +  the current object. ​ On exit, the current object is set to .X.  This
 +  routine may be used to cycle through the list of active objects.
 +* Object manipulation routines
 +  On entry: .X  = angle around x-axis
 +            .Y  = angle around y-axis
 +     .A  = angle around z-axis
 +  On exit:
 +  SetMat is used to set a rotation matrix for the current object. ​ Angles
 +  go from 0..127. ​ Note that SetMat rotates around the fixed _world_ axis, 
 +  not an object'​s local coordinate axis; use Yaw/​Pitch/​Roll to rotate about
 +  the local axis.
 +  On entry: C clear -> positive rotation
 +            C set   -> negative rotation
 +  On exit:
 +  ​
 +  Yaw, Pitch, and Roll rotate an object by a fixed amount (3 degrees or so)
 +  about the local coordinate axis.  (The local coordinate axis rotates
 +  with the object).
 +  On entry: .A  = distance (signed)
 +  On exit:
 +  MoveUp/​Side/​Forwards are used to move an object along its local coordinate
 +  axis, by an amount proportional to .A; negative values of .A will move in
 +  the opposite direction.
 +  On entry:
 +  On exit: (.X,.Y,.A) = signed (x,y,z) coordinates of orientation vector
 +  GetVec is used to figure out what direction the current object is
 +  pointing in.  The vectors are signed, and of length 64.
 +* Visualization routines
 +  On entry: .AY = pointer to pattern table
 +     .X  = Bitmap address (high byte)
 +     C set   -> solid polygons
 +     C clear -> wireframe
 +  On exit:
 +  SetParms is used to set certain rendering parameters. ​ The pattern table
 +  consists of a list of 8x8 patterns.
 +  On entry: .AY = Maximum object range
 +     .X  = Minimum object range
 +  On exit:
 +  SetVisParms is used to set the range in which an object is considered
 +  "​visible"​. ​ An object'​s center z-coordinate is compared to these values;
 +  the range is not a radius.
 +  On entry: .X  = Viewpoint object
 +  On exit:
 +  CalcView computes the view from the viewpoint object, by translating
 +  and rotating centers relative to the viewpoint object. ​ The relative
 +  centers are stored in the CX HCX etc. lists; the ObCenPos element in
 +  the object record is an index into this list.
 +  On entry:
 +  On exit:
 +  SortVis computes a sorted list of visible objects. ​ Objects are visible
 +  if they are within a 45 degree cone forwards of the viewpoint object,
 +  and within the min/max visibility range, which may be changed with
 +  SetVisParms. ​ CalcView must be called before calling this routine.
 +  Sorting is necessary to ensure that objects overlap one another
 +  correctly on the screen. ​ On exit, the current object is set to the
 +  first object in this list.
 +  On entry:
 +  On exit:
 +  DrawAllVis draws all visible objects, in order, as determined by SortVis.
 +  On entry:
 +  On exit:  .X  = Current object
 +     .AY = Pointer to object
 +     N set -> at end of list
 +  GetNextVis fetches the next object in the visible object list, setting
 +  it to the current object. ​ This routine is used to draw one object at
 +  a time, but in the proper order.
 +  On entry: .X = Object number
 +  On exit: Cool rendered polygon
 +  RotDraw renders an object to the screen, by rotating and projecting
 +  the object vertices and then drawing faces, either wireframe or solid.
 +  On exit, the rotated and projected points, i.e. the screen coordinates
 +  of the object, are stored in PLISTX and PLISTY; PLISTZ contains the
 +  rotated (but not projected) z-coordinates.
 +  On entry: .AY = pointer to face data
 +  On exit:  .AY = pointer to next face
 +  DrawFace renders a polygon to the screen, either wireframe or solid.
 +  It would be an awfully good idea to have the rotated points in PLIST,
 +  i.e. to call RotDraw before calling this routine.
 +.                                   C=H #18
 +Stroids ​   o
 +------- ​ o   o
 +               o SLJ
 + When you get down to it, just displaying things on the screen is
 +awfully easy.  And clearly, the obj3d library is up to the task of simple
 +programs like Cool World. ​ What isn't clear, however, is whether it is
 +useful for making applications like games, where objects are constantly
 +being created and destroyed, and have different properties and velocities
 +and such.
 + Hence stroids. ​ Stroids is a demonstration of the obj3d routines,
 +more specifically a demonstration that obj3d is suitable for writing games
 +and such.  In stroids, you can fly around in an asteroid field containing
 +three different types of randomly drifting asteroids. ​ The first part of
 +this article will discuss the main algorithms and techniques used in the
 + Stroids is also meant to be a starter project for people who would 
 +like to experiment with the library. ​ For the most part, all of the difficult
 +routines have been written; with a little more work, Stroids could be made 
 +into an actual game, and there are lots of opportunities for modification and
 +improvement. ​ Thus the second part of this article will suggest a number
 +of possible ideas and modifications,​ and how they might be coded up.  This is
 +rather akin to a C=Hacking C=Hallenge, so I'm interested in publishing the
 +niftier modifications and applications that might result.
 + Finally, the full source code is included at the end of this article
 +(the PETSCII el cheapo version is in the .zip).
 +The program
 + The source code is in El Cheapo format. ​ To use the program, just
 +load and run the loader program. ​ Stroids accepts the following keys:
 + joystick in port 2 Pitch and yaw, fire
 + @ and /​ Increment/​decrement velocity
 + space Switch viewpoint object
 + r Toggle radar on/off
 + a/z q/w s/d Pitch, roll, yaw
 + +/​- Increment/​decrement asteroid density
 + = Toggle wireframe mode
 +Initially, the viewpoint is set to a large, fixed tetrahedron at the
 +center of the world; your ship is straight ahead, and you can move
 +it around using the joystick and @/.  Pressing space shifts the viewpoint
 +to the next object -- the ship.  Pressing space again will hop onto
 +the next object -- an asteroid! ​ Pressing it some more will cycle through
 +all the objects, eventually returning to the first object.
 +The code
 + The purpose of this project was to test out the obj3d routines
 +in some tougher game-like conditions, in which many non-indentical objects
 +are present and are being created and destroyed more or less randomly,
 +and where things like collisions must be detectable. ​ A 3D asteroid field
 +seemed like a reasonable test -- asteroids can created more or less randomly,
 +with different sizes and velocities. ​ A player could then fly around in
 +the asteroid field, maybe with a radar display showing the relative
 +positions of asteroids. ​ Maybe the asteroids could be shot, or simply
 +dodged. ​ And so on.
 + The program is really pretty simple. ​ There are two routines which
 +control the asteroid field; one controls creating and destroying asteroids,
 +the other controls their movement. ​ There are two routines to compute and
 +render a crude radar display. ​ Obj3d is used to render the screen, and
 +all that's left is to move the ship around and wait for keys.  A little ​
 +IRQ routine is used to keep the program running under 60fps on a SuperCPU,
 +which also reads the joystick (and which in principle would play music,
 +handle sound effects, etc.), and that's about it.  Thus the main loop is
 +quite straightforward:​
 + - Update asteroid field
 + - Set up radar
 + - Compute/​render main screen
 + - Render radar display
 + - Move ship and wait for keys
 +Stroids  ​
 +         JSR Init
 +         LDA #00
 +         STA CURD
 +:loop    ​
 +         JSR CheckDensity
 +         JSR DriftStroids
 +         JSR SetRadar
 +         LDX VOB          ;Calculate view
 +         JSR CalcView
 +         JSR SortVis ​     ;Sort objects
 +         JSR DrawAllVis ​  ;Draw objects
 +         JSR DrawRadar
 +         JSR CheckFire
 +         JSR SwapBuf
 +         LDX ROB          ;Set rotation object
 +         JSR SetCurOb
 +         LDA VELOCITY
 +         JSR MoveForwards
 +:wait    LDA IRQFLAG
 +         BEQ :wait
 +The asteroid field
 + The asteroid field only "​exists"​ in a little cube.  When asteroids
 +exit this cube they are destroyed, and new asteroids created. ​ Currently
 +this cube stays fixed in the world, but one of the suggested projects is
 +to center it on, i.e. make it move with, the ship.  The purpose of the
 +cube is to keep things manageable -- it makes no sense to keep track of
 +asteroids that are many thousands of units away.  That would just massively
 +bog down the system, and mean that the player wouldn'​t ever see or interact
 +with any asteroids. ​ What's fun about that?
 + The asteroid field within the cube is characterized by a "​density",​
 +which may be changed by pressing the + and - keys.  There are three different
 +kinds of asteroids -- large, medium, and small -- and each asteroid has a
 +"​weight"​. ​ When an asteroid is created, its weight is added to the total
 +field weight; when destroyed, its weight is subtracted. ​ If the total weight
 +exceeds the "​density"​ value, no new asteroids are created. ​ The net result
 +is a constantly changing asteroid field with a pleasantly uniform density.
 + To create an asteroid, a random value is chosen for the weight;
 +this weight determines the size of the asteroid. ​ If the weight is too large
 +(would exceed the field density) then no asteroid is created. ​ This keeps
 +the field changing, since asteroids are not simply replaced by identical
 +objects, and probably gives big asteroids a chance. ​ That is, a field that 
 +is always "​full"​ surely favors small objects: if a large asteroid leaves
 +the field, it can be replaced by multiple smaller ones; if a small asteroid
 +leaves, it can only be replaced by a smaller one.  So I predict that the
 +field would otherwise quickly degenerate into a bunch of little asteroids.
 + If an asteroid makes it into the field, it is assigned a random
 +position, speed, and orientation. ​ The orientation is computed using the
 +obj3d routine SetMat. ​ The object'​s "​weight"​ is stored in the ID byte of
 +the object, and the velocity is stored in the user byte.  This provides an
 +easy way to identify the type of object, and keeps the velocity along with
 +the object (instead of putting it in another table). ​ By storing all non-
 +asteroid objects with the high bit of the ID byte set, it is very easy to
 +differentiate asteroids from other objects. ​ Once created, an asteroid
 +just moves in a straight line, until it leaves the play area.  There is no
 +collision detection -- that's a project for you!  (But it's pretty easy).
 + All of this is done using two routines: CheckDensity and DriftStroids.
 +CheckDensity simply creates new asteroids until it can't create any more;
 +DriftStroids simply goes through the object list, looking for asteroids
 +and moving any it finds (recall that the velocity of the asteroid is
 +stored in the user byte). ​ The code is pretty straightforward,​ so you
 +can go through the source listing to examine the routines in more detail.
 +Random numbers
 + It is worthwhile to say a word or two about the random number
 +generator used.  I used it as an experiment in my quest for a very quick
 +but reasonable random number generator, and it seems to work fairly well --
 +certainly better than other ones I tried. ​ (Deficiencies in random number
 +generators become awfully apparent awfully quick, in the asteroid field).
 + This particular generator uses the "​middle-squares"​ method. ​ In
 +this method (which was proposed by VonNeumann),​ an n-bit number is squared,
 +and the middle n-bits are taken as the next number in the sequence.
 +In this case, the numbers are 8-bits. ​ To get the next number in the
 +sequence, the number is squared, giving a 16-bit number, and the middle
 +eight bits -- bits 4 through 11 -- are used as the next number in the
 + To do the squaring, I simply used the lib3d multiplication tables
 +of f(x)=x^2/​4. ​ Two more divisions by two thus gives x^2/16, i.e. the middle
 + Middle-squares is not without problems. ​ The method is known to
 +have a number of short sequences -- ideally you want a sequence of numbers
 +that takes a long time to repeat; a sequence like 1 99 63 1 99 63 ... isn't
 +so useful. ​ The method also dies once you hit a zero -- zero squared is
 +just zero.  So I built in a simple modification to compensate for these
 +deficiencies:​ the routine re-seeds itself whenever a) the current seed
 +generates itself (like zero will), or b) the current number sequence
 +exceeds a maximum length (that is, after generating N numbers it re-seeds
 +itself). ​ The re-seeding is just a simple method to keep the algorithm from
 +getting stuck.
 +Radar Display
 + Navigating around a 3D world is very difficult without some form
 +of reference. ​ A compass is one possibility;​ landmarks another. ​ A radar
 +display is potentially much more useful, though, and also more difficult --
 +another good test of the routines. ​ The stroids radar uses a pretty neat
 +trick: it actually creates a "radar object",​ whose vertices are the object
 +centers, and lets obj3d do the hard calculations. ​ This will be explained
 + The purpose of the radar is to display where objects are located
 +relative to the person. ​ With a few moments of thought this is seen to
 +be very similar to the "​viewpoint"​ problem. ​ That is, to compute the
 +viewpoint from a particular object, the surrounding objects need to be
 +translated and rotated about the viewpoint object. ​ This is exactly what
 +the obj3d routine CALCVIEW does.  So by dipping into the obj3d center list
 +(after calling CALCVIEW) we can get all the relative locations automatically.
 + The purpose, however, is to _display_ the relative object locations,
 +not merely to compute them.  Imagine, for a moment, a cube, with us located
 +at the center of the cube (this is, after all, all a 3d world really is).
 +Then imagine a bunch of dots around the cube, representing the locations of
 +other objects. ​ There'​s our radar display -- now how could we draw something
 +like that on the screen?
 + Well, we said it was contained in a cube.  But drawing a cube is
 +pretty easy -- just specify the vertices of the cube, locate the center out
 +away from the viewer, rotate and project. ​ Well from there it's awfully
 +easy to compute the points inside the cube -- just add them to the list
 +of vertices, and rotate and project them along with the rest of the cube,
 +and then maybe plot them on the screen. ​ And when you get down to it,
 +the vertices of the cube aren't really necessary.
 + The only other thing to realize is that there is an obj3d routine
 +for rotating and projecting points -- RotDraw. ​ So now we have a way of
 +generating a radar display: create a new object, whose vertices are exactly
 +the relative object centers computed by CalcView. ​ Give the object a
 +center coordinate out a little ways from the viewpoint object, and then
 +just rotate, project, and plot the resulting points somewhere on the screen.
 + The radar code is a little tricky, and could surely use some
 +modification,​ so it's worth going through it in some detail. ​ The radar
 +is an actual obj3d object, and is the first object created -- thus it
 +is object number 0.  Recall that CalcView translates and rotates all
 +objects relative to the viewpoint object. ​ Before CalcView is called,
 +the location of the radar object is set to the location of the viewpoint
 +* DrawRadar -- Compute and draw radar
 +* display, by creating a new object whose points are simply the
 +* (scaled) object centers.
 +RADOFF ​  = 24             ;Size of radar
 +RADFLAG ​ DFB #$00         ;​Radar toggle
 +IDENTITY DFB 64,​0,​0 ​      ;​Identity matrix
 +         DFB 0,64,0
 +         DFB 0,0,64
 +* Set radar center to view center
 +SetRadar ​
 +         LDX VOB
 +         JSR SetCurOb
 +         STA TEMP
 +         STY TEMP+1
 +         LDX #00
 +         JSR SetCurOb
 +         STA POINT