Category Archives: retrocoding

Structured C64 BASIC: A Complete Example

Now that we’ve worked through the basic principles, let’s go through a worked example. I don’t work in BASIC much, but the largest program I’ve written that’s pure BASIC is a directory-editing program for disk images, weighing in at about 120 lines of code. It should serve to demonstrate the core principles.

The Problem

When I was organizing some of my projects and other programs into disk images, I kept running into a host of minor irritations endemic to disk work on the C64. My initial list of goals was:

  • The ability to reorder directory entries, including entries that didn’t refer to any files. You see, instead of storing directory entries as a linked list, CBM DOS stored as an array and when a file is created the first “empty” entry is used. That means that if you’ve deleted files, your newly created files appear seemingly randomly in the middle of the listing, instead of at the end. That’s super-annoying so I wanted to be able to move those gaps down to the end of the list.
  • Undelete files.
  • Hide files so that they existed on the disk but didn’t show up in directory listings.
  • Create unloadable delimiter files that did exist in the directory listing, as dividers or headings.

Deletion and renaming were easily managed with ordinary disk commands, so I wasn’t as interested in those. I didn’t need a DOS shell or directory commander—I needed a program that would let me do things to the directory structure that the DOS didn’t provide as commands.

Now, as it turns out, that whole list can be boiled down to three operations:

  • Exchange two entries in the directory listing.
  • Alter the file type of a file in the directory listing.
  • Optionally scan the directory structure and adjust the block allocation map to match it.

This gives us all the operations I want to perform. Any permutation can be built out of an exchange primitive. Deletion and undeletion involve changing the file type to or from “not a file” and then performing the scan for the block structure. Hiding files is accomplished by changing the file type to “not a file” and then not performing the scan, leaving its blocks protected and inaccessible. And delimiter files can be created from any other file by setting their file type to the special delimiter type.

The Initial Drafts

The basic implementation strategy falls out of the considerations above. To do reordering and file type manipulation, I would load all the disk blocks that hold the directory into memory, edit them in place, and then write them all back out at the end. The DOS provides a special “validate” command that does the directory structure scan and block allocation reconciliation. I would also add a “move slot A to slot B” command that performed repeated exchanges, to simplify moving gaps in the directory to the end of the list.

My first draft stored the directory blocks in a two-dimensional array of integers. One array dimension was the disk blocks, and the other was the bytes within them. This worked, and it was relatively simple to implement, working much like our sector-dumper program from last time did. However, it was impossibly slow; exchanging directory entries involved multiple loops that would copy values around, each executing dozens of times. If I wanted to get decent performance out, I’d need to write it in machine code, or create a completely external tool to handle it. Neither of these options appealed to me much.

I then set about trying to implement the core operations more efficiently, which in turn meant changing the data structures. The alterations were pretty drastic.

First, instead of reading and writing every single sector in track 18 (where the directories and allocation maps were stored), I would only read entries in the directory listing itself. These are arranged as a singly linked list. I created an array SN% that stored the order of tracks to write back and a variable NS for the actual number of sectors we read. I then created a parallel array D% to hold “dirty bits”—these all started as zeroes and got a 1 written to them if I ever altered anything in that relevant sector. At the end of the program, I would only need to write out those sectors that I actually changed. In the extreme this could result in me doing seventeen times less work.

That leaves the contents of the directory entries themselves. These were 30-byte data structures that my exchange-directory-entries routine would be copying back and forth as blocks. Instead of copying blocks of array entries back and forth, I kept each directory entry as a string and simply edited them or swapped references around as needed. These entries were stored in an array named DE$.

Finally, to keep myself honest, I added a couple of variables to check to see if I’d ever deleted or undeleted any files to remind me whether or not I should validate the disk.

All of these changes were a fairly significant rewrite. I did the implementation in C64List (which was also the initial import into Github, and while I made some basic efforts to make the code look nice when LISTed, I was pretty lackadaisical about it. Code flow jumped all over the place, modules were huge and all had differently-sized ranges of lines assigned to them. You couldn’t ever really type LIST with a line range and have any idea of what you would get.

So as written, in the modern era, with modern cross-development tools, this draft was basically fine. It would not have been fine in the 1980s. It would have been extremely difficult to understand or modify without printing out the entire source code and studying it as hardcopy.

So, before I do a walkthrough of the program itself, let’s fix that.

Structuring Constraints

For the purposes of this project, I set the strictest set constraints that I’d commonly seen used in programs published with the intent of being typed in by the end user:

  • The code will be organized into blocks, each of which is assigned one hundred line numbers. Block 0 is lines 0-99, block 1 is lines 100-199, and so on.
  • Line 0 of each block (the line that is an even multiple of 100) will be a comment explaining what the block does.
  • GOTO statements will only ever target locations within the same block.
  • GOSUB statements will always target the beginning of a block and nowhere else.
  • Blocks will be self-contained: any block you enter with GOSUB will be exited with RETURN. (The main program is a special case, and we’ll cover that below.)
  • A reader should be able to type the code verbatim into a C64 without using input abbreviations or tricks. (The C64 line editor had a limit of 80 characters, but BASIC lines could be up to 255 characters long, and this limit wasn’t enforced until after all the keywords had been compressed to a single byte. Clever users could use special keyboard abbreviations to make lines that were over 80 bytes long when LISTed, or use program generators to produce lines that were much longer. This constraint demands that we structure the code so that this is never necessary.)
  • A block will fit on the screen when LISTed. There needs to be room for both the intial comment and the reiterated BASIC prompt after the LIST completes.

I then added some additional structure that made sense for this program.

  • Blocks would be further grouped into megablocks of ten blocks each. These would comprise related blocks that cooperated to perform some task.
  • Megablock 0 would hold the main program and general utilities. Any GOSUB statement that called into a routine would either be calling a block in megablock 0, a block within their own megablock, or the first block in some other megablock (that is, an even multiple of 1000).
  • Block 0 would be the entire main program. The only things it would do would be initialize globals and call into the other megablocks, and the last line in the block would be END. Blocks 1 through 9 would be reserved for general-purpose routines. (This was uncommon. Generally, 9 blocks wouldn’t be enough for your general purpose routines, and so a set of megablocks near the end of the program would be used for them. Likewise the main program would be kept near the end instead of the beginning. Extremely low-numbered blocks would hold routines that needed to run very quickly, taking advantage of the way backwards branches are faster at lower line numbers. Our directory editor is I/O bound basically all the time and doesn’t care about this.)

Of all of these restrictions, only the requirement that each block fit on a C64 screen when LISTed made my task harder. The others provided a structure that made the outlining and implementation simpler.

Structure is your friend. That’s not unique to BASIC, either.

Let’s take a walk through the final program.

Continue reading


C64 BASIC: Disk I/O

It’s time to start actually doing floppy disk I/O in C64 BASIC. This was a little tricker than one might like, because the Programmer’s Reference Guide only covers the most generic forms of the OPEN command, and the drives’ own manuals were infamously opaque. Still, there’s enough information in them to let us do the work.

We’ll start with the basics; we’ll create a data file and put some text in it, then read it back out. Here’s a program that writes the text:

   10 OPEN 1,8,2,"HELLO WORLD,S"
   40 CLOSE 1

And here’s the very similar program to read it back out:

   10 OPEN 1,8,2,"HELLO WORLD,S"
   20 INPUT#1,A$:PRINT A$
   30 IF ST=0 THEN 20
   40 CLOSE 1

OPEN takes four arguments, normally. The first argument (the 1) is BASIC’s name for this open file. The second is the device number, which is 8 as usual for the first floppy drive. The third is specific to the device—disk drives let us use any number from 2 to 14 for this—and it serves a very similar purpose to the first argument. For now it’s arbitrary, but it will become relevant later when we are giving the disk commands directly. We go with 2 as the lowest legal value. Lastly, the final argument is the filename and any optional parameters. The “,S” at the end tells us that this is a SEQuential file.

ST is a special variable that is set whenever we do any I/O. This is 0 on success, and various bits are set if the operation fails in various ways. The result 2, for instance, means the file was not found, and as we can see…


64 is End of File. Unlike feof() in C, though, this is is set on the last legal read, not the first illegal one. That makes the loop logic look a bit different than we might otherwise expect.

To do anything more fun, though, we’ll need to work at the byte level, not the formatted BASIC level. We’ll do that below the fold.

Continue reading

C64 BASIC: Performance Tuning

There’s a bunch of heuristics and rules for getting BASIC code to run slightly faster. Some of them even actually help. The core principles, though, are that command interpretation is work and so you want to do as little of it as possible. This has some occasionally surprising side effects. That means that to be sure what’s going on you’ll need to do some measurement.

Our Sample Task

For our initial experiments, we will put the checkerboard graphic at a hundred random places on the screen. Now, while Sinclair BASIC would let us place the cursor with a command like

PRINT AT INT (RND*22),INT (RND*32);"{H}";

we don’t have PRINT AT in Commodore BASIC. The KERNAL, however, does have a routine called PLOT at $FFF0 which will position the cursor for us. Thus, here is our first cut at the routine, in petcat format:

  10 print "{clr}{gry3}";
  20 s=ti
  30 for i=1 to 100
  40 y=int(rnd(1)*24):x=int(rnd(1)*40)
  50 poke 781,y:poke782,x:poke783,0:sys 65520:print"{CBM-+}";
  60 next i
  70 e=ti
  80 print "{home}{lblu}total time:";(e-s)/60

UPDATE, 10 Sep 2017: This listing has been updated to fix the POKE to 783 (clearing the status flags). The original post zeroed 780 instead, which cleared the accumulator. The PLOT command requires the carry flag to be clear in order to move the cursor. (Otherwise it reads the cursor position instead of setting it.) This edit does not alter the timing information.

We time our program by consulting the TI variable. This is initialized to 0 at power-on and increments every time the clock-tick IRQ is processed. That’s 60 times per second under normal operation, and 0 times per second when we’ve disabled interrupts. We don’t have to worry about interrupts this time, so TI works fine and will give us frame-level accuracy.


Running this program shows an average runtime of 4.47 seconds. That will be our baseline.

Lowering Instruction Count

Our inner loop is executing seven full BASIC commands per loop, all to perform cursor positioning. This isn’t even the usual way people did cursor positioning, though; usually they would exploit the way you could put cursor-move characters into strings. String operations permit a lot of work to be done and can be surprisingly fast. If we add a new line and then replace line 50:

  15 y$="{home}{24 down}"
  50 print left$(y$,y+1);tab(x);"{CBM-+}";

This speeds up the average runtime to 3.87 seconds, which is nearly 15% faster. That’s not bad at all, and clever use of LEFT$ and RIGHT$ can be used to get reasonable horizontal scrolling effects in pure BASIC.

Continue reading

C64 BASIC: Coexisting With Non-BASIC Data

One of the most defining aspects of C64 BASIC is that there is almost no high-level support for the various features the hardware provides. This means that you can usually tell when a BASIC program is for the Commodore because the vast majority of its work is doing memory-mapped I/O with the POKE and PEEK statements. This mixes very uneasily with the requirements of an interpreted, garbage-collected language like BASIC. The VIC-II graphics chip, in particular, tends to want to deal with continuous chunks of memory at well-defined locations. In machine code programs, we can simply not put our own code or data in those locations, but with a higher-level language, we need to somehow ensure that the language runtime won’t do so. (This problem is in no way unique to BASIC, but BASIC shares the dilemma.)

Now, there’s some regions of memory that BASIC and the KERNAL beneath it guarantee won’t be touched. There are four important regions there:

  1. $C000$CFFF (49152 to 53247 decimal) is 4 kilobytes of contiguous space that are free for application use. Machine language programs that expand BASIC’s capabilities do well here.
  2. $033C$03FB (828-1019 decimal) is technically the casette I/O buffer, but if you aren’t in the middle of a tape operation it won’t be touched. It’s also part of the default 16KB of memory that the VIC-II can see, so it’s a good place to stuff sprites or very small machine language support routines.
  3. $030C$030E (780-782 decimal) let BASIC affect the registers at the start of a SYS command. It’s more common to dedicate some memory elsewhere for communication with machine language routines, but if you want to make a syscall direct from BASIC, these locations will let you do that.
  4. $FB$FE (251-254 decimal) are 4 bytes of zero page that can be used for pointers by machine language routines without interfering with any BASIC or KERNAL operations. This is important because you must indirect through the zero page any time you’re doing pointer-like operations, and you’re going to want to do that a lot.

None of these are suitable for storing custom character sets—it’s very inconvenient and slow to set up the VIC-II to use the $C000 range for text and still have things like PRINT and INPUT work—and none of them are even minimally sufficient for bitmaps. If we want to do anything serious with the VIC-II, we’ll have to reconfigure BASIC’s usage of memory.

Continue reading

Making 8-Bit BASIC Actually Work

Time for a new series of posts on a retro project, I think.

I’m going to work through what it takes to design and implement actually useful software in the 8-bit BASICs generally, and C64 specifically. Standards and mores in program design have evolved so far since the early 1980s that assembly language programming is actually more natural and familiar to a modern developer. So this post will cover implementation discipline, how to get as much out of the system as you can without mixing in machine language, how best to mix in machine language, how to make the BASIC runtime coexist peacefully with various distributions of memory, and tools to minimize the pain of implementation.

Of course, that does leave the most obvious question…

Why Anyone Would Do This In The First Place

There is very little that you can do in BASIC that isn’t better done either in machine code or some compiled language, and most of what BASIC would do that you wouldn’t do in machine code you’d instead do in a language like Python on a modern machine. (Indeed, if through some bizarre set of circumstances you have wound up on this blog hoping to learn how to program, and have an old computer emulator with its BASIC system waiting to be your environment, please, please, consider picking up Python instead. It has a very BASIC-like interaction model, it will grow with you better, and as you grow to work with more of its power you will learn things that will translate at least in broad strokes to the other development systems of today.)

The main reason you’d write a program in BASIC these days is because you want to prototype something that doesn’t require a lot of speed, or that does require something like floating point to work the way you want. (Back in days of yore, it was very common for demo writers to use small BASIC programs to generate their data tables. In my own programs here, I’ve been using Python for that, outside of the emulated system. But then, back in days of yore, there wasn’t an “outside of the emulated system.”) The requirement for floating point isn’t idle; while the general outlines of the floating point routines in the BASIC ROM were well-documented from very early on, it’s pretty tricky to actually use them in your own machine code without corrupting some internal state and getting wildly incorrect answers out of it. (I discussed this a bit back when I discussed my Hammurabi port—it’s not obvious that outside of working as a test case for my BASIC floating point integration it gained much from being in assembly language.)

But if you do need to run on an 8-bit system and you also need floating point, BASIC is your easiest and best bet to get it. Even the premier open-source C compiler for the 6502 won’t touch floating point.

Why I Wound Up Doing This

Last year I had done some work hoping to put together some programs that would do some useful work with diskette I/O. In particular, I actually wanted to be able to reorder and edit directory entries without having to hex-edit the directory blocks, and the Windows tools I’d found for doing this were all distastefully intrusive in terms of what they wanted to do to the system as a whole.

That project fell a bit by the wayside, but one of the reasons it did fall by the wayside was that my initial directory-editor prototype in BASIC wound up being good enough to do the job entirely on its own.

Continue reading

4th of July Special: Type-In Programs

One of the other things I’ve been doing lately is fiddling around a bit with old type-in programs. This has been amusing mostly because it’s a major part of how I learned to program in the first place. It instilled, by accident, much of the discipline that modern tutorials like Learn Python the Hard Way hope to instill on purpose: patience, precision in typing and reading, and a certain sense that inexplicable commands we echoed would at some point make sense.

But there’s a certain art that the modern courses lack, because the modern courses are explicitly instructional. Old type-ins needed to do something cool or useful to be worth the effort of typing in. The needed to be simple enough to type in that you either would not make errors, or the errors would become clear quickly. And, of course, they had to be actually correct. I must admit that a distressing number of actual published listings failed this last criterion, and that this is a major part of how I learned to debug.

The gold standard for type-in software on the Commodore 64, now that we can look over the era, is Compute!’s Gazette, collected in full on the Internet Archive. Sufficiently so, in fact, that I consider it to be very nearly out of scope; many programs published here could compete with full commercial offerings, and the BASIC listings were accompanied by a program that would compute a checksum of every line you typed in. It also included a program called “MLX”, which was a program for entering machine code programs as a raw series of bytes, with a checksum byte of every line you typed in. Compute! was very confident indeed in the correctness of what was printed. Those who learned programming techniques from Compute! learned it more from the articles surrounding the programs they typed in rather than the programs themselves.

I didn’t have the Gazette as a kid, though. Scholastic, Inc. published its own computer magazine with type-ins and article about games and hardware called Family Computing and offered subscriptions as part of its book club for elementary school students. Its programs were often simpler, since it was targeting a younger audience with many fewer needs, and also because it supported many different home systems. Most programs had to have six or more editions, so that cut heavily into the size of any individual program. It did not include any automatic typing checkers like Compute! did, though, so it often had to rely on other mechanisms. Usually there weren’t any, but programs with very large sequences of DATA statements often would compute checksums along the way and have the program error out if there were problems. (This is sound practice—Compute! itself used it to make sure that the proofreader program was properly entered.) For the most part, though, you just had to have sharp eyes and fingers as a young typist.

When I was young, I mostly skipped over the programs that were 90% DATA statements by weight. But I’m a faster and more accurate typist these days, and so when this issue caught my eye, I decided to enter in its flagship program:


The flagship program in this issue was a program that would draw a rendering of the Statue of Liberty and then play The Star-Spangled Banner. Unusually for Family Computing, this program actually shared most of its source code across all platforms. (This shared code was a gigantic block of DATA statements that stored vector information about its image of the Statue of Liberty in a platform-independent manner.) Then each platform had its own code for interpreting the vector information and then playing the music. For the C64, this was another enormous block of DATA statements. Most of that was sound data for the SID chip, but there was also a small machine language routine that it used to clear the bitmap display in a reasonable amount of time, POKEd into memory and then SYSed at program start.

Everything else was handled in BASIC, including this particularly impressive little block of code:

   80 X=A:Y=B:GOSUB 3000
   90 F=0:V1=ABS(C-A):V2=ABS(D-B):S1=SGN(C-A):S2=SGN(D-B)
  100 IF X=C AND Y=D THEN 150
  110 F1=F+V1:F2=F-V2
  120 IF ABS(F1)>=ABS(F2) THEN F=F2:X=X+S1:GOTO 140
  130 F=F1:Y=Y+S2
  140 GOSUB 3000:GOTO 100

The subroutine at line 3000 plots a single point on the bitmap display. That detail aside, this handful of lines is a complete implementation of Bresenham’s line algorithm. That is a very impressive feat of compact and effective code.


“Effective” might be too strong a term. The screen clears nice and fast, but the full display takes over ten minutes to actually draw, and it looks awful:


To add insult to injury, the rendititon of The Star Spangled Banner only uses one of the SID’s three voices.

Perusing the code for the other platforms, it becomes clear that the IBM PCjr edition was actually the one with the highest production values. It—like all BASICs that supported it—supplemented the vector graphics with three strategically chosen flood fills. It picked better colors for the display—green on grey instead of white on blue. And uniquely amongst the implementations, it provided a three-voice arrangement of the anthem.

There’s nothing here the C64 couldn’t do, if only we had a decent bitmap library and a minimally acceptable 3-voice music driver. Fortunately, we have just that. All I need to do is convert the string-based PCjr music code into numerical commands for my music driver, and implement the flood fill. The rest is just minor details.

The difference flood fill makes, though, is absolutely night and day. The garbled mess we saw above conceals an incredibly effective chiaroscuro-like effect:


That’s much more like it. Below the fold, I discuss what went into making the port.

Continue reading

Demo Release: Coast to Coast

I recently collaborated with the musician Nick Vivid on a Commodore 64 demo named “Coast to Coast” that was presented at @-Party 2017. It seems to be traditional to do writeups of one’s demos after the fact, but even if it weren’t, I’d be doing one. I’ve held off until the actual first presentation, though.

The Effects Used

The core techniques used here will start out looking pretty straightforward. There’s some text scrolling at the bottom of the screen, and the main part of the screen is consumed by text scrolling down at one pixel per frame. Then the 8 sprites are being used to cover the top of the screen with an additional marquee of scrolling text. (It take all 8 sprites to do this while keeping each letter static within the sprite; X-expanded sprites are 48 pixels wide, and the screen is 320 pixels across. So you need 368 pixels of space to scroll over the screen, but 7 only gives you 336.)

There’s nothing at all exciting about the visuals on this effect if you’ve been following this blog, but if you’ve been following this blog you also know that single-line splitscreen is a bit harder than it looks. Back in 2015 I didn’t really deal with the problem of scrolling the color RAM, either. I don’t address that here, either; I was just careful about my choices of colors.

Things get more fun about 20 seconds in, when I reveal that I wasn’t actually using the technique I just linked:


At this point I begin scrolling graphics up the screen as well as a down-scrolling background. Part of the reason I stuck to the simple sprite text scrolling was to show that I wasn’t doing this the easy way with sprites; this is a purely textual effect. The trick here is that while it looks like I’m scrolling one scanline down each frame, I’m actually using a much simpler technique to scroll up two lines every frame. The downscroll effect is created by combining that with rotating the character graphics down three lines each frame.

Of course, to get the objects that move up to actually move up, I also had to “coarse scroll” the screen up by one character row every four frames. This was handled via the traditional C64 technique of double-buffering the screen, spending the intervening four frames copying over all the characters, and then updating the video-matrix pointer as needed. I had a little jump table that would let me generate the map I was scrolling through a line at a time, and threading values through that jump table turned into a little miniature scripting language. So that was fun.

I did end up being a little more clever than I perhaps needed to be there. I was writing this while Nick Vivid was working on his music, and I didn’t know how expensive the music update would be each frame. I thus ended up restricting my coarse-scroll effects to the bottom line and then a selected (and variable) column of characters on the screen itself. In the end, I think this probably was overkill, but better to do the work and not need it than the other way around.

Speaking of the music, I had very little to do with that at all. He worked on his own with his musicmaking tools, produced a very nice piece, and I asked him if he could export it so that it would run only using RAM over location $3FFF. (The graphics and logic all live completely within the first 16KB, even uncompressed, even at runtime.) That was easily arranged and all I needed to do from there on out was copy it into place and then call into it at appropriate times.

With all the pieces together, all I did after that was run it through the PuCrunch system. This essentially turns a C64 binary into a self-decompressing, self-running archive. The home page has been gone for awhile, and while the code is still out there on the Internet, The Wayback Machine is the only way I can link the original site. PuCrunch was unreasonably effective at compressing this program; it dropped in size from about 12KB to less than 6.

Challenges, Compromises, and Goofs

Graphically, the biggest issue that I had was that I really, really did not want to have to wrangle color memory with things scrolling in three directions at once. I set multicolor text mode once, filled the entire screen with a single color, and then designed everything else around that. This meant that my scrolltext color also had to be in the range 8-15, and also had to be exactly the color 8 above whichever color I used for the text’s per-cell color. I wasn’t allowing myself any slack in the display, so that means (as we saw in the original vertical scrolling articles) that the scroll text’s color values would be shared with the last row of displayed graphical cells. Our change in scroll values would force a re-read, but the badline would come too quickly for us to do any reassignment. So I stuck with solid colors, and red/pink produced a reasonably nice display.

Conceptually the biggest problem that I had was that just scrolling stuff is kind of boring. Since one of the other goals of the demo was to be the invite for SynchroNY 2018, and that particular party takes place on a train that runs from NYC to Montreal, the train theme was set in pretty early. Discussions with Nick Vivid while we were talking about progress sort of developed the idea of it being about traveling in general, and once “remote entry to @-Party” was decided upon as the best venue to release in, that also brought in the notion of travel as a theme. The demo was being written, roughly, between SF and NYC, and about an event that would travel from NYC to Montreal. That’s when I got the idea of running the city names as extra text from, and it wasn’t too hard to hit enough major cities on one route to fill the time the music took up.

Unfortunately, those cities aren’t on an actual train route. If you want to take a train from the West Coast to NYC, you need to start in Los Angeles. I used Interstate 80’s route to select my interstitial cities.

An additional goof, which fortunately was caught before release, was that I ended up bungling the copying of the music code into place, and as a result a full third of it was originally missing. I think the three most devastating bugs that emerged during development and testing all ultimately were single-byte errors.


At the actual compo, the demo placed second, coming in behind an extremely elaborate demo for the Intellivision that is the most impressive work I’ve seen on the platform. Comments on it after the fact can be summarized as “not bad for a first effort”, which is about where it belongs. I’m not using any incredibly advanced techniques here and I’m not particularly talented as a graphician, either—the part of this project I like the most is that with only one extremely arguable exception, I’m getting an “impossible”-looking effect out of this without actually pushing the system’s limits. (The exception is that I do have to compensate for some of the VIC-II’s slightly inconsistent behavior surrounding vertical split-scrolling. I think that can count as “compensating for a quirk” rather than “pushing the envelope.”) This also let me put a lot of the small, independent routines I’d developed over the years into a single coherent program, and that was nice too.


Here is the disk image that was sent to the competition.