
Sign up to save your podcasts
Or


MEGA65 Summer Filehost Roundup. Dan’s MEGA65 Digest for October 2026.
This month we’re celebrating four years of Dan’s MEGA65 Digest, the five year anniversary of the original MEGA65 pre-orders, and the 500th file uploaded to Filehost!
I’ve been traveling a lot in the last month, with VCF West in California and VCF Midwest in Illinois. I drove my Mini Cooper full of MEGA65 hardware all across the country, enjoying the open road and America’s best truck stop diners. I returned home to a huge stack of work that I’m still catching up on.
Meanwhile, there has been a ton of activity on MEGA65 software projects, alternate cores, and genAI experiments, so much that I haven’t been able to keep up with it. My ability to digest is strained by all of this goodness! Let’s spend this issue doing a project roundup based on recent announcements and uploads.
I have not had a chance to test all of these, so just pick what inspires you and give it a go.
Zak McKracken and the Alien Mindbenders, running on the MEGA65.
kibo and M3wP have done it again! MEGASPUTM, their implementation of the LucasArts graphic adventure game engine for the MEGA65,
See the MEGASPUTM User’s Guide for instructions on setting up the data files with the included utility program. See also the source code repo. I learned a lot from kibo’s MEGASPUTM code that informed my Digest series on developing large programs in C.
Simon the Sorcerer, running on the MEGA65.
wombat has done their own ScummVM-based graphic adventure engine port of the Amiga CD32 version of Simon the Sorcerer. Get the engine on Filehost, see the Github repo, and find the original Amiga CD32 version of the game to extract the data files.
Shogun, a text adventure game with graphic by Infocom, running on Ozmoo for the MEGA65.
Infocom, the venerable makers of text adventure games in the 1980s, built all of their games on an in-house engine, retroactively named the “Z-machine.” The Z-machine evolved its game format over several versions, with most Infocom releases using versions 3 and 5. With version 6, Infocom added support for graphical images and atmospheric sound effects. (Text adventures were the subject of the October 2022 Digest.)
The MEGA65 version of Ozmoo, the modern Z-machine interpreter, now supports version 6 games. The creators of Ozmoo, Fredrik Ramsberg and Johan Berntsson, have uploaded some games for the new version, including an Infocom Z6 game bundle with Arthur, Journey, Shogun, and Zork Zero, and two original games by Johan: Wyrmward, a wizard-ing adventure, and Vanyar, a port of a game Johan wrote back in 1986.
Excited to have such a great Z-machine for the MEGA65, and to have so many great vintage and modern text games to play!
MyLISP65, a LISP-1 interpreter for the MEGA65.
The new releases keep coming! Here are more MEGA65 programs to try!
Fenix has released MyLISP65, a LISP-1 interpreter for the MEGA65. This is a port of MyLISP for the Sinclair QL, derived from a recent port of the interpreter from Pascal to C. This package includes some impressively nerdy math packages to play with. See the Github repo, as well as the Discord thread.
TwoBitRetro continues to make major improvements to Pascal65, an on-device Pascal compiler and IDE. Download the latest.
MegaPad by piramania is a featureful document editor, using a Markdown compatible file format for inline text formatting. Menus, mouse support, and characters and keyboard layouts for multiple languages.
piramania has posted several games this summer as well: Quackout, Towering Rescue, Columns 65, LeMans, Labyrinth, and MegaQix.
KnockCSV by Frogboon is a Comma Separated Values (CSV) columnar data editor.
RTC Tool by Warlock dives deep into the Real Time Clock hardware. It can set the date and time, identify and query the specific model of RTC hardware, and can even contact an NTP time server over the Internet to synchronize the clock when the MEGA65 boots.
Speaking of clocks, fredrickr has a nice BASIC Clock that displays the time, and serves as a nice BASIC coding example.
Mega Adventure Creator by NeonKnight is a toolkit for implementing adventure games in BASIC 65, with menus and NCM still graphics. Filehost, Github.
Chessoban by lochmana, is a Sokoban variant with an original concept.
dugout by bushnrvn, is a game which they describe as “A spooky baseball-themed digging game.”
Search and Rescue by johan, a voxel flight sim.
Tiwaz has been using genAI to explore the capabilities of the MEGA65, and has released eleven titles in just four weeks: GOGOAstra, a demo with music and visualization; IONDRIFT, an Asteroids-like space shooter; Rockstorm, another Asteroids-like shooter; NEON WARDEN, a platformer with a cyberpunk theme; AstraAmp, a SID player; SHIPYARD, a spaceship generator based on a small neural network; TURTLE65, a turtle graphics environment; TINYBRAIN65, a small local interactive text LLM demo that runs directly on the MEGA65; mesh65, a 3D polygon rendering demo; Space commander, a Wing Commander space battle game; and Space particles, a music and video effects demo to accompany Space commander.
Net Tools by silent-command (idiolect on Discord), a collection of Internet protocol tools, including SSH, FTP/SFTP, IRC, Gopher, and a telnet BBS client.
The Summer of Cores continues with many core releases in the last two months, from major new projects to minor releases. I haven’t had the time to try them all, so I’ll just briefly summarize the ones the authors have mentioned in the Discord server’s announcements channel:
As always, check cores.mega65.org for updated documentation on alternate cores.
So many alternate FPGA cores for the MEGA65 hardware, but only eight core flash memory slots! What is an alternate core enthusiast to do?
Paul has an idea: a PicoPi-based core loading device that stores MEGA65 FPGA cores on a separate SD card and loads them via the JTAG port, allowing for more core storage than the eight built-in core slots. This allows you to maintain a large core library and load them quickly and easily, without having to install them to flash memory.
The JTAG port is a set of pins on the mainboard used for FPGA cross-development. They allow another device to dump a “bitstream” directly into the FPGA, similar to how a core is loaded from flash memory during MEGA65 start-up. The connector also includes serial (UART) communication pins, so the MEGA65 can communicate with the PicoPi to browse and request bitstreams. No need for another computer to control the PicoPi, the MEGA65 can provide its own loader interface.
Fun project! See Paul’s blog for more project ideas—including recent updates on the MEGAphone mobile phone project.
That should be enough to keep us busy until the next Digest feature article. I have something half-written, and if I finish it by the end of October I’ll post it as a bonus issue.
With the increased community activity, I can no longer test and feature everything that gets uploaded to Filehost in this Digest. I’m concerned that if I try to feature every project, I might accidentally skip over someone’s hard-earned victory. (I’m certain I missed something cool just in this roundup!) So for future issues, I’m going to scale back on “Featured Files.” I will continue to post MEGA65 project news, and write feature articles and programming tutorials. I will also feature the occasional project that I think you might find inspiring for your own MEGA65 adventures.
My goal with the Digest is to get you—yes you!—interested in programming, and learning about computers, software design, game design, art, music, and all of the adjacent crafts and skills of our hobby—electronics! computer science! historical preservation!—using computing as a creative medium. Hopefully by doubling down on that focus, I can keep the workload reasonable for myself, and still offer something each month that gives you more to explore with your MEGA65.
If you’d like a more comprehensive regular rundown of all the stuff people are making, I recommend these sources:
If you have a project that you’d like to see featured here in the Digest, let me know! I’m always excited about what everyone is learning at every level, from beginners to experts. By sharing what we’re learning from our projects, we inspire others to try something new.
More soon!
— Dan
Calypsi C, part 3: Making large programs. Dan’s MEGA65 Digest for August 2026.
Previously, on The Digest… we were looking at ways to fit the original Unix Rogue dungeon crawler game into the MEGA65 memory system using the Calypsi C compiler and a boot loader. The memory map we came up with keeps the KERNAL in memory, so the program can use a KERNAL-based implementation of the C standard library without fuss, in case we needed it. That meant storing both code and data in banks 0 and 1, and using the 45GS02 MAP register to point 24 KB of the 16-bit address space at one of five regions of the 28-bit address space. We got as far as teaching Calypsi how to place code and data into these regions upon request, compiled as if visible at the 16-bit addresses, as well as how to output each region as a separate file that could be loaded into memory by a boot loader program.
The 24 KB window works like a giant spotlight pointed at one region at a time. We need a mechanism to move that spotlight when code in one region wants to access code or data in another region. Calypsi doesn’t know how to do this when generating code for function calls, so we also need to revise the Rogue code to move the spotlight at appropriate times. Finally, we need to decide how to organize all of the pieces of Rogue into the regions.
I’ve been working with Calypsi’s author hth313 while writing this series, and he has a new version with improvements based on those discussions. Go get Calypsi 5.18 for your platform. A bit of code in this Digest depends on a fix in the most recent version.
Let’s see how far we get!
Trenz Electronic has temporarily paused shipments of the MEGA65 to the EU except for Austria and France. A new EU regulation on packaging waste has gone into effect, and many small retailers are pausing shipments while they figure out how to comply.
The European Union Packaging and Packaging Waste Regulation (PPWR) seeks to reduce packaging waste, a serious problem in the EU that needs some kind of solution. It covers all packaging for products made for sale in EU member states, including imports. See the European Commission website, as well as this article for more information.
Several alternate core projects have been inspiring each other to make progress this summer. I try to only introduce projects in this space and not report on every incremental improvement, but it’s worth doing a round-up, especially considering the growing interest in the MEGA65 as a multi-core platform. As always, see Boris’s MEGA65 alternate cores website (cores.mega65.org) for up-to-date information.
The Amiga 500 OCS core by sy2002 now has a stable version 1 release, with support for a single virtual disk drive, analog CRT displays, and the ability to disable slow RAM. sy2002 is already working on a version 2, with v2 alpha 8 (Discord link) being the latest test release as of this writing. This test release includes read-only physical floppy disk support with the built-in disk drive, up to three virtual drives, and improved support for some disk copy protection. Testers are reporting good results, including the ability to use X-Copy to copy original physical disks to ADF disk images. Note that the core does not yet have the ability to create ADF images on the SD card, so you’ll have to use a PC tool to make some empty images to play with. Be sure to follow along in the Discord when testing alpha releases, and provide feedback.
MJoergen and sy2002 are making improvements to the C64 core with two exciting projects: physical internal 1581 floppy drive support, and a built-in RR-Net-compatible network interface based on the MEGA65’s ethernet port. You can test out version alpha 19-X2 (Discord link) to play along. See Sy’s RR-Net article (Discord link to a PDF attachment) for more information.
sy2002 has also updated the Game Boy Color core to v1.0 (Filehost link) to support the latest R6 mainboards and use the latest MiSTer2MEGA framework. The Game Boy Color core was a very early alternate core, and it’s exciting to see this update bring it forward for the latest hardware.
muse5150 is back with an exciting new vintage desktop computer core. Mega //e is an Apple IIe core, with support for .nib disk images, Commodore-style joysticks and Amiga mice, and various video display options. Caution: do not connect an Apple II mouse to your MEGA65, they are not electronically compatible. See also the Mega //e Github repo. Oh, and muse is also working on an Atari 800 core (Discord, Github).
Fenix has launched a Sinclair QL core (Github link). The QL was based on the Motorola 68008 CPU at 7.5 MHz, with a multitasking operating system called QDOS. This version 1.0 release supports .mdv disk images, selectable memory configurations and CPU speed.
Last but not least, Paolo Pisati (piso) has started an MSX1 core (Discord link), based on the MiSTer core. See the Github repo. The current test version supports PAL video, cartridge ROMs, and Commodore joysticks.
Keep up the good work, everyone!
A quick reminder that I’ll be at the Vintage Computer Festival Midwest in Schaumburg, Illinois, USA, September 12-13. It’s one of the biggest volunteer-run vintage computer shows in the United States. Do stop by if you have a chance.
VCF West was a blast. I shared a table area with Garry Kitchen and David Crane, and I got to chat with them about GameMaker, Activision, and 6502 assembly language. Steve Wozniak was on stage telling Apple I stories. I showed off Roguecraft and the Amiga core to tons of people. And I brought home too many books, as always. Good times!
I know the last few Digest feature articles have felt like a lot of material that seems specific to Rogue and Calypsi, so I want to take a moment to review why this is important. These are generally useful design patterns for large MEGA65 programs.
The 32 KB PRG. The DLOAD command can load a PRG up to 53 KB in size, into bank 0 starting at address $2001. This is sufficient for BASIC programs up to 53 KB. For PRGs that contain a BASIC launcher and machine code (assembly language, compiled C code), the default memory map invoked by the SYS command only allows the CPU to see 40 KB of this as machine code. The default Calypsi linker config limits this to 32 KB, which can be amended to use the remaining 8 KB for the heap and soft-stack if needed. The KERNAL remains fully available to the program in this MAP.
Short programs and far data. Calypsi uses 16-bit addressing for code, heap, stack, and variables not flagged as “far” data (the __far qualifier). A program that keeps all of its code in the first 32 KB could use upper memory for data, in several ways. One way is to amend the linker config with “far” sections, and tell Calypsi to put specific variables there with the __far qualifier. Another way is for the program to manage upper memory directly, using __far pointers and addresses. A third way—which we haven’t properly introduced yet in the Digest and I’ve been meaning to get to it—is to use the DMA feature of the CPU to copy upper memory into lower memory as needed. The program might not even need to manipulate this memory much if it’s just loading in graphics or sound data, to be pointed at by hardware registers. Such a program can still be a single PRG file, and can still use the KERNAL at any time.
Multi-region programs and boot loaders. When machine code outgrows the 32 KB, the next step up is the strategy we discussed previously. KERNAL code stays visible in the upper half of the 16-bit address space (MAPHI), KERNAL variables stay visible in the lower half (MAPLO), and DOS variables remain unmolested in bank 1. There’s room for the heap and soft-stack in the upper half of bank 0. That leaves a 24 KB region of the lower half, $2000-$7FFF, that can be pointed to any region in memory using the MAP register. This requires manually organizing the program into 24 KB regions, and using a technique for transitioning code execution between regions that we’ll discuss in this Digest. It also requires a separate bootstrap program that the user DLOADs and RUNs to pull all of the regions into memory from separate files, and launch the program.
KERNAL-less full control. If a program doesn’t need the KERNAL, or only needs it under specific circumstances, the program can take over the entire 16-bit address space and do whatever it wants. It still has to comply with a few requirements of the CPU, specifically the placement of the base page (“zero page”) and CPU stack, and the management of interrupt handlers, which the KERNAL does for us in the other schemes. The program could even switch back to a KERNAL-compatible MAP temporarily to use its features, such as accessing disks, as long as it’s careful to stash and restore KERNAL/DOS variable space and meet other pre-conditions. More control over 16-bit address space gives the program more options for efficiency and speed, with less time spent on MAP switching and far data access.
Rogue could be a KERNAL-optional program. It only needs disk access when saving and restoring the game, or updating the high score table. But I don’t want to make the first version too complicated—unless I have to.
We’ve seen how to convince Calypsi to put code and data into different regions, using the #pragma statement. But all this does is change how the output of the compiler is organized. The code that Calypsi generates for calling a function simply uses the 16-bit address assigned to the fragment in the region. If a function in one region calls a function in another region, we need to add code to the program that changes the MAP before the call, and changes it back afterward.
Consider a simple C function, and another function that calls it:
If we compiled this program in Calypsi… well, it’ll outsmart us and generate machine code for a single function that stores the number 21, pre-calculated by the optimizer. For this thought experiment, imagine that optimization is turned off, or that this otherwise doesn’t happen.
When the compiler sees times(7, 3) in the definition for main(), it produces code that performs these steps:
One of the biggest tasks of the compiler is to figure out the optimal code to perform these steps, in a way that works for all callers of the times function. In most cases, the compiler puts arguments in the zero page, then adds instructions that move them around to meet the expectations of the function being called. If necessary, it’ll put arguments on the C soft stack. The code that it generates for the times subroutine knows where to find its arguments, and all callers comply with the decisions that the compiler has made.
The address of the times subroutine is determined by the linker. The compiler generates object code that leaves the address of the jsr instruction blank, to be filled in later. In this example, the compiler assigns both functions to the same section, and the linker puts them next to each other in memory at distinct 16-bit addresses.
Now imagine that we’re using our regional memory system, and the functions are in two separate regions.
The #pragma clang section directives tell the compiler to generate the object code for each function earmarked for different sections. When the linker goes to place the fragment the compiler generated for the times function, it consults our mega65-crogue.scm file and sees that the r2Text section starts at address $2000, and so wires up the times subroutine to be at that address. When the linker places the main function, it sees the compiler has requested the r0Text section, which is also configured to start at address $2000. So the linker wires up main as if it lives at $2000.
Obviously, we wanted this to happen. These subroutines will actually reside in different regions of memory, and we want to add a step to move the MAPLO window from region 0 to region 2 when main calls times. Perhaps the calling code looks something like this:
Without further changes, the final machine code for main includes the instruction jsr $2000, which is the 16-bit address of the times subroutine in region 2. We want this to occur after we change the MAP, so that times is visible at address $2000. There’s just one problem: the jsr $2000 instruction is in region 0. As soon as we change the MAP, region 0 will no longer be visible, and the CPU won’t see the jsr instruction.
For all intents and purposes, you can’t perform a MAP change with instructions in the 16-bit addresses whose MAP is changing. When the CPU performs the map instruction, the program counter advances to the 16-bit address for the next instruction, but the view of memory has changed out from underneath it.
The solution is to use a dedicated subroutine for managing the transition, and to put that subroutine outside the regional memory window. In my mega65-crogue.scm file, I reserve a “common” section at addresses $1600-$1FFF for this purpose. The revised function call should look something like this:
The jsr to the times routine functions correctly because it occurs in the “common” section, unaffected by the MAP change. When times returns, control returns to the dispatch routine, so it can change back to region 0 before returning to the main subroutine.
Recall that to update the MAP register, you perform the following 45GS02 machine code instructions:
This example sets MAPLO so our region window $2000-$7FFF is pointing at region 1, which starts at 28-bit address $0.A000. This uses an offset of $0.8000, so 16-bit address $2000 is 28-bit address $2000 + $0.8000 = $0.A000.
Similarly, this example sets MAPHI so 16-bit addresses $E000-$FFFF see KERNAL code at $3.E000-$3.FFFF, using an offset of $3.0000. You must always set both MAPLO and MAPHI when invoking the map instruction, because it’ll use all four A, X, Y, and Z CPU registers. In Rogue’s case, I’ll just always set Y and Z to $00 and $83 when changing MAP.
So how do we get this assembly language code into our C program? There are two ways to do this: assembly language files, and inline assembly language inside C functions.
Calypsi has its own assembler, which can be invoked with the command as6502. You can build entire assembly language programs with Calypsi, but it is more appropriate to use it to mix assembly language and C in the same project. Given an assembly language source file (typically with the .s filename suffix), as6502 can produce an object file that can be linked with other object files into the final program. As with the C compiler, the object file the assembler produces is not a complete program, and needs the linker to decide where the machine code lives in memory.
The Calypsi MEGA65 starter project has a Makefile rule that assembles all .s files in the project into object files. I’m using that Makefile, so I merely need to create .s files alongside .c files, and they will be built into my project.
Assembly language source code for a C project looks a bit different from a pure assembly language project in other assemblers. The code needs to tell the linker the intended section for the code, and how to make a subroutine available to C code as if it were a function. With some effort, an assembly language routine can be a full C function that other C code can call with arguments, and can return a value. The routine can even access other C functions and global variables.
Here’s the beginning of a file named dispatch.s in my code, which sets up region_dispatch as an external symbol that the linker can use as an address for the dispatch code:
I also have a header file dispatch.h that describes how the C code should treat this subroutine as a function. I’ll explain this more later, but for now it can just contain this declaration:
This is enough for the C program to invoke the region_dispatch assembly language subroutine by calling the region_dispatch() function, without arguments or a return value. The .section directive assigns it to the commonText section, type text (code), which my linker configuration puts in $1600-$1FFF. This code will always be resident, so any code can call it at any time, regardless of the current MAPLO window setting.
Assembly language can refer to symbols in other modules, such as global variables or C functions, by declaring them as “externs,” like so:
If you know that the symbol refers to a zero page address, prefix it with zp: to force zero page addressing:
If the symbol refers to a 16-bit value, prefix it with .byte0 or .byte1 to access its individual bytes:
Assembly language knows nothing about C structures or types. Take care that the assembly code is treating the memory the way it wants to be treated.
For more on Calypsi assembler syntax, see the Calypsi guide, chapter 21: Assembler. For information on passing arguments to or returning values from assembly language functions, see chapter 20: Assembly language interface.
If you just need a few raw CPU instructions inside a C function and don’t need them to be separate functions, you can use inline assembly. You invoke the __asm() directive directly within a C function, and give it a C string containing Calypsi assembly language. This machine code is inserted into the definition of the function.
The C string must resemble an assembly language source file, including a leading space and a newline for each line, as shown.
The __asm() directive takes three optional “constraints,” separated by colons. The second and third of these can be a list, delimited by commas. These constraints serve the following purposes:
Here’s the example from the Calypsi guide:
This says:
(The “=” and “K” symbols are required as shown.)
If you don’t need a constraint, omit it, but keep its colon. For example, to indicate clobbered CPU registers without output or input constraints:
Calypsi supports register classes for a, x, y, z, and q for the 45GS02 CPU.
You can also request zero page soft registers for the output and inputs, using the desired size as the register class: zp8, zp16, or zp32. The assembly code refers to a zero page output variable address using %0, and refers to zero page input variables using %1, %2, etc. in the order they appear in the input expression list. You can also give these inputs names instead of numbers; see the Calypsi guide for the syntax.
For more on inline assembly, see the Calypsi guide, chapter 20: Assembly language interface, section 20.4: Inline assembler.
With assembly language at my disposal, I can now implement the dispatch mechanism. This needs to be a little bit clever.
I mentioned that Calypsi generates code for a function call to prepare arguments, perform the jsr, then process the return value. Ideally, for a dispatched function call, the MAP changes happen just before and after the jsr, so Calypsi can still own the argument and return value prep. If the C code says int result = times(7, 3);, there isn’t really a way for me to inject the MAP changes into Calypsi’s usual sequence. Whatever alternative I provide needs to work for any function type signature. Also, the dispatch routine needs input parameters of its own, including the destination region.
Here’s what I came up with:
These type casting gymnastics took me a while to figure out. In particular, __typeof__ is a non-standard but popular extension of the C99 standard. It was added to GNU C a while ago, and only adopted into the C standard starting with C23. (The standard numbers refer to the year they were ratified: C99 in the year 1999, C23 in 2023.) Calypsi supports __typeof__.
In dispatch.h:
A dispatched call to times(7, 3) might look like this:
There’s no way I’m typing all of that out for every dispatched call. That’s what preprocessor macros are for. Back in dispatch.h:
In the earlier example, we put a function called times() in region 2, and want to call it from the main() function in region 0.
Instead of:
I can now dispatch between regions like so:
The C preprocessor performs transformations on a C source file before handing it over to the compiler. For example, when it sees a #include directive, it replaces it with the contents of the file being included. #define can be used to define names that get replaced with strings. In this example, REGION_2 gets replaced with 0xe100.
You can also define macros that take arguments. These look kind of like functions, but they aren’t, it’s just more text substitution. In this example, after REGION_2 is replaced with 0xe100, the REGION_DISPATCH macro expands to its definition with dest_maplo replaced with 0xe000.
As of C99, macros support variadic argument lists. The ellipsis (...) at the end of the argument list says that the macro can accept any number of comma-delimited arguments at that point. The special symbol __VA_ARGS__ in the macro definition expands to all of these arguments, preserving the commas.
According to the C standard, C99 requires at least one variadic argument. C23 added support for empty variadic argument lists. Calpysi has not back-ported this feature exactly, but it seems to support empty variadic argument lists anyway with the C99 syntax, so I’m not complaining.
The slashes at the ends of the macro definition lines are important. Preprocessor macros are required to be on a single effective line of the source file. Each slash says the macro continues on the next line. It’s important to remember that this isn’t C source code, this is a C preprocessor directive that becomes C source code later.
The macro expands to multiple lines of code, inside parentheses and brackets: ({ ... }) This is a statement expression, and is critical for this technique to work. A statement expression contains multiple C statements, then ends with an expression. The entire expression evaluates to that last expression. This causes the entire macro expansion to evaluate to the return value of the dispatch function, so the macro can be used just as a function with a return value might be used. This also works for void-type expressions, without changes to this example. Statement expressions are another GNU C extension not strictly supported by the C standard, but they are supported in Calypsi.
Here is a complete implementation, in dispatch.s:
The region_dispatch assembly language routine is standing in for the dispatched function. We are tricking Calypsi into thinking it is the dispatched function so that it prepares arguments and manages return values in the same way as simply calling the function. The first thing region_dispatch does is stash the CPU registers to be restored just before calling the dispatched function, in case they’re important. They may not be, but we don’t want to confuse Calypsi.
The dispatch subroutine keeps track of the current state of MAPLO by storing it in a variable every time it is set (region_dispatch_last_maplo). This works fine as long as dispatch is in full control of the MAP register, which it is in this case. There is a way to read the current state of the MAP register by triggering the MEGA65’s Hypervisor, but this is not needed in this case.
Before it calls the function, the dispatch subroutine remembers the caller’s MAPLO using a stack data structure. When the function returns, it pulls the caller’s MAPLO off of the stack and restores it, so it can return control to the caller. The stack allows for the function being called to make its own dispatch calls: if the function being called tries to dispatch somewhere else, the callers pile up in the stack, then un-pile as they return. I allocated 32 bytes for this stack, so this mechanism supports 16 “nested” dispatch calls.
There’s one more subtle thing we need to get this technique to work. On its strongest setting, Calypsi’s optimizer will attempt to share pockets of generated assembly code between functions. This is clever and useful, but we have to tell Calypsi to not share code between two functions that live in different regions.
Add these two arguments to the compiler (cc6502) command line, in your Makefile: --no-cross-call and --no-interprocedural-cross-jump
I didn’t know about this one at first and spent days trying to figure out why my program was crashing. I stepped through the generated assembly language, noticed the cross-call, and thought I was sunk. Then kibo told me about these command line arguments, and the day was saved. Thank you kibo!
Last month I proposed that this region switching mechanism (the MAPLO window) could accommodate keeping the KERNAL active in the 16-bit address space, and could still use the RAM underneath the KERNAL’s addresses in bank 0 as a mappable region. I described a candidate memory map that did so, with regions all over bank 0 and 1 that avoid colliding with KERNAL and DOS variable space. There’s no reason I couldn’t allocate more regions in banks 4 and 5 if needed, I just wanted to see if this program would fit entirely into banks 0 and 1.
The most important thing to remember about this region switching technique is that the compiler will treat everything as if it is accessible at 16-bit addresses. At any given point in the code, the 16-bit address space must meet this expectation with whatever region is active. This works fine if the code in the active region only needs to access static memory (constants, local variables) within its own region, or on the heap, C stack, or “common” areas, which are always visible.
When code in the active region needs to access code in another region, it uses the dispatch macro to invoke the function. There is no equivalent mechanism to reach directly into another region’s data space. One way around this is to provide accessor functions that could be called via dispatch. Arguments and return values are managed through the dispatch process via the C stack.
Another way around this is to move shared data outside of the region space, such as into the “common” area. There’s not a lot of room in there, but if having just a few shared variables untangles the program, it’s worth doing. You can relocate a module’s data simply by changing the #pragma:
More care is needed when passing, storing, and dereferencing pointers. Whatever the pointer points to must be visible when the pointer is dereferenced. The pointer itself can pass through functions in other regions, but the thing that acts on the pointer has to be able to see that data.
This can be counterintuitive when it comes to passing string literals as arguments. C makes it look like the string literal is a value that is being passed to the function like any other. What actually happens is the string literal is generated into the current rodata section, and a pointer to that value is passed to the function. If the string literal is an argument to a dispatched function, the literal will not be visible after the map has changed.
One solution for string literals is to tell Calypsi to place all rodata into the “common” area. In my case, I don’t have nearly enough room. Rogue is very string heavy, and makes extensive use of passing string literals as function arguments. Instead, I just barely managed to reorganize functions that take strings into the “common” area, so calls to these functions don’t need the dispatch and the string literals stay visible in the region window.
I eventually decided to implement another dispatch mechanism for functions with a single string argument, which happens frequently in Rogue for printing messages. This new mechanism copies the string argument to a buffer in the “common” area, then calls the function with a pointer to the buffer substituted for the string argument. I won’t paste the whole thing here, but this is what it looks like to use it:
I originally put the default code, cdata, and switch sections in region 0. Calypsi also uses these regions to place the C standard library functions that get pulled in when I use them. I was able to get away with dispatching to standard library functions for a while, but in the end I moved these sections into a common area so I wouldn’t have to worry about it. I also narrowed my dependency on the standard library to just a few features. I didn’t want to make any assumptions about whether the standard library was implemented in a way that was compatible with the dispatch mechanism.
While functional, use of this dispatch mechanism is error prone and difficult to debug. If I use the wrong region ID for either the caller or destination, the program compiles but crashes. If I forget to use dispatch when it is needed, the program compiles but crashes. Debugger breakpoints are fraught, because they’re associated with the program counter and not the 28-bit address of the instruction, so a breakpoint intended for code in one region might trip at the same address in another region.
You might ask, with all of these limitations, is this dispatch technique even a good idea? I have two answers. For one, mostly yes. Most of these issues come from the fact that my project is a port of existing code that was written for the Unix process memory model. If I were writing a program from scratch for the MEGA65, I’d design the program to the constraints of the memory map. And second, you don’t really have much of a choice. If you have more than 32 KB of code, you have to do something about it, eventually needing a mechanism to bring code from upper memory into the 16-bit address space.
It sure would be nice to end this article series with a complete port of Unix Rogue for the MEGA65 that you could just download and play, even if you’re not interested in the techniques. Unfortunately I don’t have that for you just yet.
Just to be clear: My goal was to port the actual original Unix source code to the MEGA65, not just come up with an arbitrary Rogue-like game that vaguely resembles the original. This is a challenging goal. Rogue relies heavily on the Unix process model to simplify its code base. Any function can call any other function, and any function can access any global data, with all of the overhead of calling functions and accessing data handled by the C compiler. The easiest way to write a game in this environment is to put all of the game state into a single public memory blob, and not think too hard about organizing functions into modules. The developers of Unix Rogue didn’t have to think about 24 KB memory banks or cross-region visibility.
I wanted to get something working as quickly as possible, so I started making some rash decisions. I tried revising my memory map so that the 23,674 bytes of global data were always visible at 16-bit addresses. To do this, I deleted all the disk-based features, disabled interrupts, ejected the KERNAL, and replaced “region 1” with global memory. This was not a complete solution to memory visibility, because many functions still used local variables—and passed the addresses of those local variables to functions in other regions. I plowed through the code, juggled modules between regions, added dispatch calls, and fixed memory visibility bugs as I went. Dispatch calls generate much more machine code than regular function calls, and simply adding dispatch calls caused modules to overflow their regions, requiring more revision. I wrote Python scripts to help manage this process.
I originally tried to use the call graph to help organize code into regions, thinking that “adjacent” code would benefit from being in the same region and limit the number of dispatch calls. I didn’t get very far with this technique. It was much more important to pack regions tightly. This is a classic partitioning problem, with simple approximate solutions. “Longest processing time-first scheduling” is the algorithm I understand, so I wrote a simple Python script that analyzed module sizes and proposed region placements. The compiler makes .lst files for each C module that summarizes the section contributions in bytes at the end.
The linker’s own crogue-mega65.lst file provides a complete summary of where it puts things and how the sections are filling up, invaluable if I were building up from scratch. In this case, until the regions were packed properly, the linker wouldn’t even finish, printing the relevant information in a giant report of error data.
Because the original code wasn’t written to accommodate regions, I ended up with a grotesque amount of dispatch calls. Nearly every cross-module call became a cross-region call. I allowed this just to get to a point where I could see something running, promising myself I’d clean it up later.
I got far enough to prove the point. The game starts, and you can walk around one floor of the dungeon and pick up objects. I’m confident that the rest of the work is just more of the same, cleaning up memory and code visibility bugs. It successfully runs a 90 KB program spread across two banks of MEGA65 memory, all from compiled C code.
All of this manual effort of placing code into regions and tracking visibility resembles tasks that you would normally delegate to the compiler. If the compiler were aware of the region-based memory map, it could place fragments into regions automatically, optimize their placement to minimize dispatch calls, and generate the dispatch code itself as part of function calls. This could provide a C abstract machine similar to the Unix linear memory space, such that I wouldn’t need to do any of this by hand.
And yes, I tried using genAI coding tools to do this optimization. I won’t go into detail about what I tried and didn’t try, but suffice it to say that inexpensive context windows can’t hold enough information for an LLM to act as a meta-compiler on its own. For my goals of understanding the original Unix Rogue code base and developing the region dispatch system, I didn’t want genAI to completely rewrite the program, so I didn’t make a deeper investment in token costs or genAI workflow. A better approach would be to use genAI to build the meta-compiler—but I’m not inclined to outsource the fun part of my own hobby. 😛
In the end, I’ve decided not to finish the dirty port strategy. I have a way to write large programs in C, and I have bigger Rogue-like fish I want to fry.
I don’t like promising future results for hobby projects—it takes the fun out of it—so don’t take this as a pledge. But I have made some exciting progress on a new path.
Taking what I learned from this dispatch technique, I started a fresh project with a new goal: to implement the maximal memory management strategy. I now have a new C-based game shell with these features:
This shell could be used for all kinds of games and applications. And really, it’s the right way to do a port of Unix Rogue in the first place. Start with a shell like this, then gradually populate regions with Rogue functionality, one piece at a time.
I’m not ready to support this shell as a product. But it’s working, and if I make a full game out of it, I’ll share source code.
It’s been a super busy August, with a combination of retro computer work and family vacation. September will be no different! I have stuff planned for VCF Midwest and not nearly enough time to prepare. I have a more laid-back Digest planned for the end of next month, so hopefully that’ll be a nice change of pace.
Your support for the Digest is more important than ever these days. Thanks as always for the feedback, encouragement, and contributions. If you’d like to support the Digest, please visit: ko-fi.com/dddaaannn
Keep circulating the tapes!
— Dan
Calypsi C, part 2: Using memory. Dan’s MEGA65 Digest for July 2026.
The story so far: Back in May, we introduced the Calypsi C cross-compiler, a highly capable optimizing compiler toolchain for multiple vintage and retro computers, including the MEGA65. Calypsi’s default linker configuration works well for pure C programs that compile to a single MEGA65 PRG file, a fine starting point for learning the language and writing small- to medium-sized programs.
Then last month we discovered a vintage Unix text game, written in C and based on the curses terminal display library: Rogue: Exploring the Dungeons of Doom. I got the idea in my head that given how portable C can be on a good day, it might be possible to use the original source code for Rogue to make a faithful version for the MEGA65. I would just need to replace the curses display library with a MEGA65 equivalent, and everything would just work, right?
Without further changes, I ended up with a program that Calypsi tried—and failed—to build into a single PRG file 112 KB in size. While the MEGA65 has enough memory for that much code and data, its memory architecture and KERNAL don’t support a single PRG file that large. I will need to use advanced memory management, coding, and boot loader techniques, along with some powerful features of Calypsi, to produce a MEGA65 program that will load and run this much code and data.
I owe most of this article to kibo and his work on the MEGASPUTM graphic adventure game player, which is built in Calypsi, uses many of these techniques, and serves as a useful code example for large MEGA65 projects in the C language. The final solution uses a combination of multiple techniques, each of which are worth knowing about so you can apply them selectively to your own projects.
Amiga 500 core for the MEGA65, by sy2002.
The MEGA65 gets its first Amiga core!
sy2002, of the C64 core project, has launched an FPGA core that recreates an Amiga 500. This early version is feature complete, and supports the OCS chipset and PAL video output, with 512 KB of Chip RAM and 512 KB Slow RAM. Connect an Amiga mouse (or mouSTer or Tank Mouse) to port 1, and a joystick to port 2. Disk support is currently limited to 880 KB ADF disk images on the SD card, in a single df0: virtual drive. It loads Kickstart and Workbench from disk images, and can run demos and games. Yes, including Lemmings.
You will need to acquire files for Kickstart 1.3 revision 34.5, Workbench 1.3 ADFs, and any other ADF disks you might want to run. Put the Kickstart ROM on the SD card as: /amiga/kick.rom
The author has clarified that this was largely an AI-assisted port of MiniMig that he won’t have time to maintain. This version will likely inform an active sibling project that will carry it forward. Head to the announcement post to download it, and don’t miss the project’s documentation website. I know lots of people who have been waiting for this milestone for the MEGA65 project, and I can’t wait to see what develops.
Commodore 128 core for the MEGA65, by Stefan Eilers.
Meanwhile, Stefan Eilers has a Commodore 128 core in progress, with an early release for people to try. So far only 40-column mode and IEC disk drives are supported. It does not yet support cartridges, 80-column mode, or virtual disk images off the SD card. But there’s meaningful progress, and you can play with it!
You will need C128 ROMs for this one as well. The 72 KB system ROM bundle is to be installed as: /c128/boot0.rom The 512 KB drive ROM is: /c128/boot1.rom Check out the Github repo for the project for more information.
sy2002 has also extended The Ultimate MiSTer2MEGA Porting Guide with a supplement on the QNICE CPU and the shell ROM that provides the pop-up menus. If you’re interested in porting MiSTer cores, be sure to keep track of these useful updates.
If I were making a game for the Commodore 64 that required 112 KB of code and data, I’d have no choice but to build it in a way that loaded pieces from disk at moments during play, keeping only a small portion in the C64’s 64 KB of memory at a time. The MEGA65 has 384 KB of main memory, so I should be able to get Rogue to run entirely from memory after it is loaded the first time, using the disk only to save and restore the game state at the request of the player, and perhaps to save a high score table to disk at the end of each game.
The DLOAD command can only load a PRG file into a single contiguous region of memory in bank 0, and will refuse to load a PRG file larger than 53 KB. I will need to write my own custom program, known as a boot loader, that loads discontiguous chunks of the game’s code and data into banks 0 and 1. The user will load and run the boot loader program to start the game.
This requires splitting the game code and data into chunks. The default Calypsi linker configuration only knows how to build a single PRG file of 32 KB, but it is possible to give the linker more specific instructions to fit the target architecture. I will need to configure the Calypsi linker to organize the program according to my desired memory map.
I will also need to tell the Calypsi compiler how to access code and data across banks 0 and 1, once the linker has placed it there. The compiler doesn’t do this automatically: it doesn’t know anything about what the linker will do. I’ll need to be explicit about how I want the machine code that Calypsi generates to use the MEGA65 memory system, according to the needs of the program.
Lastly, I want to fit the entire program into banks 0 and 1. Technically that’s 128 KB of space, but that has to include zero page registers, the CPU stack, and dynamic memory allocation space. While the program is loading from disk, the KERNAL will be using disk system memory at the beginning of bank 1, so I can’t just load data directly into that space. In theory, I can hide the KERNAL while I’m not using it, but it would be easiest if I could just keep it in memory. Overall, there’s something between 24 KB and 32 KB of the first two banks that I cannot use for program code and data.
So I will have to reduce the size of the game. Some of this will just be a bit of belt tightening. I may be able to reduce the game’s reliance on the C standard library, so I’m pulling in less of its code. Worst case scenario, I’ll just need to use more banks. My target, for now, is to use only banks 0 and 1.
32 KB doesn’t seem like much. Why is Calypsi’s default maximum PRG size so small?
When you type the command DLOAD "SOMEFILE", the MEGA65 loads the complete contents of the file into memory starting at address $2001. It is universally expected that the beginning of this data is a BASIC program that can be RUN. You can combine the DLOAD and RUN commands by simply giving RUN the filename: RUN "SOMEFILE"
The file can contain other information beyond the BASIC program. DLOAD just paints all of the data from the file into RAM, and doesn’t care what it is. We take advantage of this to make runnable programs in machine code, such as assembled from assembly language or compiled from a language like C. The start of the PRG file is a short BASIC program that invokes the machine code that appears after the BASIC program in the file. This is what Calypsi produces when you ask it to build a PRG file with the --output-format=prg linker argument.
DLOAD loads the program into MEGA65 RAM in bank 0. The next reserved region in RAM starts at address $F700, where BASIC keeps some of its variable storage. DLOAD will refuse to load a PRG larger than $F700 - $2001 = $D6FF bytes, or 55,039 bytes, about 53 KB. This is the largest possible size of a program written in pure BASIC: the tokenized BASIC code cannot go beyond that address.
When machine code is involved, we have to take the rest of the MEGA65’s memory system into account, especially how the CPU locates machine code instructions. We’ve covered a few of these concepts in previous Digests, but it’s important enough to review.
The MEGA65 memory system has 2^28 = 268,435,456 possible addresses, a 28-bit address space. Some of these addresses are connected to memory, others are connected to peripherals such as the VIC video chip, and most aren’t connected to anything (reserved for future expansion). The 45GS02 CPU has ways it can access any of these addresses, using four bytes to store the address and leaving the top four bits set to zero.
For backwards compatibility with the MOS 6502 lineage of CPUs, most 45GS02 CPU features expect the addresses of things to only be two bytes = 16 bits. In particular, the program counter, which keeps track of the address of the next instruction to execute, is only 16 bits wide. It’d be a shame if these features could only access the first 2^16 = 65,536 addresses of memory. So, instead, the memory system maintains a 16-bit address space that assigns regions of 16-bit addresses to regions of 28-bit addresses. These assignments can be changed by the program as needed.
When the KERNAL is active, it uses a 16-bit memory map that puts KERNAL code at 16-bit addresses $C000-$CFFF, hardware registers at $D000-$DFFF, and more KERNAL code at $E000-$FFFF. KERNAL code actually lives in upper regions of the 28-bit address space, within 28-bit addresses $002.0000 to $003.FFFF (banks 2 and 3). There is still RAM at 28-bit addresses $000.C000 to $000.FFFF, but the KERNAL sets the memory system so 16-bit addresses $C000 to $FFFF refer to the KERNAL code and I/O registers. The RAM is shadowed in the 16-bit address space by the KERNAL’s memory settings. This is how the DLOAD command can load a 53 KB PRG file into the RAM, underneath the KERNAL code visible at those 16-bit addresses.
This establishes a practical size limit on machine code in a PRG file loaded by the DLOAD command. Without changing the memory settings, the CPU can only see machine code from the PRG file up to address $BFFF. $BFFF - $2001 = $9FFE, or 40,958 bytes, or about 40 KB.
It’s entirely reasonable for a program to execute machine code that changes memory settings before accessing code at higher 16-bit addresses. If we were writing an assembly language program, this would be straightforward, because we have complete control over where the entry point of the program is in memory. When writing a C program, we delegate that decision to the linker. We’d need a way to tell the linker to put certain things in certain places if we want more control.
But wait, why did I say 32 KB earlier if we have 40 KB? That’s one last detail. Calypsi’s default linker configuration keeps everything in a single region of 32 KB, from 16-bit addresses $2001 to $9FFF. This leaves $A000-$BFFF (8 KB) unassigned.
When we first started with Calypsi, I said the mega65-plain.scm configuration file is included with Calypsi, and it’s for simple programs. Here are the complete contents of this file:
Without understanding the syntax of this file, you can see where it sets the 32 KB limit. We could change #x9fff to #xbfff to extend the maximum PRG size to 40 KB.
This file uses the Scheme programming language to define a data structure named memories. You don’t need to know much about Scheme except that all the parentheses need to match, and the little single-quote mark in the 2nd line is important. All of the important settings are within the second set of parentheses.
In this case, the configuration file defines a set of memory entries. Each one has a name, such as program or zeroPage, and some properties in their own sets of parentheses.
From top to bottom, mega65-plain.scm defines four regions of MEGA65 memory and declares their purpose, like so:
This tells Calypsi that it has 32 KB of space in which to place code, data, and variables, all in bank 0. If the linker runs out of space before it has placed everything, it aborts the process and reports an error.
To provide Calypsi with a custom configuration file, create a file similar to mega65-plain.scm in your project, and give it a name that starts with mega65- and ends with .scm. Provide the file path to the linker command line instead of mega65-plain.scm, such as in your Makefile.
You can set up the Makefile rule to re-build the project after making changes to the linker configuration file like so:
Let’s take a moment to clarify Calypsi’s memory configuration terminology.
Recall that the compiler takes a C source file and produces an object file. The object file defines fragments that belong to sections. The linker takes all of the object files for the project and tries to place fragments into memory locations based on the linker configuration. For example, code sections would all be placed in a memory configured to accept code.
A few useful section types to know about:
Calypsi can generate a report of all of the linker’s decisions. This is a really handy way to make sure your linker configuration is doing the right thing. Back in May, we saw that the MEGA65 starter project is already set up to generate this report, as the file hello-mega65.lst. The --list-file argument to the linker tells it to produce this file.
Here’s a brief excerpt of the report from a short sample program I wrote. The mega65-plain.scm configuration defined these memories:
Calypsi assigned the sections of my program to memories like so:
For a complete list of sections and their types, see the Calypsi manual chapter 6.1: “Sections.”
A C program can allocate an arbitrary amount of memory determined while the program is running using a built-in memory management system known as the heap. Here’s a trivial example:
If you build this program with the default mega65-plain.scm, compilation and linking will succeed, and the program will run, but it will report “OUT OF MEMORY” then quit. This is because the default linker configuration does not assign any memory to the heap. To fix this, add the following definitions to the project’s custom linker configuration file:
The block rule defines a chunk of heap memory for the linker to assign. Most blocks come from the object files of the program, but blocks for the heap and stack need to be defined explicitly. The memory named bss_main_lo assigns the heap to an explicit 8 KB region of memory at addresses $A000 to $BFFF.
Try setting up the MEGA65 starter project with this configuration, and see how it changes the report in hello-mega65.lst.
Important: Calypsi only knows how to use 16-bit addresses when allocating dynamic memory. While you can manipulate the memory system settings to map these addresses to upper banks, it’s easiest to assume that heap will use RAM in bank 0, and keep its 28-bit addresses un-shadowed when messing with the memory system.
We have seen how to access specific addresses in bank 0 using pointers. This allowed us to access registers in the $d000-$dfff range by casting the numerical address to a pointer type, then dereferencing the pointer, like so:
A pointer such as this one refers to the 16-bit address space. The pointer data is 16 bits, and Calypsi uses the CPU’s 16-bit access mode in the code that it generates.
For example, if screen memory is in bank 0, you can use a 16-bit pointer to access it, reading the start address out of the SCRNPTR register. The following code reads the location of screen memory from the lowest two bytes of SCRNPTR as a 16-bit unsigned integer, then treats it as a pointer to a byte:
We’ve seen that there are several ways to access color memory, including a way to use registers starting at $D800 as a window into the first 1 KB of color memory, or 2 KB while manipulating the CRAM2K register. (For a full explanation, see “Colour Memory” in the MEGA65 Compendium.) It’s often easiest to access color memory at its physical location at address $FF8.0000. In machine code, you could use 32-bit indirect addressing to get at these addresses. Calypsi can also do this, but it needs to be told explicitly to use a 32-bit pointer.
The solution is to use what’s known as a far pointer. It looks like this:
The __far qualifier is part of the pointer’s type, and makes the type distinct from a regular non-far pointer. A far pointer’s data is 32 bits, and Calypsi uses the CPU’s 32-bit access mode in the code that it generates. This typically requires more machine code instructions and some space in zero page, so it takes more CPU cycles and is more difficult for Calypsi to optimize.
Calypsi far pointers can refer to any address, but for efficiency purposes, pointer arithmetic (such as adding an index i, as above) is not allowed to leave the bank. If you need to cross banks, Calypsi also has a huge pointer (__huge) that doesn’t have this restriction, at the expense of needing more machine code.
There’s currently a bug in Calypsi’s parser where __far and __huge aren’t recognized everywhere they’re supposed to be recognized. The workaround is to use the longer phrase, __attribute__((far)), which works everywhere.
The __far keyword is specific to Calypsi. This can be confusing to code editors with support for C language error detection. I’m using VSCode and its C language mode to verify my code as I type, and it doesn’t recognize __far like this. You can tell VSCode to ignore __far by putting the following in the project’s settings.json file, nestled appropriately with other values that might already be in there:
You can tell Calypsi to place a global variable into the MEGA65’s upper memory. To do this, use the __far or __huge qualifier with the variable type.
This has two effects:
The linker attempts to place these sections in appropriate memory based on the linker configuration. If no memory is defined to hold far data (as is the case in mega65-plain.scm), the linker aborts with an error.
The following configuration declares addressees $1.2000 to $1.3FFF as reserved for far memory:
Setting up far memory and using it for variables gives Calypsi permission to spread out your data storage. But remember that it comes at the expense of slower access and more code generated. Every use of a far variable, or dereferencing a far pointer, requires 32-bit indirect addressing, which involves storing an address in zero page and invoking the addressing mode.
In the previous Digest, I mentioned that using C standard library functions can drag a lot of code into your project. While printf() is powerful for formatting output in complex ways with very little calling code, it comes at the cost of a big chunk of library code to support all of its formatting features.
This short program uses printf() with one of its most expensive formatting features, rendering floating point numbers:
This produces a PRG of 10,983 bytes. That’s quite a lot for a single function call.
Calypsi has a cool trick to help with this. Calypsi actually has several versions of printf() it can choose to include, each with an increasing selection of supported formatting features. The compiler reviews all uses of printf() (and related functions) in your program, and selects the leanest version of the string formatting engine that supports all of the features the program needs.
In the previous example, I forced Calypsi to use the beefiest version of printf() by formatting a floating point number (%f). If I change the printf() call to only use integer formatting:
I get a PRG of 4,860 bytes. That’s a big improvement, and I still have many useful formatting features I can use with this version.
While looking over how Rogue uses printf(), I thought maybe I could save a few more bytes by writing my own version, with even fewer features. Perhaps it was foolhardy to try to write my own version of the most famous standard library function of all time, but I got a positive result. By limiting it to just %d, %s, %c, and %%, I managed to write a smaller printf() that brought this test program down to 3,708 bytes.
If absolutely no string formatting is needed, the program is better off using puts() instead of printf():
This compiles to 2,761 bytes.
Calypsi chooses the printf() implementation automatically based on how your code uses it. If for some reason you want to force it to use a specific version, you can pass a command line argument to the linker, such as --rtattr printf=nofloat. For a complete description, see the Calypsi manual, chapter 18.6: “Library on a diet.”
While on this topic, Calpysi can also save a few bytes if your program never exits. Many Commodore programs never bother to exit to the READY prompt because users don’t need it. When the user is finished with the program, they simply reset the computer.
Calypsi normally attaches a chunk of clean-up code in case the program returns from the main() function, or calls the exit() function. If your program doesn’t need this exit code, it can request that it be omitted, with a linker argument: --rtattr exit=simplified
I saw a savings of 87 bytes when I did this with this test program. Not much, but I’ll take all the help I can get.
If I were writing this game from scratch, I would try to get something working within the default 32 KB, and only start mixing in advanced techniques with a working application. The responsible thing to do with this port would be to cut out entire features—potions, scrolls, even monsters—down to this size, then start making decisions so I can add stuff back in. I’m not going to go quite this far, but I do want to see how much space I’ll actually need to get the game going.
I started by stripping out all of the Unix-specific stuff I mentioned last month. I simplified main.c extensively, having the program enter an infinite loop instead of exiting. I kept wizard mode disabled in the build, and deleted code related to encrypting the wizard password. Nearly all screen manipulation was happening in calls to my curses alternative, so I was able to clean up or replace calls to writing to the Unix standard output stream (printf, fflush, etc.). My custom printf implementation is now just a limited sprintf(), which performs string formatting into string memory. This gets written to the screen with my curses library.
A big space hog turned out to be rip.c, which handles all of the game ending logic and exiting the program. When you die, this code prints a large ASCII art tombstone with your name on it, and implements this as a collection of large strings stored in program memory (cdata). The tombstone is charming and I’ll want to replace it, but removing the inline strings for the art saved 482 bytes alone. There’s a similar ASCII art mural for the unlikely event when you win the game, which I also removed temporarily. I might replace these with separate files loaded from disk as needed, or load them separately into bank 4, instead of keeping them in program memory during the game. I also disabled the high score file for now.
According to the linker, my changes brought the program down to $c9ef of code and $185d of data, with $1000 reserved for cstack and $69a4 of zdata, for a total size of $15bf0, or about 86 KB.
My goal for now is to get a version of Rogue working that keeps the MEGA65 KERNAL in memory. This gives me the following restrictions, around which I can plan a memory map:
Recall that the MAP register specifies which 8 KB regions are “mapped” or “un-mapped,” and applies an offset to the mapped regions to translate its 16-bit address to a 28-bit address. Also recall that the first four regions must share a single offset setting (MAPLO), as must the last four regions (MAPHI). To keep the KERNAL code mapped at $E000-$FFFF, the MAPHI offset must be $3.0000, which effectively rules out any custom mapping of $8000-$BFFF. If we set any of those regions to “mapped,” they’d use a +$3.0000 offset, which isn’t useful. So $8000-$BFFF stay un-mapped, and refer to memory in bank 0. This is a good place to put the heap.
$0000-$1FFF needs to refer to bank 0 memory, so we can’t set that region to “mapped,” at least not with a non-zero offset. But I can leave it un-mapped, and use the rest of $2000-$7FFF (24 KB) as a MAP window to access upper memory. To do so, I would set MAPLO with an offset that gets added to the 16-bit address to form the 28-bit address. This doesn’t have to be memory in upper banks! It can also refer to other addresses in bank 0, to access the RAM being shadowed by the KERNAL ROM and banking mechanisms at $C000-$FFFF.
My first guess at a memory strategy is to reserve parts of physical RAM in banks 0 and 1 as described, assign the heap at $0.8000, then divvy up the rest into 24 KB regions that I can access using MAPLO and 16-bit addresses $2000-$7FFF. I get four full 24 KB regions, plus another 8 KB at the end of bank 1 that I can either use the same way, or come up with a special purpose for later. The 28-bit addresses for these regions are 0.2000, 0.A000, 1.2000, 1.8000, and the little one at 1.E000. If I really want to use that little one at the end, I’ll need to account for the color memory window at 1.F800-1.FFFF, such as by pushing COLPTR ahead by 2 KB.
In theory, that’s 104 KB I can use for both code and data. In practice, I can only use 24 KB at a time. I will need to do the following:
We’ll cover the first four steps in the rest of this article. We’ll save step 5 for next month, so we can take our time to enjoy it.
Recall that our linker configuration (mega65-crogue.scm) defines a memory named program at addresses $2001-$9FFF, of type any, with some additional parameters to set it up like a runnable program.
With this configuration, the linker will try to fill this memory with code and data fragments, and also set the startup routine to call main(), wherever it ends up.
For Rogue, I want to declare memory for each of my regions, then assign code and data to specific regions. Here is a complete linker configuration for Region 1:
The first memory, crogue-r1-region, declares sections where the linker will place fragments. It sets the 16-bit address range that the linker will use when sewing together branch instructions and data references within the memory. Each type of memory needs its own section, so the linker can group stuff together by type.
When inventing section names, use camelCase or underscore_case. Some parts of Calypsi do not support section names containing hyphens.
These sections are organized into two placement groups: one for sections that will contain information (the “bits” placement group) and one for the section that will contain uninitialized memory (the “nobits” placement group). Uninitialized memory is filled with zeroes by the start-up code that Calypsi adds to the program. As such, it doesn’t need to be saved to disk the way code and initialized memory is. “BSS” stands for “Block Started by Symbol,” which is the term of art for this type in object files. The “nobits” placement group must come after the “bits” placement group, so the file on disk can simply end just before the “nobits” begins.
The second memory, crogue-r1, declares how the sections of the first memory are actually stored in the 28-bit address space. The scatter-to property in the first memory tells Calypsi that it should pretend the fragments are being placed at the 16-bit addresses, but they will actually be stored at the 28-bit addresses of the second memory (section r1). Calypsi needs both sets of addresses because that start-up code uses 28-bit addressing to zero out the uninitialized memory.
Regions 2, 3, and 4 are similar, with the same 16-bit address in the first memory, and their unique 28-bits addresses in the second memory. Region 4 has a smaller address range in the first memory because it is only 8 KB.
Region 0 gets a bit of extra love, in several ways. For one, it actually starts at $2200 / $0.2200, to leave a little bit of room for the boot loader that I’ll add later. Also, it gets attributes to tell Calypsi to put the startup code at $2200, so the boot loader knows how to invoke the program once it is fully loaded. The startup code provided by Calypsi expects specific section names, types, and placements, which this configuration accommodates.
There’s a minor oddity here due to how I have chosen to provide my own boot loader. programStart is actually a BASIC preamble that expects to be at address $2200, and the program’s machine code start address is startup at $220D. I have no use for programStart and it’s wasting a few bytes, but leaving it in is the easiest way to account for the startup code. You can see the assembly language source for the startup code here: /usr/local/lib/calypsi-6502/contrib/MEGA65-SDK/src/commodore-startup.s I bet I could customize this further, but this is fine for now.
With a custom configuration like this, Calypsi needs me to decide where to put three special types of memory. We already saw the heap earlier, for dynamic memory. We also saw cstack briefly, which had an implicit location in the default configuration, but we now need to declare it explicitly. Finally, zdata, the default section for uninitialized variables, needs an explicit location. I decided to go with 4 KB of heap at $0.8000, 3 KB for cstack at $0.9000, and 1 KB for zdata at $0.9C00. These were arbitrary decisions just to get the program to build, and I may have to adjust them later if my program runs out of one type of memory.
One more and we’re done. As we will see next month, CPU instructions that change MAP have to be careful not to change the mapping of the region containing those instructions. Otherwise, the CPU will pull the rug out from under itself, and won’t be able to see the rest of the instructions that complete the adjustment. I declared a new memory outside of the five swappable regions to contain the code that does the swapping, at $0.1600-$0.1EFF. Other than the name and address ranges, its configuration looks like any of the other regions.
I can tell Calypsi to put a C module into a section by issuing a pragma at the top of the C source file. The pragma tells Calypsi that all of the fragments produced for the subsequent code in the file should be assigned to the given sections, for each type of fragment. You can get fancy with this, but for my initial attempt I just want everything in a given C module to live in the assigned region, so it is visible when the region is active.
The following pragma tells Calypsi to put all code that follows into Region 1. It will do so for all code that it processes until it sees another pragma.
If I were writing this program from scratch, I would give each region a purpose, and try to organize my code appropriately. For this port of Rogue, I will start by assigning C modules to regions based on their compiled sizes, so that I’m making efficient use of the memory in each region. This can be a delicate balance, and as you’ll see next month, I had to make some sacrifices to cram Rogue code, which was not intended for this, into these regions.
Calypsi cannot build this program as a single runnable PRG file. Instead, I need it to produce one file per region. Each region will be loaded separately by the boot loader.
For this technique, edit the Makefile (or wherever you have your linker command), and remove the --output-format=prg and -o hello.prg argument. Replace it with the following arguments:
Calypsi will produce a file for each memory in the configuration in which code or initialized data are placed. Empty sections don’t get files.
Notice that there is still a -o argument that specifies an output filename, which we were previously using for the PRG name. This name is used for the first non-empty memory that appears in the configuration file. I organized mega65-crogue.scm in address order, so the first non-empty memory is the one named crogue-common, which I named crogue-common.raw in this command line.
All subsequent non-empty memories get files named after the 2nd memory, with .raw at the end. I named my memories such that each region’s file has a name such as: crogue-r0.raw
The linker is compiling the program into five files, but none of them are files that the user can DLOAD and RUN. We need a short program that loads all of the regions into their designated memory locations, then starts the game.
My boot loader is a BASIC program. It uses the BLOAD command to load each of the five files, then uses the SYS command to start the program, with the entry point in Region 0. It’ll look something like this:
All of these files will go on a D81 disk image. See previous Digest articles for examples of command line tools that build disk images, which you can use in your Makefile. For example, MEGASPUTM uses the cc1541 tool that comes with the VICE emulator. I build my disk image such that each memory file is a PRG-type file, but Calypsi doesn’t insert a PRG starting address, so I use the “raw mode” of the BLOAD command (the ,R flag).
It’s a best practice to make the boot loader the first program in the disk’s directory listing, so RUN "*" (or the shortcut Shift + Run/Stop) finds it. The D81 disk image building tools often provide some degree of control over file ordering. My own tool that I wrote in Python does this.
It’s also a best practice to name the boot loader AUTOBOOT.C65. This is a special filename recognized by the MEGA65 KERNAL. If this file exists on a disk inserted in the drive, the MEGA65 loads and runs it automatically. (You may have seen the Intro Disks do this.) This file also loads and runs when the user enters the BOOT command.
We’ve made some good progress! We learned some useful features for accessing upper memory in Calypsi, and how to set up and use the heap for dynamic memory allocation. We figured out a strategy to spread our 86 KB program across several regions of 24 KB each, and use the MAP register to access one region at a time using 16-bit addresses. We also figured out how to tell Calypsi to put specific parts of the program into specific regions, and to write each region as a separate raw binary file instead of a single PRG file. With just a bit of BASIC, we have a way for the user to load and run the program just like any other, using a boot loader that installs the regions according to our plan.
There’s one last thing we need for this strategy, and it’s a doozy. Calypsi does not know how to change the MAP register when it needs to switch regions to access code and data. I will have to do this manually in the code itself. For example, if the game loop is in region 0 and the code that handles potions is in region 2, when the player drinks a potion, the code will have to call the drinking function in a special way that switches to region 2, performs the drinking logic, then switches back to region 0 to resume play. I will need to make some decisions about how to organize the regions to keep the region switching code easy to manage.
In the next Digest, we will build these last pieces, and (hopefully) finish this project.
Thank you and welcome to all of the new Digest patrons that joined last month! If you’d like to support the Digest, visit: ko-fi.com/dddaaannn
— Dan
Classic Rogue. Dan’s MEGA65 Digest for June 2026.
It’s the game that defined a genre. Rogue: Exploring the Dungeons of Doom by Michael Toy, Glenn Wichman, and Ken Arnold took college campuses by storm in the early 1980s, as a freely distributed game for Unix-based mainframes. Toy and Wichman formed a company to develop versions of Rogue for microcomputers, distributed by Epyx, with notable releases for the Amiga, classic Mac, and Atari ST. It inspired similar dungeon crawlers The Dungeons of Moria (1983), Hack (1984), NetHack (1987), and the Tolkien-themed Angband (1990). NetHack and Angband are both in active development today.
Rogue’s distinctive combination of gameplay properties, especially procedural level generation, turn-based movement, and “permadeath,” have persisted in video games over the decades, including recent commercial successes such as Diablo, Spelunky, and Hades. Hobbyist developers love to make “Roguelikes,” and the annual Roguelike Celebration joyfully encourages creative computing through personal game development. The Roguelike Celebration YouTube channel is packed full of informative and inspiring talks from the last ten years.
And of course, Roguecraft DX for the MEGA65 (and Amiga, Game Boy Color, Evercade, and modern PC) by Badger Punch Games is a clear descendant of the seminal Unix game.
The authors of the original released the C source code for the Unix version of Rogue under the BSD open source software license in 1986, currently maintained by David Silva. That got me wondering: how could I use the original code to make a faithful version of classic Rogue for the MEGA65?
In this Digest, we’ll cover how to play the original Rogue on a modern PC by building its vintage source code in just a few easy steps. We’ll look around the code to see how it works, and chart a path to porting the game to the MEGA65. Next month, we’ll return to the Calypsi C compiler, see how to build large projects with the tools, and get a real MEGA65 version of Rogue up and running. Spoiler alert: Rogue is much larger than 39 kilobytes.
The BitBinders MEGA1581 external IEC floppy drive.
BitBinders is now taking orders for the MEGA1581, a MEGA65-themed external 1581-compatible IEC 3-1/2" floppy disk drive. BitBinders has been making 1581-compatible external drives in interesting form factors for several years. This new drive has a case that matches the shape and color of the MEGA65, taking its place literally by its side. The drive includes JiffyDOS and a 12-month warranty.
Craig has shown prototypes of this model at recent computer shows. I have a BitBinders dual drive, and it’s good quality and fun to use. Congrats to Craig on the launch!
As we know, the MEGA65 is just one kind of computer a MEGA65 can be. You can load alternate cores into the MEGA65’s FPGA to run cycle-accurate recreations of other famous microcomputers and arcade game machines, including the Commodore PET, the VIC-20, the Commodore 16 / plus/4, the Commodore 64, the ZX Spectrum, and the TI-99/4A. And the list keeps growing.
Hobbyists have been recreating vintage microcomputers using FPGAs for over a decade, with a great deal of work going into producing high quality cores for the MiSTer FPGA platform. MJoergen and sy2002 created the MiSTer2MEGA65 framework to make porting MiSTer projects to the MEGA65 hardware easier, and this framework is the basis for many (but not all) of the MEGA65 alternate cores currently available. You can also use the framework for all-new recreations, as well as original computer designs, taking advantage of the framework’s well-documented connectivity to the MEGA65 input/output hardware.
MJoergen and sy2002 just launched The Ultimate MiSTer2MEGA65 Porting Guide, a comprehensive step-by-step technical manual for using the framework to bring MiSTer projects to the MEGA65. Many hard-won lessons porting cores are fully documented to support future projects. The guide is highly technical, but also walks through every part of the process of porting a MiSTer core to the MEGA65. I’m looking forward to trying this myself.
Huge thanks to MJoergen and sy2002 for this new book, and for their ongoing contributions to the MEGA65!
Vintage Computer Festival Pacific Northwest 2026.
Vintage Computer Festival Pacific Northwest 2026 took place in May, and the MEGA65 was there! Roguecraft DX was a huge hit, and people also came up to the booth to write BASIC programs and ask lots of questions. The show spanned many decades of computing, with some rare pieces in miraculously working condition available to try. Check out the shared photo album from the event.
I will be bringing the MEGA65 booth to these upcoming events around the United States:
For the sake of running on modern computers, Rogue is distributed as C source code. You can compile it for your PC with the most common C compiler toolchains, with the included GNU Autotools configure script and GNU Make. On Linux, you probably have the tools installed already. On macOS, xcode-select --install gets you everything you need. Windows users have plenty of options (MinGW, Windows Subsystem for Linux, Cygwin), but I don’t have regular access to a Windows computer so you’ll have to find them yourself.
With the appropriate tools installed, and the Rogue source code downloaded and unpacked in a folder, open a command prompt (terminal) window. Change the current working directory to the Rogue source folder, run the configure script, then run make.
This produces the rogue program. To start a game, run the command.
The game runs inside the command prompt (terminal) window. If the program exits immediately back to the command prompt, your window may be too small. Rogue needs the window to be at least 80 columns wide and 24 rows tall. It can be larger, though Rogue will keep itself to the upper-left 80 x 24 area. Already this is good news for a MEGA65 port: we have an 80 x 25 character display we can use.
Each game of Rogue is different. The layout for the entire dungeon, including the locations of treasures and enemies, is generated based on a pseudo-random number generator and a seed value. You begin knowing only about the room where you first appear. The rest of the dungeon is for you to explore and discover, making a map as you go.
You begin in the first room of the first floor of the dungeon, shown in a top-down view. You are the @ symbol. The rectangular room may or may not contain other items, creatures, or a staircase to a lower level. You should see at least one door to a hallway, the + symbol. Hallways (#) connect rooms. (On rare occasions, hallways may have dead ends.)
Rogue is a turn-based action game. To take an action, press a key. For every action you take, other creatures in the dungeon may also take an action. If you’re not pressing a key, the game is effectively paused.
With so many keys and so many characters, it can be difficult to keep track of what everything does or is. Thankfully, Rogue includes a built-in help feature:
The game describes the results of actions at the top of the screen. If you see --More--, press the spacebar to dismiss the message. Other messages are dismissed by pressing Enter; it’ll say so. When the game asks a question, it typically expects a single keypress as a response. Spacebar will cancel the action that prompted the question. Escape can also cancel some prompts or exit some screens. On occasion, the game will prompt for a word or phrase.
There is a way to complete the game. Locate the Amulet of Yendor below the 20th floor of the dungeon, then escape with your life. According to the Rogue user manual, “Nobody has achieved this yet and if somebody does, they will probably go down in history as a hero among heroes.” Of course, some people have indeed achieved this in the last 46 years. But take it as a warning. This feat is quite difficult, and most players just gather as much gold as they can before they are viciously murdered by a bat or a hobgoblin.
Part of the fun of Rogue is figuring it out as you go. I recommend playing it without knowing more about it. For the sake of the porting project, I will describe the game further, but consider the rest of this article to be a mild spoiler. Of course, the source code itself is a complete spoiler for the game’s surprises.
To move, use either the cursor keys (up, down, left, right), or the following keys for all eight directions:
Every item you find can be picked up, and every creature you find can be fought. To pick up an object or fight a creature, simply walk into it.
Keep an eye on your health points (Hp) at the bottom of the screen. If this reaches zero, you die, and the game is over. This will happen often. Health points regenerate gradually over turns not spent fighting. To spend a turn without taking any action (thus regenerating health points), press . (period).
Letter characters represent creatures. Some creatures will chase you, others may leave you alone unless you torment them. When an upset creature is next to you, it may attack you for its action, even if you don’t attack it. Creatures will chase you down hallways to other rooms. They’re relentless.
Other things you might find:
You may also find armor, weapons, magic rings, and other unidentified objects. Wearable objects must be worn to take effect. See the actions list for the appropriate action keys to don and doff items. To see a list of everything in your inventory, press i.
Identifying items without using them is a skill you can pick up later in the game, typically through scrolls. Sometimes, you’ll experience a mysterious effect, and the game asks you to give the effect a name, which the game will use to describe it when it occurs later. To review what you have discovered about objects, press Shift + d.
Some rooms are too dark to see. You can walk through these rooms like any other, but you will only be able to see the area around you, and won’t be able to see walls, creatures, or objects unless you are standing next to them. Be careful!
Defeating creatures earns you experience points (Exp), experience points cause you to gain levels, and each level grants you greater strength and maximum health. These mechanics should be familiar from Dungeons & Dragons, and countless tabletop and video games that followed.
And that’s not even everything in the game! I have yet to actually finish the game or see everything during gameplay. I’ll spoil the last few surprises for myself while digging through the code, but it’s very deep for such a compact vintage terminal game.
David Silva’s code repository for Rogue includes a nice README.md file that describes the structure of the project’s C source files. I won’t repeat it all here, but I will start traversing it in the order that I found useful.
Rogue has 33 C source files (.c), but only three C header files (.h). Nearly all of the declarations that allow functions in one C source file to call functions in other C source files are in a single giant header file, named rogue.h. It’s not that big a project and it probably worked fine for the original developers. Personally, I prefer to keep closer track of how modules depend on each other, and one header per module forces each source file to list all of its dependencies at the top of the file. When I was trying to understand Rogue’s display code to replace it with a MEGA65 alternative, I had to reorganize some of this.
The first order of business is to find the main() function, where the program begins. This entry point is in main.c. Without fully understanding what’s going on, we can get a sense that this routine sets up the game: it initializes global data structures, setting them up with the values they are expected to have at the beginning of the game. The main() function then calls the playit() function, also in this file, to play through all of the actual game.
When the game is over, main() exits the program entirely, back to the command prompt. For the MEGA65 version, it may be easier to return to a title screen and allow the player to start a new game right away, so the game doesn’t have to exit cleanly to the READY. prompt. It is quite charming that Unix Rogue forces the player to re-run the program to play again, one of many ways Rogue blurs the line between the player’s reality and the game’s fantasy.
The playit() function does some more initialization, then drives the turn-based game loop by calling the command() function repeatedly. When the effects of a turn end the game, the code sets the global variable playing to FALSE, which exits the loop.
Why does the playit() function perform more initialization after main()? Why is it a separate function at all, instead of putting the command() loop directly into main()? playit() is actually called from two places in the code: right away from main(), or later after restoring a saved game from save.c in the restore() function. The player can restore a saved game during a turn, so calling playit() again resets a few things to continue the restored game.
Notably, this starts a new game loop inside the first one, as if the entire restored game is happening during a single turn of the previous game. Restoring a saved game results in a call stack where the main() function is waiting on the first playit() call to return, which is in turn waiting on the inner playit() call from restoring the game. As long as the player doesn’t restore too many times in a single play session, potentially overflowing the limited space for the call stack, this works itself out. Once the playing global variable is set to FALSE, every playit() game loop in the call stack exits, finally exiting the program from main(). It’s a cheap design, but it works well enough, especially considering that in Rogue, when you die, the program and its call stack die with you.
There are three ways that a C program uses memory:
Rogue uses dynamic memory directly in a few places. For example, memory is allocated whenever the player assigns a name to a discovered thing. Most of the program’s use of the heap happens in its display library, which we’ll discuss in a moment.
All other game state is managed in global variables. For example, the player’s hunger level is managed by the food_left global variable. This is initialized to the constant HUNGERTIME in the init_player() function. Most of the global state is declared in extern.h and defined in extern.c. The definitions provide some initialization, though all properties need to be initialized in functions anyway because they might be restored from a saved game during play.
Global variables are a challenging software pattern in general because they can cause confusion between modules that have to coordinate how and when the variables change. But they’re common in games, especially vintage ones running on small computers. Modern software patterns mitigate the complexities that global variables cause by spending large amounts of memory and CPU time on coordination, luxuries that small computers can’t afford, and many games don’t need.
There’s a fourth way that C programs use memory, referencing constant (unchanging, non-variable) data built into the program. Some of this, like messages in strings, is managed by C itself. For some constant data structures, Rogue just uses global variables, such as the monsters table that describes various aspects of how monsters behave. If we were writing it today, we’d use the const type specifier to tell both the programmer and the compiler that constant data shouldn’t change during the run of the program. But this feature wasn’t added to the C standard until 1989, years after Rogue was written, so naturally Rogue doesn’t use it, except in a couple of places that were added much later in Rogue’s lifetime.
The game loop takes a player action, attempts to perform the action within the game state, then makes other updates to the game state. The loop returns to the beginning, awaiting the next player input.
Rogue also manages game effects that happen over time. Within the code, these are known as daemons and fuses.
A daemon is an effect that takes place periodically. The game loop gives each daemon an opportunity to do something before the players turn or after the player’s turn. Daemon effects include moving monsters (runners), healing the player over time (doctor), making the player more hungry (stomach), and spawning new monsters at random moments (rollwand). There’s also a daemon that manages a special effect that changes the game’s appearance under special circumstances (visuals).
A fuse is simply a daemon that runs once, then quits. Fuses manage time-limited effects of potions and spells, and trigger the spawning of monsters under certain circumstances.
Rogue can create daemons and fuses dynamically as needed, but only needs a maximum number of them in a regular game. Instead of using dynamic memory, Rogue manages a pool of reserved data space in a table (d_list), then creates and deletes daemons (start_daemon) and fuses (fuse) in the table. The game loop iterates over the table, and performs the before and after actions for each active daemon and fuse as needed (do_daemons, do_fuses).
This is built as an abstraction to make daemons and fuses easy to implement. The program calls start_daemon with a pointer to a function that performs the duties of the daemon, along with information about the daemon’s frequency or duration. The daemon mechanism doesn’t know anything about what the function does, it just knows to call the function during certain turns through the loop. Similarly, the function doesn’t know anything about when it is being called, it just knows how to fulfill its purpose. For example, the doctor() function updates the player state in global variables (pstats) according to the game’s healing rules, and it just does this whenever it is called by the daemon system.
Here’s an excerpt of daemon.c showing how fuses are implemented, edited for space:
The game lights a fuse by calling fuse() with the function to call when the fuse runs out:
The game loop calls do_daemons() and do_fuses() for the “before” phase and the “after” phase, shortening all fuses and calling fuse functions when their time is up:
This causes the game loop to call swander(0); after WANDERTIME turns, before the last turn. (In this case, WANDERTIME is a macro that generates a random number between 56 and 84.)
There is much that is magical about Rogue. One major source of that magic is how the game manipulates the text display to draw the dungeon, player, monsters, objects, messages, and status line. This is all thanks to curses, an early system for building textual user interfaces. The variant ncurses is better known today and widely used. curses is the only software library used by Rogue that is not part of the C standard library, though curses is so common that some version of it is included with most Unix-compatible development environments.
Rogue uses curses for pretty much all of its display needs. One strategy for a MEGA65 version would be to make a MEGA65 version of the curses library. Add a MEGA65 version of the keyboard reading routine, and, in theory, everything else would just work. I spent some time teasing out which features of curses Rogue actually uses. Notably, Rogue does not use the ability for curses to treat windows as scrolling displays. The entire game is designed to fit in 80 x 24 frames.
curses defines the display in terms of windows, overlapping rectangles of characters. Each window has its own cursor, and a program can use the cursor to print characters or strings. When multiple windows overlap, only the frontmost window is visible for a given screen location. curses manages the contents of the windows, and tries to optimize drawing the composite of all of the windows with the fewest terminal operations as possible. Optimization was especially important back when each operation was transmitted to the terminal over slow connections, such as 1200 baud modems over telephone wires. curses windows aren’t like a windowed operating system user interface: they aren’t draggable, and don’t have borders or close buttons. The program can add such features itself by drawing characters into the windows, but it has to manage them itself.
The set of functions provided by curses (the application programming interface or API) is fairly intuitive from this definition: initialize the main window (initscr), create and destroy windows (newwin, subwin, endwin), move windows (mvwin), draw at the cursor (waddch, waddstr), move the cursor (wmove), clear the screen and redraw windows (clear, wrefresh), and variants for combined moving and drawing, and for manipulating the main window. Several functions exist for accommodating terminal-specific features (raw, noecho, keypad, leaveok, getmaxx), which a Rogue-specific MEGA65 replacement can leave out.
I was impressed to see that Rogue uses curses to read characters from the screen as well (inch and variants, getyx). When the player wants to move in a direction, Rogue checks the screen directly to see if a wall character or a creature character are in the way. This is mostly surprising from the perspective of modern day, where a modern program might figure this out from a data structure that represents the entire dungeon, and update the display based on that data structure. In character-based games on computers with limited memory and CPU speed, it’s just easier for the screen itself to be the source of truth for that information. Any wall character on the screen, no matter how it got there, acts like a wall. Plenty of Commodore games do the same thing, especially in short games you find in magazines.
Rogue also reads characters from windows to save the game. With no other data structure representing the dungeon, or the portion of the current level known to the player, the best way to save the game is to take a screenshot. When the player restores the game, it loads the display back into the window. Save files contain much more information than this (see rs_restore_file in state.c), but this is the best way to remember the dungeon itself.
Rogue uses overlapping windows for a few purposes. The message area at the top might contain multiple lines of text, and this text might overlap the dungeon map. So the message area uses an overlapping window in these cases. curses remembers the main window underneath, so when the upper window closes, the display can be restored.
Needless to say, curses is a big deal for Rogue. Back in the day, Rogue was a big deal for curses, perhaps the most popular app to use the library.
There’s a lot of stuff in the original Rogue code specific to the Unix-like environment in which it expects to run. I won’t cover all of this in detail, but we can list a few things to watch out for that would simply be removed from a MEGA65 version:
We also don’t need any of the build automation and conditional compilation that supports building the game for multiple target platforms from the same code base. In theory, we could extend the Rogue code with similar logic for MEGA65-specific extensions, and then someone could use the ./configure script to decide whether to build the MEGA65 version or the Unix version or the macOS version or whatever, all from the same source code. In practice, the MEGA65 version will be drastically different, and, well, it’s not worth my time to make it a multi-platform source repository. I’ll be starting with the Calypsi C MEGA65 starter code and Makefile, and ditching everything out of the original code base that I don’t explicitly need for the MEGA65 version.
There are a bunch of places throughout the Rogue code that use conditional compilation to include or exclude features based on preprocessor variables set in config.h or provided to the compiler by the build automation. One of the more interesting ones is MASTER. If this is set, then the compiler produces a version of Rogue with a special “wizard” mode. When built with this feature, Rogue begins by prompting you for a password. If you know the password, it enables “wizard” mode for the play session.
Wizard mode is a debugging mode for Rogue. It allows the operator to set the random number generator seed (a “dungeon number”), so that random events become repeatable across runs for testing purposes. A wizard can inspect internal game state during play, and is shown more explicit messages about how actions affect the game. A wizard can also step out of wizard mode temporarily during the game, to see what a normal player would see.
It’s especially amusing to me that wizard mode is password protected. Rogue was originally a competitive sport on a multi-user system, and perhaps there was too much of a chance that a MASTER version might leak to the public. In its early days, the authors kept the C source code to themselves, and maybe there was a situation I’m not thinking of where it was important to have a Wizard mode on a public system that only the authors could access.
I might want to keep some debugging features to ensure the MEGA65 version works as intended. But I can drop the code involved in encrypting the password.
Based on a cursory code review, it looks like the fastest way to get Rogue running on the MEGA65 could involve the following steps:
I was able to get a simple curses-like windowing system going pretty quickly, leaving out features like scrolling and display update optimization. The original code made some assumptions about the size of the int type being larger than Calypsi, so I had to make some judgement calls about whether it actually needed long values or was just using int for convenience. Some work was done on the original to put as much platform-specific code into just a couple of files, and I tried to drop those files entirely, either by offering MEGA65 equivalents in higher-level functions or just disabling features.
After a bunch of building and fixing and building and fixing, I got every source file to compile, using this new library instead of Unix curses.
The Calypsi compiler built a bunch of object files, but the Calypsi linker took one look at them and refused to build a PRG file! As we saw last Digest, the default linker configuration from the file mega65-plain.scm is capable of building a program up to 31 KB in size. According to the linker, my version of Rogue needs 112 KB of memory.
The MEGA65 has 384 KB of RAM (not counting its 8 MB of Attic RAM), so there are some possibilities. But I will need to dig deeper into Calypsi’s feature set, so I can tell it exactly how to use the MEGA65’s memory architecture. Each program needs to make its own decisions about accessing memory and working with (or around) the KERNAL, so Calypsi can’t do this automatically. There may be ways to reduce the memory footprint as well.
I’ll also need to consider a boot loader strategy. The default Calypsi configuration accommodates a program up to 31 KB in size, reserving 8 KB for the heap. The DLOAD command could load a runnable PRG file into memory up to 39 KB, at which point the data would collide with the KERNAL if it went any further. To load a larger program, I need a small program that loads the rest of the program according to my needs. In particular, I will need to load code and data into discontiguous regions of memory, so I don’t collide with the KERNAL code and variables related to disk handling—the code that’s loading the program in the first place.
Will I manage to get Rogue working on the MEGA65? Tune in next time to find out!
Boy it sure would be a shame if the Digest got canceled before the next issue. Better support the Digest! Visit ko-fi.com/dddaaannn to find out how.
Same Bat time, same Bat channel!
— Dan
Calypsi C. Dan’s MEGA65 Digest for May 2026.
I enjoy writing assembly language. No seriously, I do. I like learning about how the computer works down to the bits and bytes, registers, clocks, and signals. I like knowing exactly what my program is going to do, and using bare metal debugging techniques to figure out why it isn’t doing what I want it to. I find it meditative to express algorithms in the simplest possible instructions. Writing a program in a low level language requires rigor, patience, and regular breathing exercises. But the end result is quite satisfying, like building a coffee table out of a nice piece of oak.
But sometimes you want to build a house. Systems and structures from construction lumber delivered on a flatbed truck, complex designs that need to be set up quickly and iterated upon to get the right result. A game like Tactical Strike needs intricate sets of rules, both for gameplay and for rendering graphics and sound effects. Expressing those rules in assembly language is unnecessarily tedious and error prone, and doesn’t lend itself well to prototyping and rapid refinement. I need a higher level language that’s good at expressing these structures while minimizing errors.
The most popular step up from assembly language is the C programming language. Especially on microcomputers like the MEGA65, C makes it easy to express structures for data and program flow without giving up bare metal control. To write a program in C, you use a compiler that translates your C program into equivalent machine code. As we saw in Cross Development for Fun and Profit, part 2, an optimizing compiler is often better than a human at writing assembly language, especially for large programs.
In this Digest, we will start a new journey, writing programs in the C language for the MEGA65. We will introduce the Calypsi C cross-compiler by Håkan Thörngren (also known as hth313 on the Discord), and try out some simple examples. We’ll also see how to access the MEGA65 hardware from a C program, and whatever else we may need for small-to-medium-sized projects. In a future Digest, we will follow up with tips and tricks for organizing larger projects that use more of the MEGA65’s capabilities.
Elite, for the MEGA65.
The legendary space trading game Elite (1984) was so popular it was ported to every major platform of the era. Now thanks to xlar54, you can play Elite on your MEGA65.
Elite co-author Ian Bell released the original binaries and source files for multiple versions of the game on his personal website back in 1999, on Elite’s 15th anniversary. Not content with just a straight port of the C64 version, xlar54 combined segments of the original Elite binary code with his own MEGA65 graphics library. Check out the Github repo for details.
Command your Cobra space ship in a fantastic voyage of discovery and
Trade between countless planets, using the proceeds to equip your ship
Black market trading can be lucrative but could result in skirmishes
However you make your money, by fair means or foul, you must blast
While I was writing this month’s feature article about the Calypsi C language cross-compiler tool chain, Craig Taylor (ctalkobt) released a new C language cross-compiler tool chain, which he just calls the M65Compiler. The project’s goal is to specialize in producing efficient MEGA65 programs, able to take advantage of the 45GS02 CPU and abstract away upper memory access. The tool chain includes a C compiler, assembler, linker, and other useful tools. M65Compiler is new, and as of this writing ctalkobt is still building out the standard library implementation.
Maybe in a later Digest we can compare the various C compilers with MEGA65 support. For now, we can support ctalkobt on this project release, and give it a spin. You can clone the project from Github, and build it for your PC with the traditional GNU build tools.
MJoergen and sy2002 are back, and they’re making big improvements to the C64 core. In particular, they’re responding to community requests for greater compatibility with unusual cartridges. Other enhancements include the ability to use the simulated REU in tandem with both hardware cartridges and CRT ROM files. This improves support for configurations used by some modern games, utility cartridges, and C64 OS.
See this Discord post to download the latest alpha release and help test. We could especially use more testers with access to unusual hardware cartridges. And of course, watch the skies for the upcoming formal release of C64 Core version 6.
Calypsi is a cross-development tool chain for writing C programs for multiple vintage and retro computing platforms. The tools are closed source, and free to use. By the time we’re done here, you’ll want to support the project with a donation, so keep that link handy.
The tool chain is available for several target platforms: the MOS 6502 and MEGA65 45GS02, the WDC 65816 with support for the Wildbits F256 (née Foenix F256), and the Motorola 68000 with support for the Foenix A2560 and the Amiga. There’s also a version of Calypsi for the HP-41 Nut and NEWT processors for the HP-41 series of calculators, which I’ll be excited to try another time.
For the host platform, Calypsi is available for macOS, Linux, and Windows. These are cross-development tools: you author your project on your PC, use the tools to build the PRG, then transfer them to your MEGA65 (or the Xemu emulator) to run it.
Download the 6502 version of the Calypsi C compiler for your host platform:
The user’s guide has detailed installation instructions for each platform. For macOS users, I will add my obligatory rant about unsigned binaries. When you double-click on the .pkg file, it won’t open: macOS will complain that it can’t verify the file. Click Cancel; resist macOS’s desire to throw it in the trash. Open System Preferences, Privacy & Security, and scroll way down to where it says “‘calypsi-6502-5.17.pkg’ was blocked to protect your Mac.” Click “Open Anyway,” then click “Open Anyway” in the dialog that opens. Follow the prompts to authenticate and install Calypsi C.
You may need to adjust your command path for your platform. On macOS, Calypsi installs to /usr/local/bin/, which is typically already on your command path. You can invoke the cc6502 command to test it, like so:
It’s worth locating the Calypsi home directory. In the case of macOS, this is /usr/local/lib/calypsi-6502/. There’s fun and useful stuff in here.
Don’t want it any more? Run this script: /usr/local/lib/calypsi-6502/uninstall
Let’s start with a classic. Create a C source file named hello.c with the following contents:
Even though there’s almost nothing about this program that’s specific to the MEGA65, Calypsi will convert this to a MEGA65 PRG. Indeed, that’s one of the reasons the C language exists in the first place. With some constraints, a C program can be written in a way that it behaves as intended no matter what computer you run it on, as long as you have a C compiler for that computer. The language (in theory) is portable.
You may have noticed that the iconic message HELLO WORLD! uses uppercase letters. This appears as a matching all-uppercase message in Commodore’s default uppercase text mode, but there is some nuance. We’ll take a closer look in a moment.
There are two phases to converting a program from C source code into the target computer’s machine code. The first phase is compilation, performed by a compiler. The compiler takes C source files (.c) as input and produces object files (.o) as output. Try this now, with the Calypsi cc6502 command:
This produces the object file hello.o. An object file contains a mix of machine code for the target platform, in this case the MEGA65, and information about how the source file is organized. In this case, the source file contains a definition of a function named main() that takes no arguments and returns a value of type int. The object file represents this fact for later use. A project can have multiple C source files, and each source file is compiled to its own object file.
The second phase is linking, performed by a linker. This takes all of the object files and sews them together to make the final machine code program. Run the Calypsi ln6502 command on our single object file to build the runnable MEGA65 PRG file:
The mega65-plain.scm file is a configuration file that is included with Calypsi. It tells Calypsi how to build simple MEGA65 programs. We’ll cover this bit in a later issue of the Digest; for now, just know that it is required for Calypsi’s linker.
The linker produces the file hello.prg that you can run in the Xemu emulator or on real hardware. See the earlier Digest articles Cross Development for Fun and Profit, part 1 and part 2 for a reminder on how to get PRG files from your PC to your MEGA65. With Xemu, you can start the emulator, then drag and drop the PRG file onto the window. Alternatively, you can invoke Xemu from the command line such that it loads and runs the hello.prg program, like so:
Either way, Xemu starts up the MEGA65 ROM, injects the PRG file into memory, then invokes the RUN command. HELLO WORLD! is the output of the program. On real hardware, you won’t see the address advisory on the screen. Xemu prints that message itself when it injects a PRG.
Managing a project of multiple source files is one of the strengths of the C language. Once you have lots of files, you’ll want a better way to invoke the compiler and linker for the entire project without typing the commands out every time.
By far the most popular way to do this is with GNU Make, a build automation suite. The make command is included with Linux, can be installed on macOS via the Xcode Command Line Tools (run this command to install: xcode-select --install), and is available for Windows in various ways.
Once you have make installed, download the Calypsi MEGA65 starter project. Unzip the file to create a project directory. The most useful file here is Makefile, which knows how to invoke the compiler and the linker with all of the appropriate arguments. Change your command shell’s current working directory to the project, then run make:
The starter project keeps its C source code in the src/ subdirectory. The file main.c is identical to our hello.c, except for two additional lines:
This #pragma line tells Calypsi to inject additional code that stashes the contents of the internal memory of the BASIC interpreter before the beginning of the program, just before calling main(), then restores them after the end of the program, just after return 0;. This is handy for playing with common examples of C programs that you might find in books, so you can run a program, then get back to a READY. prompt, so you can run it again, or inspect the state of the system with the MONITOR. We didn’t need it in hello.c because the program was very simple, but other programs benefit from this #pragma if returning to the READY. prompt is important.
The C language was originally written for the Unix operating system, which has powerful features for running programs as commands at the command prompt, also known as the “command shell” for how it acts as an intermediary between the program and the operating system. The MEGA65 does not have such a command shell, so some of these features are limited. For example, on a Unix-like system, return 0; returns an integer (int) value of 0 from the main() program to the command shell, indicating that the program completed successfully. On the MEGA65, changing this to a non-zero value (255) has no effect, whereas a Unix-like command shell would report the return value to the user as an error code.
Of course, your program is not obligated to return to the READY. prompt, and many Commodore programs don’t. Programs like games change too much system state to be able to return to BASIC cleanly. Your finished program may opt to just never exit, like a game that displays its main menu after a “Game Over.” During development, you might have a point in the program that intentionally enters an infinite loop:
Calypsi allows the main() function to have a void return type, so control flow can reach the end of the function without an explicit return statement. With an int return type, main() must return an integer, or never return.
Like nearly all example C programs, these examples print messages to the screen. When Calypsi encounters a character or string literal value in the source code, it uses the ASCII encoding to convert the characters to bytes for the final program. Commodore computers use PETSCII, not ASCII, so a little bit of care is needed.
Every classic introduction to the C language starts with a program that calls the printf() function in the C standard library, like so:
By default, Calypsi interprets this string of characters as ASCII codes, and has the MEGA65 print those codes directly. The MEGA65 interprets them as PETSCII, and the message that appears on the screen doesn’t look like it does in the source file.
The first character in the message is an uppercase H in the source code. In ASCII, this is code 72 (in decimal). In the Commodore default text mode of uppercase and graphics characters, code 72 is an uppercase H, so this appears correctly.
The second character in the message is a lowercase e. In ASCII, this is code 101. The PETSCII character 101 is one of the vertical bar graphics characters—actually Shift + E on the keyboard. In uppercase-and-graphics text mode, all of the ASCII lowercase characters will appear as PETSCII graphics characters, which isn’t what this code intends.
The easiest thing to do is make all of the letters uppercase, and assume the program is starting in uppercase-and-graphics text mode:
Now the letters appear on screen like they do in the C source code: HELLO WORLD! But what if we really want it to say Hello world! in mixed case?
The default uppercase-and-graphics text mode doesn’t have lowercase letters, so we need to switch to lowercase-and-uppercase mode. There is a PETSCII code for this, code 14. In BASIC 65, you might PRINT CHR$(14) to achieve this.
In C, you can use the special sequence \x followed by the hexadecimal representation of the code, in this case 14 = $0E. It looks like this in the string:
Now all of the uppercase letters in source appear as lowercase on screen: hello world! That’s better, but we want the H to appear in uppercase.
PETSCII keeps its uppercase letters at codes where ASCII keeps its lowercase letters, and vice versa. One way to capitalize the H in the final PETSCII message is to use a lowercase h in the source code:
Mission accomplished: Hello world! Personally, I’ve gotten used to this when using tools that don’t know PETSCII, but it is a bit odd looking and difficult to remember.
Calypsi v5.17 has a simple feature to help with this. If you give the compiler the command line option --string-literals=shifted-petscii, Calypsi will swap the letter casing in string and character literals. Add this to your Makefile, or use it when running the cc6502 command:
Now, as long as you’re printing the PETSCII code to switch to lowercase mode, you can use proper letter casing in the source file, and get the intended result.
You only need to switch to lowercase mode once, and all characters on the screen will appear in this mode. You may also want to emit code 11 (\x0b), which locks the ability for the user to switch modes by pressing Mega + Shift.
The canonical C Hello World program ends the message with another special sequence, a backslash and a lowercase N: \n. The backslash tells the compiler that the next characters are an escape sequence, representing bytes that can’t be typed into the string and still conform to C syntax. There are escape sequences to represent non-typeable ASCII control codes, raw byte values, and the C string syntax characters single quote, double quote, and backslash itself.
Most C programs intend \n as a way to move the cursor to the beginning of the next line. The history of this maneuver on teletypes and screen terminals is fraught, to say the least.
In a typewriter mechanism, moving the cursor to the beginning of the next line is two actions: one action to feed the paper up by one line, and another to return the carriage all the way to the right, so that the next typing position is in the first column. Manual typewriters had various designs for this over the years, but the one that’s most familiar to me is a big lever on the carriage. Pushing the lever a little bit feeds the paper up by one line, and shoving the lever the rest of the way with some force returns the carriage to the first column. One big push performs both actions.
The ASCII standard assigns separate terminal control codes for each of these actions: the line feed (LF) is code 10, and the carriage return (CR) is code 13. Some people like to tell a story of how this was necessary to accommodate the first mechanical teleprinters to support ASCII codes, such as the Teletype Model 33. As the story goes, the Teletype needed to keep up with characters fed to it at a certain speed. But in the time that it takes for the teletype to perform a “carriage return,” moving the print head back to its home position, it might receive another code. If the Model 33 received a printable character while it was moving the print head, it would smear the character wherever the print head was at the time. With CR and LF as separate codes, in that order, the teletype could perform the line feed while it is still moving the print head for the carriage return.
This story is somewhat apocryphal, at least with regards to ASCII. The CR and LF codes pre-date ASCII—and the automated teleprinters that would use them.
In 1833, physicists Carl Friedrich Gauss and Wilhelm Eduard Weber invented the first electromagnetic telegraph. They constructed a communication line between the Göttingen Observatory with the institute for physics in the town center.
From 1837 to 1844, Samuel Morse, Joseph Henry, and Alfred Vail developed Morse code, a method of encoding textual messages over early commercial telegraph lines. Each line carried a single binary state (on or off), and Morse code used pulse duration and pauses to encode messages. By this point, inventors were already developing mechanisms to record received signals on paper tape, and speed up the transmission and receive process performed by human operators. Morse’s original code transmitted only numbers, to correspond to entries in a code book. Vail expanded the code to include letters, numbers, and special characters that would spell out full messages. The single-bit nature of the code made it useful in other contexts, such as radio transmissions.
From 1870 to 1876, French telegraph engineer Émile Baudot sought to improve on the speed of transmitting Morse code specifically over wires. He originally imagined a six-bit code, entered by an operator using a six key chorded keyboard, sent in parallel over six lines. Based on an idea from Gauss and Weber themselves, Baudot reduced this to five keys and five lines by splitting the six-bit code space into two five-bit code spaces, with reserved “shift” codes to switch between them mid-message. The five-bit Baudot code kept letters in the first set, and numbers and symbols in the second set. To make the most out of using up five lines, transmission workstations would seat four operators simultaneously, and a motorized mechanism would multiplex the messages on a rhythmic cadence.
Converting between text messages and codes was a specialized task, and entering codes by hand was arduous. In 1901, Donald Murray proposed modifications to Baudot code to accommodate typewriter-like keyboard entry on the encoding side, and eventually mechanized typing on the decoding side. Murray’s system performed the encoding and decoding offline: the transmitting operator used an alphanumeric keyboard to prepare messages as 5-bit codes on paper tape to be transmitted, and the receiving end would punch the codes back onto paper tape automatically, to be decoded and re-typed as text later. Murray imagined that the receiver could recreate full documents based on these encoded instructions, so he included the first-ever control codes in Murray code: one for carriage return, and one for line feed.
Both Baudot code and Murray code were in active commercial use on competing networks. With multiple codes in play, the International Telecommunication Union committee on long-distance telegraphy, the CCIT, advocated for standardization at their 1st Plenary Assembly in 1926. The Baudot code was adopted as International Telegraph Alphabet Nr. 1 (ITA1). At the 2nd Plenary Assembly of the CCIT in 1929, the ITA1 standard added carriage return and line feed, inspired by the Murray code. The revised ITA2 was standardized in 1932.
The ASCII standard, published in 1963, had teletypes in mind, but also data storage, computation, network transmission, and additional input and output devices. ASCII was a 7-bit successor to the 5-bit ITA2, replacing Baudot’s shift mechanism with a dedicated character bit, and re-ordering codes for collatability, sorting letters alphabetically and numbers numerically. To facilitate the transition to ASCII, the Western Electric TWX network needed to support both ITA2 and ASCII simultaneously. TWX translation bridges used a real-time signal converter that could only convert single codes, and could not translate two ITA2 codes into a single ASCII code. So ASCII kept CR and LF as separate codes.
Early computer mainframe operating systems, especially those by the Digital Equipment Corporation (DEC), stored both CR and LF codes as line endings in data files, and sent them verbatim to teletypes and terminals. Later microcomputer operating systems such as CP/M and MS-DOS retained this convention. Meanwhile, Multics took a different path, establishing LF alone as sufficient to end a line, for more optimal use of memory and data storage. Multics systems would translate LF to CR+LF only when sending data to a terminal that needed it. Unix and several others—notably the Amiga—followed Multics. Commodore 8-bits, older Macs, and others decided CR was sufficient for their micros. So much for standards.
Having originated on Unix, the C language specifies that the \n escape sequence means the single code for line feed, ASCII code 10. To reconcile the divergent standards across platforms, the C standard library insists that the program declare whether it is reading from or writing to a terminal or data file in “text mode” or in “binary mode.” In text mode, a platform-specific implementation of the standard library translates between \n (code 10) in program memory and the platform-specific line ending received from or sent to the device.
Starting with version 5.17, Calypsi offers text mode newline translation from the standard library for Commodore targets. When your program uses printf() and the like to send code 10 to the screen terminal or a text file, it is output as code 13, and everything works as intended.
The printf() function is provided by the C standard input/output (I/O) library. The program declares its intent to use this function with this line:
Calypsi includes an implementation of the C standard library that interfaces with the Commodore KERNAL for terminal and disk operations. The Commodore KERNAL doesn’t support all of the same features as a Unix-like system, so this is a best effort. You’ll find good support for examples from beginner books on the C language.
Here is a program that prints the dimensions of a rectangular prism, prompts for its width, then calculates and reports its volume. You could compile and run this program on nearly every computer with a C compiler.
When you use the C standard library in your program, Calypsi tries to optimize the PRG file to contain only the parts that you use. This can still be quite a bit of code, even for small programs. With space optimization enabled (the default), this example compiles to a PRG of 12,831 bytes. Considering that a simple MEGA65 program—one that doesn’t use memory mapping or disk loading tricks—has to fit in 40,958 bytes, that’s quite a commitment of space.
If I drop the scanf() call from this program and replace it with a hard-coded width, such as width = 5;, my PRG size drops to 4,936 bytes. Calypsi sees that I don’t need any of the scanf() support code and leaves it out.
Here is an example of reading data from a file, using the standard library:
Given a SEQ file on the currently mounted disk in drive unit 8 named EXAMPLE.DAT, this reads that file (up to a maximum of 1,024 bytes) and stores it at address $7000.
The C standard describes its fundamental data types in terms of minimum bit sizes. Especially on microcomputers and embedded systems where memory is at a premium, it’s important to know exactly how much memory the compiler uses for each C type.
The three integer value types are char, int, and long. You’ll be using these most often.
char is an 8-bit (one byte) value. It can be signed or unsigned. If the type just says char, it can store either signed or unsigned values. Signed values are stored as Two’s complement, the most popular method that assigns exactly one value to each of the 256 possible representations.
int is a 16-bit (two byte) value. It can also be signed or unsigned. On the MEGA65, multi-byte integers are stored Little Endian. This means you can use these types to write to multi-byte registers without issue.
long is a 32-bit (four byte) value. There’s also a long long type that is a 64-bit (eight byte) value.
Obnoxiously, the integer sizes are merely minimums in the standard. A program using an int might get 16 bits on one platform and 32 bits on another. Calypsi implements the C99 standard, which includes the stdint.h standard library. This provides a set of type specifiers for integers that lock in their sizes. I always use these. It just overloads my brain to think the compiler might be giving something I don’t expect.
Calypsi supports the C language floating point value types float and double, which can store small fractional values or very large values with limited precision. As with other Commodores, the MEGA65 does not have a math co-processor, so all floating point math is implemented by Calypsi as software, similar to BASIC 65. This means floating point math can be disappointingly slow. Unless you really need it, you might prefer to use the integer types and implement your own fixed-point math. For example, it is highly recommended in computing to represent amounts of money as an integer number of cents (1599) instead of a fractional number of dollars (15.99), because you don’t want floating point imprecision to accidentally round amounts of money up or down in a transaction.
The C99 standard provides a Boolean (true/false) value type in the stdbool.h standard library. This library adds bool as a type and true and false as value macros.
Playing with the C standard library is fun, but it won’t be long before you’ll want to access MEGA65 hardware registers from your C programs. This is easy enough with C pointers.
Here’s a simple example of assigning the address of the VIC border color register to a pointer variable, then using it to change the border color.
You can use scarier-looking C syntax to avoid the variable:
Notice that this short program doesn’t need any #include statements or the #pragma that supports returning to BASIC. This compiles to a PRG file of 545 bytes. Of course, the equivalent assembly language program might only be a couple of dozen bytes. The compiler includes a bit of overhead for setting up general purpose C programs. You might be able to reduce that with changes to Calypsi’s configuration, but you usually won’t need to.
We can also read from a register in a similar way. The following program cycles the border color as long as the Mega key is pressed.
It’s a best practice to use the volatile keyword for all pointers to registers. This tells Calypsi that the hardware at that address may not behave like regular memory, so the optimizer shouldn’t assume that it does. For example, if a hardware register is connected to the joystick port, reading it multiple times in a row might return different values as the player pushes the joystick around. If Calypsi believes that the address is pointing to regular memory, it might see two attempts to read it without any attempt to write to it in between, and think “Oh, that memory doesn’t change, so I won’t bother to read it twice.” volatile tells Calypsi to preserve every attempt the code makes to read and write at that address.
You can learn a lot about a compiler from the machine code that it generates. Even for very short programs, Calypsi generates enough preamble and postamble that it’s difficult to figure out what’s going on from running the PRG through a disassembler. Calypsi is more than happy to help. Give the compiler the --list-file argument, and it will create a text file for each C source file, explaining everything it figured out about how to compile the C code to assembly language.
The starter project already has this argument in its Makefile. As written, the build creates .lst files in the obj/ directory, alongside the .o files.
Let’s consider the shortest program so far, changing the border color:
Put this in main.c, then build (make). Open the obj/main.lst file that it creates in your text editor.
For the sake of brevity, let’s just look at the assembly language instructions themselves:
Nice! This is pretty much what we hoped to see: a concise expression of the code in equivalent assembly language. It uses 0x instead of $ as the prefix for the hexadecimal address d020, but otherwise the code is familiar. The code for return 0; is instructive. It looks like main() stores its return value in a zero page variable before returning to the outermost support code.
Earlier we put the border address in a variable to make the code easier to read. Does this change what machine code gets generated? Let’s try it:
It looks like yes, Calypsi cautiously decides to store the address in a zero page location, then uses indirect addressing.
Try tweaking the C source code and re-build the list file to get a sense of how Calypsi makes decisions.
The linker can also take a --list-file argument, and the starter project includes this as well. In addition to the hello.prg file, it produced a file named hello-mega65.lst. This will come in handy when we take a closer look at how Calypsi manages memory, in a later Digest.
The MEGA65 I/O registers always have the same addresses and structures. It would be nice if we just had a pre-made list, so we can access all of these registers by name. Well, good news! Discord user wombat (Github user mlund) created such a library for the llvm-mos-sdk project, another C tool chain with MEGA65 support. Via the open source license, Calypsi includes this library as well.
We can rewrite our border color example using the provided structures, like so:
The mega65.h library provides structures for the VIC-II, VIC-III, and VIC-IV register layouts, as well as the SID chips, Hypervisor, the 6526 CIA chips, and the 45E100 ethernet controller. The library also includes bit masks for registers that only use certain bits of a byte register. There are no fancy abstractions on top of the registers. This is just a way to give the registers a convenient set of names for use in programs.
The best documentation for the register structures is the header files themselves. Browse your Calypsi files to find them. On macOS, they are under this path:
Reading these files requires a modest understanding of C structure definitions. The main thing to notice is that mega65.h provides top-level macros that refer to the register structures from their actual addresses, so the compiler doesn’t bother with pointer variables and accesses memory directly. VICIV.bordercol = 5; is equivalent to: *((char *)0xd020) = 5;
Structures like these also empower code editors to make these easy to find interactively, without having to look up or memorize addresses. Make sure your IDE knows about the .../include/ directory that contains mega65.h. For example, if you’re using VS Code, add the file .vscode/settings.json to the workspace with this setting:
Now when you type VICIV. in the editor, it shows all of the named VIC-IV registers, with their value types. Some of these have inner structures, which you can also browse in the same way.
So far so good. But we won’t get far piling all of our code into main.c. Let’s look at a quick example of a project with multiple source files.
If the entire program is in a single C source file, the compiler can figure out everything it needs to know to generate all of the program’s machine code. Each function becomes a machine code subroutine, with additional code to handle argument passing and return values. Variables are reserved memory. The addresses for the subroutines and variable memory are determined by the linker when it calculates the final program.
A C compiler only considers one source file at a time. This might feel a bit odd compared to modern languages, but modern languages run on modern machines with tons of memory and ultra-fast CPUs. C compilers were designed to run on mainframes and microcomputers with limited memory. By separating the compilation and linking phases, the intensive compilation process only needs to consider a small piece of the program at a time.
To do this, the compiler needs help understanding when a function or variable being referenced in one C source file is actually provided by another C source file. It doesn’t need the full definition to understand the reference, it just needs to know the name and the value types. You can provide these in a declaration.
Try this. Create a C source file named numbers.c in the src/ directory with definitions for these functions:
With the starter project, we also need to tell the Makefile about the new module so it gets compiled. Edit Makefile and change the C_SRCS line, like so:
Next, write the following in src/main.c:
Try building the project with the make command.
When Calypsi compiles main.c, it prints two warnings: “implicit declaration of function” It doesn’t know anything about the definitions of these functions in numbers.c when it is compiling main.c. By default, it assumes you know what you’re doing, and that these functions will show up later in the linking process. No offense, but this is a generous assumption. It would be much easier for us if the compiler would report undeclared functions as errors. If we mistype a function name, it shouldn’t assume that it’s a real function.
Moreover, these implicit declarations interfere with another of the compiler’s important tasks: type checking. It happens to be the case that the twice() and thrice() are functions that accept an int-type value as an argument and return another int-type value. These “implicit declarations” assume as much, but these assumptions won’t always be correct. We want the compiler to catch us if we accidentally try to provide an argument value of the wrong type, or use a return value as a wrong type.
Older versions of C allow these error-prone implicit declarations. In the 1999 version of the C standard (C99), implicit declarations are specified as warnings. A standards-compliant C compiler is obligated to alert you when they happen, but the build still proceeds under the old assumptions.
Warnings are bad, and if you’re starting a new project, it’s a good idea to take them seriously. The best way to take them seriously is to tell Calypsi to treat all warnings like errors and halt the build. To do this, provide the -Werror command-line argument to the compiler. I recommend adding this to your Makefile.
Let’s try declaring these functions in main.c. Add these lines to main.c above the definition of the main() function:
These declarations inform the compiler that if it ever sees code that uses the twice() or thrice() functions, it can safely assume that the definitions will be linked in later. And the compiler has the value type information it needs to check our work. If the code tries to use a function named “qwice,” that’s probably a mistake.
Try building again. The warnings are gone, and the program builds successfully.
In practice, it’s a pain to put declarations of every function at the top of every C file that uses them. C has a better way. This is what the #include statements we’ve been using are for: they pull in all of a library’s declarations into the compilation from a separate file. This type of file is called a header file for how it gets included at the top of each source file, and has a .h filename extension.
Remove the declarations from main.c. Instead, create a file named src/numbers.h, and add the declarations there. While you’re at it, use source comments to give these functions documentation on their use.
Add this line to src/main.c just below the #include statement:
The #include statement uses quote marks ("numbers.h") instead of angle brackets () to indicate that the header file is among our project’s source files.
Why not just #include the numbers.c file and nevermind this declaration nonsense? The #include directive is doing something very simple: it is inserting the contents of one file into another. The C compiler only sees the result, which in this case would be as if we defined all of the numbers functions in every C source file where they are used. The compiler blindly generates code for each definition in each object file. When the linker tries to stitch all of the object files together, it finds multiple definitions for each of the functions, and doesn’t know which ones to use. There can only be one definition for a function across all of the object files.
Declarations give the compiler enough information to validate that externally defined functions are being used correctly. By providing all of a library’s declarations in a header file, users of that library can #include the header file and use the library’s functions safely. As a matter of convention, header files also provide human-readable documentation for how to use the library, without having to read the library’s C source code.
In summary:
Back in the July 2023 Digest, I recommended the two most popular books on learning C programming: The C Programming Language, 2nd edition (1988), co-written by Brian Kernighan and the C language’s designer Dennis Ritchie, and C Programming: A Modern Approach, 2nd edition (2008), by K. N. King. As much as I love K&R, I prefer King. It’s clear, thorough, and highly interactive, with practical examples, exercises, and advice for aspiring programmers. I’ve read it cover to cover and have completed every exercise. It’s great. Don’t miss the official website for the book, including downloads for the example programs.
We’re living in an age of unreasonable textbook prices, and I wouldn’t blame anyone for preferring free on-line resources. Thankfully, there are plenty. Harvard CS50 is a full course that introduces programming partly (but not exclusively) through the C language, with text and video. The C Book (1991) by Banahan, Brady, and Doran is a complete introduction, released freely online by the authors. Beej’s Guide to C Programming has great technical coverage and plenty of examples, also free online. The comp.lang.c FAQ is a brain-tingling supplement to learning the language, though it’s not a starting point.
There are also excellent references for the language and standard library, worthy of a professional investment in the language (Harbison/Steele, Plauger, Hanson). If your interest is in programming for the MEGA65, I would hold off on these. Only a subset of the standard library is available, and anyone writing a game or large application for the MEGA65 is likely going to ignore the standard library and prefer custom libraries and direct hardware access, to best take advantage of the computer’s features—and to fit programs into the MEGA65’s limited memory resources.
OK! That’s quite a bit for a “part 1.” With not much more than a few files, we know enough to use the MEGA65 as a platform for learning the C language from common textbooks, and to get started controlling MEGA65 features from C programs. We’re currently limited to the first 64 KB of the MEGA65’s memory, both for storage space for the compiled program and for allocating memory for data and variables.
To grow beyond this, we’ll need to learn more about how to steer the Calypsi tool chain, how Calypsi manipulates the MEGA65 behind the scenes, and how to mix C and assembly language. In the next Digest, we’ll dive deeper into Calypsi’s feature set and see how to do common useful things on the MEGA65.
This Digest is made possible by readers like you! Thank you to everyone who has supported the Digest with one-time and recurring donations. If you’d like to see more MEGA65 articles like these, consider becoming a supporter. Visit: ko-fi.com/dddaaannn
C you later!—nope, nope, not doing that. Uh, just have a good month!
— Dan
Pixie Power. Dan’s MEGA65 Digest for April 2026.
The MEGA65’s Raster Rewrite Buffer allows programs to draw characters on top of other characters, with the option to treat certain pixels as transparent. This enables all manner of graphical effects and techniques involving layers. In the previous Digest, I was able to use the RRB to sketch the user interface of a military strategy game, with terrain, soldiers, vehicles, indicators, and user interface elements in a 20-by-12 grid of 16-color 16-by-16 tiles, all in overlapping layers. Each tile is drawn separately, so it’s easy to imagine that this game could move the game objects across the terrain, like moving pieces across the spaces of a board game.
In this Digest, we’ll look at more features of the Raster Rewrite Buffer that open up even more possibilities. Specifically, we’ll see how to position layered game objects not just in a tile grid, but in arbitrary pixel positions on the screen. This is similar to VIC-II hardware sprites, but managed entirely by the program manipulating the RRB. Graphics programmers sometimes call techniques like these “soft sprites” to distinguish them from sprite features built into the video chip. Years ago, the MEGA65 community proposed that we call them pixies.
The power of pixies, coming up after these project announcements!
Dark Reaper, by MirageBD.
Dark Reaper is a new game by long-time MEGA65 contributor Lars Verhoeff (MirageBD), with an impressive smoothly-animated 3D maze engine, textures and lighting that really show off the MEGA65’s dynamic range, and atmospheric music and sound. Collect keys, open doors, find secret passages, and above all: avoid the Reaper. The map is essential to finding your way around and not getting caught.
DOS/65, MEGA65 port by Stefan Eilers.
The original DOS/65 was a CP/M workalike for the 6502 processor, by Rich Leary in 1982. Stefan Eilers has ported it to the MEGA65. You can download it from Filehost. The filesystem is compatible with CP/M version 1.4 and later, though of course a 6502 computer cannot run 8080/Z80 programs.
Don’t miss Stefan’s documentation, and the Github repo.
MegaPC, an 8086 emulator by xlar54.
Extending his emulator series, xlar54 has published an 8086 PC emulator capable of running MS-DOS, with a monochrome display and two virtual floppy drives.
You’ll need a DOS boot disk. Popular DOS apps work, though not always at full speed. See the Github repo for more information.
MegaSweeper65, by Hanyic Róbert.
I have been illustrating the most recent Digest articles on RRB graphics with a sketch of a game I’ve been calling “Tactical Strike.” Several people have asked to see these demos in their full form, beyond the excerpts I’ve been including in the newsletter.
You’ll have to forgive the crudeness of this model, I haven’t had time to paint it or to build it to scale. But if you’re interested:
The tactical.d81 disk image includes several display demos written in BASIC 65, along with the Tactical Strike tileset (NCM characters and palette) as data files. Each demo sets up the 20 x 12 tile display in the same way, as described in the previous article.
DS-MEGA65 is a version of DISPLAY3 written in assembly language. None of these demos strictly depend on features of BASIC65—it’s all register and memory manipulation—though I do use commands like WPOKE and SETBIT to illustrate the register concepts. This assembly language version also simplifies the code by embedding the tileset data directly in the program. Instead of loading the tileset into bank 4 from disk, I just tell the assembler to include it with the program at address $2800 in bank 0, and refer to those addresses in screen memory directly.
Someone asked me if a program running in GO64 mode could use these features. It sure can! You just need to switch the VIC from VIC-II compatibility mode into VIC-IV mode, like so:
The only other difference between d3-mega65.asm and d3-go64.asm is the start address, and the corresponding address in the BASIC bootstrap preamble. The rest of the code is identical. To try the GO64 version, just load and run it like any other program in GO64 mode.
So far, I’ve been drawing the tiles as they appear in the tileset. In the case of soldiers and vehicles, everyone is facing to the right. That’s not too bad given the board-game-like style of the display, but in the real game each team is going to start on opposite ends of the field and advance toward each other. Without changes, a soldier marching right to left will look like it is walking backwards towards the enemy team.
Of course, one possible solution is to have two tiles for each soldier, one facing to the left and one facing to the right. But the VIC-IV can help us out here. In the common case where we just want the same image flipped horizontally, we can just ask the VIC-IV to use the same graphic, and draw it flipped.
To draw any SEAM character flipped horizontally, set bit 6 of byte 0 of its color value.
Tactical Strike tiles are one NCM character across (16 pixels) and two characters tall (8 x 2 = 16 pixels). To flip the tile horizontally, Tactical Strike sets bit 6 of byte 0 of the color value for both characters.
Unsurprisingly, the VIC-IV can also flip a SEAM character vertically. To do so, set bit 7 of byte 0 of its color value.
If Tactical Strike had a use for flipping a tile vertically—and perhaps it doesn’t given the style of the game, but let’s just consider—notice that a tile is two NCM characters stacked vertically. To flip the full tile vertically, it would need to set bit 7 of byte 0 of the color value of both characters, and it would have to swap the characters themselves, so the bottom character is on top and vice versa.
Here’s a trivial example of drawing a left-facing red soldier at a pre-calculated screen location:
Here’s a brief review of where these numbers come from:
See DISPLAY7 on the disk. What changes would you make to flip the soldier upside down?
Tactical Strike uses the Raster Rewrite Buffer to draw game objects onto the terrain. To do this, it populates the beginning of a row of screen memory with terrain characters, then uses a GOTOX instruction to reset the draw position to the game object’s X position. This instruction is followed by the character for the game object itself, which is drawn on top of the terrain at the new X position.
I wrote my tile drawing routine to keep all tiles arranged in a 20 x 12 grid. For example, to draw a red soldier in the fifth column on a row, the routine could use a GOTOX instruction to set the draw position at X = (5 - 1) * 16 = 48, the pixel coordinate of the left-hand edge of the fifth column.
When the player moves the soldier, I want to animate the soldier marching across the field. I could just have him clomp across the grid locations, leaping 16 pixels at a time. That has a nice retro feel to it, but it’s not the only option. GOTOX takes a pixel coordinate, not a grid coordinate, so I could slide the soldier left or right by changing the GOTOX value a small number of pixels per animation frame.
For example, I could slide a soldier from one tile column to the next by one pixel per frame over 16 frames. In NTSC video mode at 60 frames per second, that would take 16/60 = 0.26 seconds. In PAL video mode at 50 frames per second, that’s 16/50 = 0.32 seconds.
See DISPLAY8, which animates a patrolling soldier, including horizontal flip.
Last month, we discussed several different strategies for using GOTOX to draw layers, including drawing full layers with “GOTOX = 0” instructions between them. Notice that to animate individual game objects, each object needs its own GOTOX instruction: each row starts with a full screen-width of terrain, followed by a “draw list” of graphics objects, each object using one GOTOX instruction to set its position followed by the graphics character (or characters) for the object. This provides enough flexibility to locate each object independently from other objects on the row, at the expense of some screen memory.
This is the general idea behind pixies. The program draws the background layer (or layers) into screen and color memory, one row at a time, followed by GOTOX instructions and characters for each pixie that appears on the row. Pixies can overlap, and must appear in screen memory in their desired order, from back to front. The VIC draws the entire row—background and pixies—by reading each row of screen memory from left (lowest memory address) to right (highest memory address), following all of the “draw a character” and “GOTOX” instructions in the order they appear.
Positioning pixies at arbitrary horizontal positions is pretty straightforward: just give the GOTOX instruction the desired pixel coordinate to start drawing the next character. The VIC draws the full width of the character from the left edge to the right edge, potentially drawing it across what we normally think of as two character columns—or tile grid columns in the case of Tactical Strike.
You can also use the Raster Rewrite Buffer to position pixies at arbitrary vertical locations. This takes a bit more effort. We want something similar to horizontal positioning, where a character is drawn across two character rows, with some of the character on one row and the rest of the character on the next row. But the VIC draws one row at a time, and can’t leave the current row to go draw somewhere else on the screen. It needs our help to understand how much of a given character to draw on a given row, and where to start drawing it. The mechanism for this is unusual, but it’s designed in a way that’s useful for common graphical effects.
Let’s consider an example in detail. Say we want to draw the red soldier such that its upper-left corner is at pixel coordinate X=0, Y=3. The soldier tile is 16 pixels tall, using two 16-by-8 NCM characters in the character set. To appear at vertical position Y=3, these two characters have to be drawn across three character rows:
The Raster Rewrite Buffer provides two separate features to make this possible.
Normally, when the VIC draws a character on a character row, it reads the first pixel row of that character’s pixel data and draws it on the first vertical pixel position of the character row, then the second for the second, and so on. For FCM/NCM characters, it gets the address of the first pixel row of the character by taking the screen code and multiplying by $40, and reads eight bytes of character set data for each pixel row.
You can tell the VIC to start reading character data on a different pixel row by setting a character data Y offset. An offset of +1 would tell the VIC to start reading the character data one row later: use the second pixel row of the character data for the first vertical pixel position, then use the third row of the character in the second position, and so on. This effectively shifts the character up by one pixel, cutting off the top. When the VIC gets to the eighth position, the +1 offset tells it to read the first pixel row of the next character in the character set.
One way to think about this is to imagine the entire character set stacked vertically. When the VIC draws an FCM/NCM character, it uses the screen code times $40, plus the character data Y offset times 8, as the address of the first pixel row. You can use the screen code and the offset to position an 8-pixel-high window anywhere in the character set stack, and the contents of this little window are drawn into the character row. The offset can be in the range -7 to +7.
To draw the red soldier at vertical position Y=3, we use a character data Y offset of -3 for all three rows. Each character row works as follows:
Tada! The red soldier now appears at pixel coordinate Y=3! That’s what we wanted, right?
OK, rows 1 and 3 don’t look quite right yet. They’re stealing pixel rows from adjacent characters in the character set to fill out the set of eight. We need another feature to tell the VIC to not draw anything on those pixel rows.
You can tell the VIC to skip drawing specific pixel rows by setting a row mask. This mask can enable or disable any of the eight pixel rows being drawn to the current character row. Combined with the character data Y offset, this completes our solution for drawing pixies at arbitrary vertical positions, like so:
In other words, all masked rows appear as transparent, and we can omit the neighboring pixels from the result of offsetting the character set data.
The character data Y offset and the row mask are part of the GOTOX instruction. This has two implications: they are set independently of the characters, and they apply to all subsequent characters on the character row, until they are changed by another GOTOX instruction, or they are reset at the end of the row.
You set the Y offset using bits 4 through 7 of the GOTOX instruction’s screen memory byte 1. Bit 4 is the sign bit: set it for a negative offset, or clear it for a non-negative offset. Bits 5-7 are the value 0 through 7.
To enable the row mask, set the GOTOX instruction’s bit 3 of color memory byte 0. Then set the row mask using all eight bits of color memory byte 1. Each bit of the row mask corresponds to a pixel row, where bit 0 is the top-most row, bit 1 is the second row, and so on. Set the row mask bit to show the row.
Here’s the complete picture for drawing the red soldier at pixel coordinates X=0, Y=3. In this example, each row has 40 bytes for the background, 2 bytes for a GOTOX instruction, and another 42 bytes for whatever else is needed on the row. The o variable is the screen memory offset for that row’s GOTOX instruction. The soldier characters follow the GOTOX instructions on each row.
Just to be sure, let’s walk through row 0:
Try working out the other rows.
See DISPLAY9 on the disk. Try enabling and disabling the row mask to see the difference. Note that Xemu cuts off the top row in this example; see notes near the end of the last Digest.
The Y offset feature worked out nicely for Tactical Strike. Each 16-by-16 tile is two NCM characters arranged vertically, and the two characters for each tile appear next to each other in the character set. When setting the -3 offset for row 2 in our example, we got exactly what we wanted: three rows from the top character and five rows from the bottom character.
This suggests a general rule for organizing character sets for large pixies: top to bottom, left to right. This makes it easier to use unmasked offset characters for the interior rows, taking advantage of the VIC’s ability to read rows from adjacent characters, as part of pixie vertical positioning.
It may not look like it yet, but we now know enough VIC-IV graphics features to implement Amiga-quality games! Super Extended Attribute Mode gives us full-color characters with a 256-color palette of 23-bit color. The Raster Rewrite Buffer lets us draw character graphics in layers with transparency, at arbitrary pixel locations. We can use these techniques to implement “pixies,” and draw a large number of them to the screen, on top of detailed backgrounds. A given object can be multiple characters wide and tall, and we can include multiple frames of animation in our character set and switch between them just by changing screen codes.
Knowing how it works and getting it to work are two different things. While we now know which VIC-IV features can be used to implement pixies with GOTOX instructions across multiple rows of characters, this isn’t how we want to think about pixies in our game code. I just want to be able to say that a game object uses a particular frame of animation from my tileset, and appears at a given set of pixel coordinates. It’d be best if some general purpose library could manage the screen and color memory, character codes and GOTOX instructions, Y offsets and row masks.
RetroCogs, the implementor of the MEGA65 port of RogueCraft DX, is developing GameShell65, an assembly language framework with abstractions for pixies and other graphics techniques based on the RRB. It’s in early stages, but there’s plenty of code to learn from and re-use. Even if you don’t plan to use it, check out the pixie documentation for hints on how to implement a workable pixie system.
Several of the demos on the TACTICAL.D81 disk use a BASIC subroutine to do the heavy lifting for drawing tiles in layers. It might be possible to encapsulate pixie positioning logic in that routine as well, depending on what kind of animations and movement I want the game to have. Whether it’s fast enough to do in BASIC, or if assembly language is needed, also depends on the game.
Of course, there’s still more to discuss about character graphics. There are several character rendering features we still haven’t yet covered. Fast-paced games typically use Direct Memory Access features to manage screen and color memory. And there’s more to say about full-screen scrolling and other special effects. These will have to wait for future Digests!
If you want to see more articles like these, consider supporting the Digest. Visit ko-fi.com/dddaaannn for more info.
See you next month!
— Dan
Roguecraft DX; Introducing the Raster Rewrite Buffer. Dan’s MEGA65 Digest for March 2026.
The MEGA65 community is buzzing with the release of Roguecraft DX, a new highly polished commercial game from Badger Punch Games for the MEGA65. Among many other fine qualities, Roguecraft takes advantage of the MEGA65’s full color character graphics features to show animated, colorful foreground figures on a colorful background.
In our continuing series on character graphics, we’ve seen how to prepare colorful high resolution graphics tiles in character sets, and plot characters onto the screen at coordinates in a grid pattern. We’ve also seen how a character set can become a canvas, by spreading all the characters across the screen like a quilt then updating the character set itself. But this is not how Roguecraft achieves a foreground and a background: it would take too much computation to draw and update foreground images onto such a canvas.
Instead, Roguecraft uses a powerful feature of the VIC-IV, called the raster rewrite buffer (or RRB). With this feature, your program can tell the VIC-IV to draw characters on top of other characters, treating some pixels as transparent. Moreover, these characters can be drawn at arbitrary pixel positions. The RRB unlocks a whole new world of graphics capabilities, such as hundreds of fast-moving graphics objects, particle systems, and multiple parallax background layers, all built on top of the character graphics features we’ve seen so far.
In this Digest, we will introduce the raster rewrite buffer through a simple example. We won’t quite reach the sophistication of Roguecraft in one sitting, but we’ll get the general idea, enough to start using it in our own programs.
But first, let’s see what everyone has been up to these last few months!
Roguecraft DX, the hit arcade dungeon crawler by Badger Punch Games, is now available for the MEGA65! You can get the digital download today, and you can pre-order the physical boxed edition, which is expected to ship in June. Our very own RetroCogs developed this gorgeous adaptation that takes advantage of the MEGA65’s graphics capabilities.
Badger Punch’s instant classic began as a 4 kilobyte game jam title for the Commodore 64, which was later expanded with gorgeous art into the commercial release Rogue64, named Game of the Year in 2022 by FREEZE64 magazine. This inspired the vibrant isometric Roguecraft for the Amiga, with all-new stunning art, animation, and turn-based gameplay. This was later expanded again into Roguecraft DX with more levels, gameplay, and music, originally exclusive to the Evercade modern retro console as a cartridge.
This announcement includes three new versions of Roguecraft DX: the MEGA65 version, an Amiga version (the DX expansions are new), and an all-new Game Boy Color version with a top-down perspective. Of course, I’m most excited for the MEGA65 version, which preserves the color depth of the Evercade art assets.
Purchase Roguecraft DX for the MEGA65 as a digital download on Itch.io, or pre-order the physical boxed edition from publisher Thalamus Digital.
Another still from Roguecraft DX for the MEGA65.
RetroTech & Café published an interview with MEGA65 creator Paul Gardner-Stephen about the project.
RT&C: What role does the community play in the evolution of the MEGA65?
Paul: Everything! Our community might not be huge, but it’s still several thousand strong, and a really high proportion of that community are active in making stuff for the MEGA65 or contributing in other ways (and they don’t all require being able to program!). We could have gone for maximum community size as fast as possible, but for us, it’s the quality of experience of being part of the MEGA65 project that makes it so rich and engaging for everyone.
And this is an invitation that we keep offering — be part of us in this crazy but rewarding project. The journey is the destination, and it’s a tremendously exciting and joyful one.
6502 Fart B*mbs! a game for the MEGA65 written in Prog8.
Prog8 is a cross-compiled programming language that targets 6502-CPU-family 8-bit platforms, including the Commodore 64, the Commander X16, and the Wildbits F256 (formerly of Foenix Retro Systems). Thanks to CommodoreSam and Andrew Gillham, Prog8 now has a full working example that can be built natively for the MEGA65. No changes to the Prog8 system were needed to get this to work: you can add support for new platforms with a configuration file.
6502 Fart B*mbs!, a Minesweeper-like logic game, is written in Prog8, and includes a working build configuration for the MEGA65. Sam and Andrew have released the game’s source code under the MIT License, so you can see how it is written, and how to set up a MEGA65 Prog8 project of your own. It’s also a fun game! Try it out!
plus4, a Commodore plus/4 emulator, by xlar54
xlar54 has started a pair of compelling experiments: a Commodore Plus/4 emulator and a Commodore 128 emulator, both for the MEGA65. Unlike alternate FPGA cores, these emulators run in MEGA65 mode, and use the MEGA65’s extra speed to outrun the original CPUs. These do not yet have every feature of their real-world counterparts, including some features of each platform’s unique video capabilities. They otherwise support nearly all of the functionality of the original ROM files, which you must supply.
See the plus/4 project’s Github repo and the C128 project’s Github repo for source code and other useful files.
c128, a Commodore 128 emulator, by xlar54
Chopper, by SirGeldi.
More fun things to check out on Filehost:
Over hill, over dale,
I’ve been playing around with this game tile set by Kenney intended for a military strategy game. There are tiles for fields, lakes, rivers, roads, and bridges to form a landscape, an assortment of military soldiers and vehicles in five color variations, and indicators and icons for user interface elements. The tiles are 16 pixels wide and tall and reasonably low color depth, so they seem suitable for a MEGA65 project.
The image above is my rendering of this tileset on a MEGA65 using NCM graphics over a solid grass-green background color. Most vintage computers are limited to drawing characters on solid background colors like this, and there is a deep history of vintage computer games doing so.
Very many vintage games, such as Crossroads or robotfindskitten, declare the solid background color to be the ground on which characters could walk, using tiles exclusively for foreground objects such as the player, enemies, and obstacles.
Other games, such as Ultima, Commando B65, or MEGAMAGE, use characters to indicate the ground also, such as grass in a field. When a foreground object is resting on a background surface, the game just draws the foreground tile, relying on the surrounding unoccupied background tiles to indicate what the object is standing on. Characters cannot overlap in most vintage character graphics systems.
Allowing graphics objects to overlap is the primary purpose of the VIC-II’s hardware sprites. Sprites can appear “in front of” or “behind” characters, and can be positioned at any pixel coordinate. The video chip composes the scene as it draws the screen, calculating the color of each pixel based on characters in screen memory and sprites described in registers. The VIC-II needs to handle sprites in a special way, so it can only support eight of them at a time. As we’ve seen, if the program jiggles the sprite registers fast enough, it can get the VIC to draw more than eight sprites on the same screen, with some limitations.
But this won’t do for my strategy game, which I’m calling Tactical Strike. I want vehicles to roll over flowers, just like in the dramatic openings of war movies. I want boats in the water, and helicopters over everything. And I have user interface elements such as unit counters and movement arrows. I can’t use VIC-II sprites for this. How can I draw characters on top of other characters?
Tactical Strike uses Nibble Character Mode (NCM) for its tiles. We got an NCM example working in the last Digest, but we left out a few important topics to focus the discussion. Let’s clear those up now.
You can think of the MEGA65’s VIC-IV as a superset of the VIC-II and VIC-III. While the VIC-III did introduce new features above the VIC-II, it retained nearly all of the behaviors of the original VIC-II registers for backwards compatibility. With the VIC-IV, we wanted to go a bit further, including enhancing some features of VIC-II registers. This poses an interesting conundrum: how can the VIC-IV expand an existing VIC-II feature, while still allowing the VIC-II registers to retain their original behavior?
A notable example: We’ve been playing with the VIC-IV CHARPTR register, which tells the VIC the precise address where in memory to find the character set. The VIC-II also has a way to relocate the character set, but it works differently, using a combination of banking bits on the 2nd CIA chip and four bits at address $D018 (register CB) to select a region on a 1 KB boundary. On the MEGA65, when a program updates these C64-style registers that change the character set location, the VIC-IV calculates the equivalent address and updates CHARPTR automatically.
$D018 is one of five VIC-II register addresses that behave this way. The others are $D011, $D016, $D031, and the VIC-II bank bits of $DD00 (CIA 2 PORT A). These are the hot registers.
That’s nice if that’s all the VIC-IV did, but there’s a catch. If the program writes to any hot register, all of the hot registers propagate to their VIC-IV equivalents, not just the one register that was updated. This can sometimes cause unintended behavior.
Hopefully nobody noticed, but there was a hot register hazard in a previous Digest. We did an experiment involving Low-res Multicolor Mode (MCM), and another experiment involving updating CHARPTR. The MCM register is bit 4 of address $D016, a hot register. If you set CHARPTR to a custom address, then activate MCM, you may be surprised to see CHARPTR get reset to the equivalent of the VIC-II registers as the hot registers propagate.
The easiest way to prevent confusion is to disable the VIC-IV hot register behavior. To do this, clear the HOTREG register, bit 7 of address $D05D.
This is sufficient for most programs that know how to use the VIC-IV. In exchange, you have to know which VIC-II registers no longer propagate.
See the “Hot Registers” section of the VIC-IV appendix in the Compendium for a complete explanation.
The H640 register, bit 7 of $D031, selects the horizontal resolution of the screen: 320 pixels (40 text columns) or 640 pixels (80 text columns). The original Commodore 65 VIC-III has a bug where characters are drawn shifted slightly to the left in H640 mode. To compensate, manipulating the H640 register also sets the VIC-II horizontal scrolling register, known as XSCL at $D016.0-2. The MEGA65 implementation reproduces this bug and the compensation for compatibility reasons, which means programs have to work around it. (We’ll cover text scrolling another time. For now, just know that there are registers that can nudge the screen horizontally by a number of pixels. Try messing with them!)
$D016 is a hot register. The VIC-IV has a more powerful horizontal scrolling register called TEXTXPOS ($D04C), and with hot registers enabled, a hot register update propagates XSCL to TEXTXPOS. In the full sequence of events, changing H640 sets XSCL to 1, which results in a TEXTXPOS of either 2 pixels for 80-column mode or 1 pixel for 40-column mode.
In most cases, the easiest way to account for this is to disable HOTREG, set H640 as desired, then reset TEXTXPOS to its proper default of 80 pixels. With HOTREG disabled, the program has full control over the VIC-IV text position, and doesn’t have to account for the VIC-III bug or its compensation.
There are other ways around this for unusual cases where you might want to keep HOTREG enabled or use VIC-II registers. If you think you have such a case, ask about it in the Discord.
As you may recall, VIC-III character attributes add effects to text characters using the upper four bits of color memory: blink (bit 4), reverse (bit 5), bold (bit 6), and underline (bit 7). Low-res Multicolor Mode (MCM) disables these automatically to use those bits for another purpose, but the other modes keep them available.
This includes the Super-Extended Attribute Modes. We were able to ignore this in our demonstrations of Full Color Mode (FCM), because color memory only stores the highest-value color for each character ($FF) and we left this unused and set to zero. You can, in fact, use the four text attributes in Full Color Mode by setting the appropriate bits of the high byte of color memory.
In Nibble Color Mode, the four attribute bits serve a dual purpose—and we pretty much never want them to. When NCM is enabled for a character (bit 3 of the low byte of color memory), bits 4 through 7 of the high byte of color memory select one of the 16 sub-palettes used by the nibble values of the character. By default, these bits also enable text effects.
Thankfully, the VIC-III has a system-wide switch to disable text effects: the ATTR register, $D031 bit 5. Clearing this bit suppresses the text effects, so these bits can be used exclusively for sub-palette selection in Nibble Color Mode.
Recall that the LINESTEP register ($D058-$D059) tells the VIC how much screen and color memory is used for each character row. In normal text mode, an 80-column text screen uses 80 bytes of screen memory per row, one byte per character, so LINESTEP is 80. Enabling CHR16 doubles the amount of screen memory per character, so an 80-column screen needs a LINESTEP of 160.
There’s another register that tells the VIC how many characters are on each row. The CHRCOUNT register is 10 bits, with the lower 8 bits at $D05E, and the upper 2 bits at $D063.4-5. When the VIC draws a row, it counts the characters it is drawing left to right, then stops drawing once it sees CHRCOUNT characters.
You can have more memory reserved per row by LINESTEP than is needed for CHRCOUNT characters—and we’ll see a reason why later in this article. You cannot have a CHRCOUNT higher than the amount of memory reserved by LINESTEP; this causes the VIC to overrun each line’s memory. (I mean, you can do this if you’re designing some oddball special effect. Don’t let some bossy newsletter stop you.)
We didn’t need to address CHRCOUNT in previous experiments because we kept it at the default of 80, and always used a LINESTEP of either 80 with CHR16 disabled, or 160 with CHR16 enabled, so there was always enough memory for all 80 characters.
Here’s the full set-up sequence for drawing the Tactical Strike tile set on the screen, using a resolution of 320 x 200 (H640=0), SEAM enabled, and 20 NCM characters per row:
For this simple demonstration of the tile set, the entire screen is filled with NCM characters. Each character is 16 pixels wide, and on a 320 x 200 display, that allows for 20 NCM characters per row. I can set CHRCOUNT to 20 and LINESTEP to 20 x 2 = 40 (two bytes per character), for a total of 25 rows = 40 x 25 = 1,000 bytes of screen and color memory. (Each tile is two rows tall, so I have a tile grid of 20 x 12, with one spare row.)
For an actual game, I’d probably declare some rows as the tile-based game board, and use other rows for 40-column text. Recall that FCLRLO and FCLRHI control which screen memory values refer to the character set indexes (screen codes) vs. FCM/NCM character addresses. By leaving FCLRLO clear, I can use the first 256 screen memory values to refer to the character set, either the default PETSCII set or a custom set that I can define in the usual ways.
If I am going to use 40-column text for some rows, then I need to set LINESTEP and CHRCOUNT accordingly. The easiest thing to do is establish 40 characters per row, and just let the extra screen memory on the 20-character tile rows go unused.
Tile set data will live at $4.0000. As we did last time, I have tile set and palette data in data files that I BLOAD into appropriate memory locations. We can’t BLOAD directly into palette registers, so I stash palette data temporarily then copy it in with a loop.
Recall that we don’t need to set CHARPTR for FCM/NCM characters: the screen memory value is an address, not a screen code. Specifically, the address is 64-byte aligned and divided by 64, so with NCM characters stored at $4.0000, the screen memory values are the tile character number plus $1000. Color #1 is the color of grass, so I set that to be the background color for now.
I used my own Python script to convert the tile set from a PNG file to NCM character set data, and did some planning around the palette. Recall that each NCM character can only use one of sixteen sub-palettes of fifteen colors each plus the background color (value $0). The character’s color memory value selects the sub-palette used by the character, and also sets which color in this sub-palette is used if the character pixel is color 15 (hexadecimal $F).
This makes it possible to reuse tile definitions with different sets of colors. This could come in handy for Tactical Strike! The tileset has tiles for five “teams,” each with the same set of vehicles, soldiers, and buildings drawn with different palettes. I only really need one set of vehicles et cetera for all five teams, and one sub-palette per team.
For this demonstration, I wasn’t so clever. The original Kenney tile set uses a total of 36 palette entries, and (luckily) each tile uses fewer than 15 colors from this palette. I organized the tile set into groups that could share sub-palettes, and left it at that. I ended up with 14 tile groups, each using a sub-palette. The tiles don’t use the $F foreground color. I don’t necessarily recommend this approach, this was just the easiest thing to do given that the tile set was not drawn specifically for the MEGA65.
In my code, the TP() array stores the sub-palette ID (0 to 13) for each of the 198 tiles, which I prepare as 26 tiles using sub-palette 0, 18 tiles using sub-palette 1, and so on. This is based entirely on decisions I made in the Python script. When I draw a tile, I will select the tile’s color memory value from this array.
We’re ready to draw some tiles on our grass-green field. Each tile is two NCM characters stacked vertically, and I made sure that both characters were next to each other in the character set data. To draw the first tile (tile #0), we would draw character #0 on the first row and character #1 on the second row, setting the color memory for each to TP(0).
The following loop draws all 198 tiles in order, in rows left to right, top to bottom. The subroutine at line 1000 draws tile T at tile position (X,Y). Remember that each SEAM character uses two bytes of both screen and color memory; 20 tiles (CHRCOUNT=20) require 40 bytes (LINESTEP=40). Read the subroutine carefully and try to figure out what each number represents.
This is enough to draw any tile at any position on the 20 x 12 grid. We could use this to draw a terrain map for the battle. We could also draw foreground tiles Ultima-style, with foreground replacing background, using the same subroutine. But we can do better.
It’s time to pull back the curtain on the final major component of VIC-IV character graphics!
We’re used to thinking of character graphics as a single field of character tiles arranged in a grid. The contents of screen memory describes which character in a character set appears at each grid location, arranged in memory as consecutive rows of equal length. As the raster beam marches down the screen, the VIC considers one row at a time, figuring out what color to set each pixel based on the screen code, the corresponding pixel data in the character set, and color memory. After the eighth line, the VIC moves on to the next row in screen and color memory.
In normal text modes, it’s easy to imagine that the VIC sees in screen memory roughly what we see on the screen, arranged in a grid. When we introduced NCM characters, we got a hint that the VIC might be doing more than we think: each NCM character is 16 pixels wide instead of 8, and you can mix 16-wide NCM characters with 8-wide characters on the same row. The VIC isn’t just looking at 40 bytes for 40 characters in a row. It’s looking at CHRCOUNT characters at a time, and figuring out from each character on the row how to draw the line. Each character may have a different width, pushing the horizontal draw position a different amount for each character.
You can think of the screen and color memory bytes as instructions that the VIC is following to draw the line: get character data from here, color data from there, use that to paint each pixel, then advance to the right by a certain amount. The VIC performs these instructions in order for the current row, and draws its calculated results into a raster buffer. When it reaches the end of the row’s character count, the VIC feeds the raster buffer to the raster beam to draw the final image.
So, how do we draw a soldier standing on a flower tile? We give the VIC a different kind of instruction! So far we’ve only seen screen and color values that describe a character to draw. You can also spend a character’s worth of screen and color memory to tell the VIC to change its state for drawing subsequent characters on the row. This non-character instruction identifies itself by setting a reserved bit of its SEAM color memory. When this bit is set, all 32 bits of screen and color memory for the non-character instruction tell the VIC what to do next.
One of several things this non-character can tell the VIC to do is to change the horizontal position of where characters are drawn in the raster buffer. This feature is so cool that the reserved bit is named after it: the GOTOX flag. Because GOTOX can tell the VIC to go back and re-draw previous pixels on a line, the raster buffer is also known as the Raster Rewrite Buffer (RRB).
Here’s a way we can tell the VIC to draw a soldier tile on top of a flower terrain tile, in three character instructions:
When the $0 pixels in the soldier tile are treated as transparent, they do not change the buffer contents. The flower tile appears “below” the soldier.
Let’s see this in action. We will draw the flower terrain tile (tile #1) in the upper left corner. This will be followed by a GOTOX non-character instruction, then the red soldier tile (tile #183).
We already know how to draw the NCM characters for instruction #1 and #3. For these characters, the GOTOX bit is clear, and the NCM bit (color memory byte 0 bit 3) is set.
The GOTOX flag is color byte 0 bit 4. Setting this bit says this is a GOTOX instruction, not a character. The VIC does not draw a character for this instruction, even though the instruction occupies two bytes of screen memory and two bytes of color memory. All of the other bits in screen and color memory for a GOTOX instruction have new meaning.
The horizontal position is a pixel coordinate, from the left border. This is a 10-bit value: screen byte 0 is the lower eight bits, and bits 0 and 1 of screen memory byte 1 are the two high bits.
For this demonstration, we’re keeping tiles aligned with the tile grid, but we could easily use this horizontal pixel coordinate for more precise placement on the screen, or smooth movement animations. (And yes, there is a way to offset the draw position vertically. We’ll cover that in another Digest.)
The last GOTOX property we’ll look at here is the transparency bit, color byte 0 bit 7. When set, this causes subsequent characters to not draw anything for pixels of color $0, allowing whatever was previously written into the buffer to shine through. When transparency is off, the screen background color is drawn for these pixels. Remember that this setting remains enabled for all subsequent characters on the row, or until it is changed by another GOTOX instruction.
This code draws the solider on the flowers. (Note that CHRCOUNT and LINESTEP are not set correctly in this case: the GOTOX instruction is eating one of the 20 character slots! We’ll discuss this in a moment.)
This simple example implies one way to draw the Tactical Strike playfield with background and foreground tiles: for each position, draw the entire stack of desired tiles, with a GOTOX to rewind back one tile position between each tile in the stack.
This produces the result that we want, but has some disadvantages. For starters, this method is difficult for the program to manage. Tactical Strike will keep the background mostly the same, then move foreground objects around on the field. With GOTOX instructions scattered throughout, the program would have to recalculate the entire row for simple changes.
Another method is to define a number of layers, allocate enough characters for each layer to span the entire playfield, then insert a GOTOX instruction between each layer (on each row) to reset the X position to 0 and enable transparency. The background fills the bottommost (first) layer, and other layers are filled with empty spaces and foreground objects.
This is much easier on the program code: it can locate the memory for any tile at any position on any layer using simple math. In exchange, it uses a bunch of memory on empty spaces. Many games are similar to Tactical Strike in this way: the background layer is full, but foreground layers are sparse.
For Tactical Strike specifically, I might go with a hybrid approach: for each row, draw the entire background, then follow it with a “draw list” of GOTOX instructions and tile characters. The program has to re-calculate the draw list on each row when stuff changes, but it can leave the large background layer alone, and locate each draw list with a simple multiplication.
To draw all of the tiles in a single layer 20 tiles across, I set CHRCOUNT to 20, and LINESTEP to 40 bytes = 2 bytes per tile for each of screen and color memory. Non-character instructions consume screen and color memory just like characters, and we’re piling more characters onto the row in layers, which also need more memory. I need to increase both CHRCOUNT and LINESTEP to account for all of these bytes, up to the maximum I expect to need for any given row.
Say we have 20 tiles across, 12 tiles down, and 3 layers = 720 tile positions. Each row needs two bytes of screen memory per tile and per GOTOX instruction, and this would use two GOTOX instructions in addition to sixty tiles, for a total LINESTEP of 20 x 3 x 2 + 2 x 2 = 124. Each tile is two character rows, for a total of 2,976 bytes of screen memory and 2,976 bytes of color memory for the entire display.
What about CHRCOUNT? CHRCOUNT must be at least the maximum number of characters and non-character GOTOX instructions in a row. 20 characters per layer plus one GOTOX instruction between each of three layers is 62 characters, so CHRCOUNT is 62.
For SEAM graphics modes where each such character takes two bytes of screen memory, LINESTEP is twice as much as CHRCOUNT. Once again, we can ask: why is LINESTEP a register, and not calculated automatically from CHRCOUNT and CHR16? This is so the program can request a LINESTEP that makes addresses easier to calculate. For example, with our CHRCOUNT of 62, LINESTEP must be at least 124, but setting LINESTEP to 128 (and wasting a few bytes of memory per row) would allow each row to start at an address that’s a multiple of a large power of two: $5.0000, $5.0080, $5.0100, $5.0180, etc. This makes character addresses faster to calculate, especially in assembly language.
In general:
maximum characters + GOTOX per row = CHRCOUNT <= LINESTEP/2
I can minimize the amount of memory required for each row by designing my program carefully. The Tactical Strike playfield might be 20 tiles across, but if I limit the number of foreground objects and layered icons, I don’t need to allocate a full 20 tiles for each possible layer. The draw list method can be made to use less memory than the full-size layer method.
Why are we worrying so much about memory? CHRCOUNT is a 10-bit register, so there’s a logical maximum of 1,024 characters and GOTOX instructions on a row. That seems like plenty. But there’s another limit we need to consider. The VIC only has so much time during which it can mess with the Raster Rewrite Buffer before it has to start drawing to the screen. This limit is influenced by a couple of factors.
The RRB has the option to use what the MEGA65 documentation calls “double buffering.” This isn’t double buffering in the sense of full animation frames, it just means that the VIC will prepare the buffer for a later line while an earlier line is being drawn to the screen. You can disable this if you’re doing something fancy, but in most cases it is sensible to leave it enabled. The register NORRDEL, $D051.7, should be clear to keep double buffering enabled.
If you’re using the lower vertical resolution of 200 pixels (as I am), you can double the amount of RRB mess-around time, in four steps:
There have been multiple discussions in the MEGA65 Discord about how to measure and maximize RRB use—and work around bugs or unexpected behaviors. The most general advice is to experiment: figure out early what you want your program to do, then write short programs to determine what you can get away with.
I did a brief test of the full-layer method using twelve rows of twenty NCM characters. I allocated a number of layers with CHRCOUNT and LINESTEP, then filled each layer with random tiles, one at a time. It handled 11 such layers without difficulty. Starting at 12 layers, my display started missing lines it didn’t have enough time to finish drawing. I would expect different results using FCM characters, or other modes or screen resolutions.
Important note: You will see different edge case behaviors on a real MEGA65 vs. the Xemu emulator, so be sure to run your experiments on both. Xemu handled 12 layers fine, and saw a different failure mode at 13. With all of the above settings, I get almost identical behavior on both real hardware and Xemu.
If you’re in a jam trying to get things to look right on both platforms, there’s no shame in reading the REALHW register at $D60F.5, which is set to 1 on a real MEGA65 and 0 on Xemu, then changing settings accordingly. For example, you might want to tweak the top and bottom border positions, $D048 and $D04A respectively, based on which platform is running. I’ve noticed a one pixel difference between them in my tests.
There’s an important caveat when using GOTOX to position foreground objects on a playfield.
When it’s time for the VIC to draw the raster buffer to the screen, it assumes that the last horizontal draw position it encounters is the end of the line. With the draw list method, the last character drawn might be somewhere in the middle of the screen. If we do nothing else, the VIC will assume that’s the right-hand border—and decline to copy the rest of the buffer to the screen. None of the characters at any layer will appear to the right of this position.
The solution is to end the draw list with a GOTOX to the right border. Also, MEGA65 developers discovered that the GOTOX must be followed by a character for this to work consistently in both the MEGA65 and the Xemu emulator. Note that the program still has to fill the unused memory on a row with dummy characters when it calculates the draw list, otherwise a leftover GOTOX instruction might mess everything up.
That’s a good start! We now know enough to draw characters on top of other characters, which makes possible all kinds of graphics techniques. A simple version of Tactical Strike sticks to the character grid, but with pixel positioning and GOTOX, clearly more is possible. There’s more to explore!
Tactical Strike also benefits from being a turn-based strategy game, so we could probably finish a pretty fun game written entirely in BASIC. Larger or faster games may need to be built in assembly language for speed. Eventually, we may need to tap in another MEGA65 feature we haven’t discussed in the Digest yet: the DMA.
Thanks to everyone for your patience on the Digest release schedule. I’ll try to get this thing back on the monthly cadence, or at least come up with a new regular schedule so everyone knows what to expect.
See you next time, and happy coding!
— Dan
Every Pixel a Color. Dan’s MEGA65 Digest for November 2025.
It’s November, and you know what that means: it’s time to level up our MEGA65 graphics knowledge! In this Digest, we reach another major milestone in our VIC-IV journey: full color character graphics. Type-ins galore, so warm up those coding fingers.
Let’s start with the news, then get right at it.
Dan’s MEGA65 Digest 2025
My talk from Vintage Computer Festival Midwest 2025, Dan’s MEGA65 Digest 2025, is now online. This one looks at the MEGA65 through the lens of this very newsletter over the last few years. It’s a haphazard introduction to the platform, its capabilities, and its community (that’s you!).
I tend to give these talks to audiences that still haven’t heard about us, so there’s always quite a bit of introductory overlap. Hopefully there’s enough new stuff, or at least enthusiastic review, for you to enjoy.
Spring, a limited edition boxed release by Gurce’s family.
Gurce’s artistic family is producing a limited edition boxed title for the MEGA65. Entitled Spring, the game is designed by Gurce’s daughter, and will be distributed on floppy disk, with boxes and add-ons by Gurce’s sister.
Spring will be available to the MEGA65 community in strictly limited quantities. If you’d like one, contact Gurce on the Discord. Check out the game’s website for more information, poster art, and a guide to the game.
The Woods of Peril, by Lipi.
The Woods of Peril by Lipi, an original text and graphics adventure game. Music by Gurce. Navigate the forest with cursor keys: rotate left and right, move forward or backward. Follow on-screen prompts in villages and during battle.
Overlord by Drex. Navigate the maze and avoid the Overlord. Listen for his footsteps, avoid his line of sight, and try to make it to the end. Based on Drex’s own Alpha Maze. Use keyboard controls to move: W, A, S, D, and Q and E to rotate.
Yet another core for your home arcade from muse, Mr. Do! is now available. Collect cherries, avoid monsters, and defend yourself by pushing giant apples and throwing your ball.
In the September Digest, we introduced Super-Extended Attribute Mode (SEAM) and the CHR16 register, a feature that allowed us to commit more memory to graphics to access more features. So far, we only got more screen codes out of the deal, but that’s a pretty big step up! We were able to fill screen memory with ascending screen codes to make a screen-wide bitmap quilt of monochromatic tiles.
I left all of the foreground colors white in that example, but we could have proceeded to assign different foreground colors to each 8x8 patch, within the first 32 palette entries. Clever pixel artists know how to hide the color transitions between character boundaries. This gets especially interesting in combination with Low-res Multicolor Mode, like so:
In this example, the background color and two MCM colors are the same for every character on the screen, requiring careful planning to blend them with individual character foreground colors. And MCM doubles the visible width of the character pixels, potentially giving those characters a chunky look. Four possible colors per character provides more options, within these limitations.
The MEGA65 can take this to the next level. In Full Color Mode (FCM), the VIC-IV can assign a different palette entry to each high resolution pixel of a character in a character set. Instead of one bit per pixel selecting between the character’s foreground color or the screen’s background color, FCM uses one byte per pixel. Value $00 is still the background color, and $FF is the character foreground color. Values $01 to $FE select the corresponding colors directly off the palette.
We started this series on character graphics with the observation that assigning a 23-bit color value to every pixel would be impractical. It uses too much memory, and by extension is too slow to update. A 640 x 200 screen with 23 bits for every pixel would require 359.375 kilobytes—nearly all of the MEGA65’s chip RAM for a single screen.
Full Color Mode strikes a balance: it’s still character graphics, and it’s still indirectly specifying the 23-bit color with an 8-bit palette. A program like a game looking to update the display quickly can still do so with minimal memory changes, using references to character tiles. To perform our full-screen bitmap trick with 80 x 25 = 2,000 character codes, FCM would demand 64 bytes per character, or 128,000 bytes (125 kilobytes) of character set graphics data. That’s still a lot of memory, but it’s doable.
There are two important things to know about Full Color Mode. The first is that FCM is a feature of Super-Extended Attribute Mode, and requires that CHR16 be enabled.
The second important thing to know is that in Full Color Mode, screen memory does not store screen codes. Instead, it stores the absolute address of the 64 bytes of character data. CHARPTR is ignored for characters referenced in this way. For example, a screen code of $1000 (4096 decimal) refers to 64 bytes at address $1000 x $40 = $4.0000.
A character address must be aligned to a 64-byte boundary: screen memory stores the address divided by 64. With CHR16 enabled, there are 13 bits of screen memory per character, with a maximum character address of $1FFF x 64 = $7FFC0 – $7FFFF. MEGA65 chip RAM only goes up to $5FFFF, so you effectively have all of chip RAM to store full-color graphics character tiles.
I’m calling this out as important because it’s a common tripping point for developers learning FCM for the first time. This absolute address behavior is described in the manual, but the current state of this information is a series of brief paragraphs, so it’s easy to miss.
To enable FCM for screen codes 0 to 255, set register FCLRLO bit 1 of address $D054. To enable it for screen code 256 and higher, set register FCLRHI $D054 bit 2. Enabling FCM changes the interpretation of the affected screen codes to absolute 64-byte-aligned addresses.
Having separate FCM switches for lower and upper screen codes makes it easy to support both traditional character sets and full-color graphics on the same screen. A common configuration is to keep FCLRLO clear and only set FCLRHI. This way, screen codes 0 to 255 continue to refer to a monochrome character set pointed to by the CHARPTR register, and screen codes 256 and above refer to absolute addresses $4000 and above.
FCLRLO is therefore not particularly useful. All it does is enable the ability to refer to full-color character data at addresses $0.0000-$0.3FFF, which is not typically where a program would keep graphics data. And it trades away access to a monochrome font, such as the PETSCII font or a custom text font.
FCM is a feature of SEAM, and requires that CHR16 be enabled: set bit 0 of $D054. As we saw last Digest, this doubles the screen memory and color memory for each character to two bytes each. This also requires setting LINESTEP D058-D059 to twice the number of characters in each line (e.g. 160 bytes for 80 characters). We can’t reliably use the KERNAL screen editor’s default memory location for this, so we set SCRNPTR D060-D063 to somewhere more useful.
For this example, let’s use FCLRHI only, and leave FCLRLO clear, so we get the best of both worlds. Set bit 2 of $D054.
Here’s the colorful @ symbol, written to address $4.0000.
And here’s a custom palette that matches my diagrams above. If you’re following along, you can skip this part, and just see the result in the default system palette.
Remember that 8-bit color component values need the nibbles swapped before storing. Here, I’m swapping them in the POKE statement, so that my DATA statements look like the actual RGB values.
Clear screen and color memory at our custom SCRNPTR location of $5.0000, remembering that both screen and color codes are two bytes now. Screen code 32 still refers to a PETSCII space, because FCLRLO is clear.
Lastly, draw a brief message, and our custom character:
Let’s round out the program by waiting for a keypress, then restoring the original screen settings.
256 colors is a luxury, but sometimes memory is a luxury we can’t afford. And yet it is still useful to be able to use many colors in a single character. What else can we trade?
In Nibble Color Mode (NCM), four bits describe a pixel, instead of eight. That gives us a respectable 16 colors to choose from for each pixel in a given character. Each pixel uses half as much memory as Full Color Mode.
Hang onto your butts, because there’s a lot to know about Nibble Color Mode. We’ll take this one thing at a time.
Nibble Color Mode is not a system mode. It is a feature of an individual character, while in Full Color Mode. You tell a character to interpret its character set data as NCM instead of FCM by setting bit 3 of its color RAM value.
Combined with keeping FCLRLO clear, this means you can mix monochrome (lower non-FCM chars), full color (upper FCM chars, color bit 3 clear), and nibble color (upper FCM chars, color bit 3 set) characters, all on the same screen.
With a character in Nibble Color Mode, each byte represents two horizontally adjacent pixels. The “lower” nibble in bits 0-3 of the character data is the left pixel, and the “upper” nibble in bits 4-7 is the right pixel. This is backwards from how we normally think of pixel order in monochrome pixel data, where the most significant bit is the left-most pixel.
A character in Nibble Color Mode still uses 64 bytes to describe all of its pixels, 8 bytes per row. With two nibbles per byte, an NCM character is 16 pixels wide instead of 8! These are normal-sized pixels, not halved or doubled. The NCM character simply takes up more space on screen because it has more horizontal pixels. The character at the subsequent screen and color memory location starts at the horizontal position where the previous character left off.
This is wild! For the first time in this series, we have to think of a character taking up a variable amount of space on a row depending on its properties. No longer can you say for sure that a character described at a given memory address has a given horizontal coordinate: its actual position depends on previous characters in the row.
And here is our first clue as to the usefulness of the LINESTEP register. Each row takes up LINESTEP number of bytes in memory. In normal text mode, a row of 80 characters needs one byte per character, so LINESTEP = 80. In CHR16 mode with monochrome or FCM characters, a row of 80 characters needs two bytes per character, so LINESTEP = 160.
If you have a screen full of NCM characters, 640 pixels per row and 16 pixels per character means you have 40 characters per row, each taking two bytes. In this case, you could set LINESTEP to 80, and avoid leaving a bunch of unused memory at the end of each row. You could even mix character modes on a row, and as long as you have a reliable maximum number of non-NCM characters, you could set LINESTEP accordingly.
In later Digests, we will see other amazing things that the VIC-IV can do with its row-wise rendering, and find other uses for LINESTEP.
The actual color of a pixel is a combination of the four-bit pixel data and the color value for the character in color RAM. The upper four bits of the color data for the character (bits 4-7 of color byte 1) are used as the upper four bits of the color value, and the character data nibble is the lower four bits. This allows multiple NCM characters to access all 256 colors of the palette on the same screen, with the limitation that only 16 at a time can be used for a given 16 x 8 NCM character.
If the nibble value is $F, the color of the pixel is the full 8-bit color RAM value for the character.
That sounds complicated, but it’s quite powerful. We can use techniques similar to the VIC-II Low-res Multicolor Mode (MCM) blending trick we saw earlier to hide differences between characters, with many more color options at our disposal.
More powerful graphics features demand richer graphics data. You’re more than welcome to stick with the old-school methods of graph paper and DATA statements. To fill a screen with color, you’re more likely to want to use graphics editing tools and image data converters.
By far the most popular graphics editor for retro-style pixel graphics is Aseprite. It’s available for Windows, macOS, and Linux, for a lean one-time $20 purchase that’s worth every penny. Alternatively, you can opt for the free LibreSprite, which has a common origin with Aesprite. Aesprite gives you fine control over pixels and palettes, and allows you to animate your creations and export animation frames in a sprite sheet.
Tips for using Aseprite for MEGA65 graphics, and for conversion to other vintage or retro-style computers:
I’d love to nerd out on the PNG file format here like I did with IFF, but you can read the Wikipedia article for details. Instead, we’ll use the PyPNG Python library to extract the data. To install this library from PyPI using pip:
In Python, access the image data of the PNG exported from Aseprite like so:
If everything exported as expected, metadata['palette'] contains the RGB values for each palette entry, in order, as a tuple of three byte values. You can confirm that metadata['bitdepth'] is 8, which says each of the color values in the palette is a byte.
The pixels value is a list of pixel rows, where each row is bytearray of palette entry numbers the width of the image. This is where most of the conversion work needs to happen: for FCM characters, the bytes need to be rearranged as 8-by-8 character tiles, where each tile’s bytes are organized row-wise top to bottom.
Here is my attempt at the simplest possible logic to convert a photograph to an almost-full-screen FCM display. I resized the photo to 640 x 192 image, so there’s enough room in banks 4 and 5 for both the FCM character data (640 x 192 = 122,880 bytes) and the CHR16 screen data (80 x 25 x 2 = 4,000 bytes). I exported the image with 8-bit indexed color and 256 palette entries.
Then I used the following Python script to prepare the palette and image data as separate binary files. The png library provides palette data as 256 (r, g, b) tuples; the script swaps the nibbles for each value, then stores them as 256 reds, 256 greens, and 256 blues, to the file mppal.dat. This is the format expected by the palette registers starting at $D100.
To convert 192 pixel rows of 640 palette entries into FCM data, the script considers each 8 x 8 rectangle in character rows, writing the character image data in order to the file mpdata.dat.
I put mppal.dat and mpdata.dat onto a D81 disk image, along with the following BASIC program. I’m using BLOAD to load the data files. BLOAD does not support loading data directly into registers, so I load the palette file into a temporary memory location first then copy the data into the registers. The character data is loaded to address $4.0000, with just enough room for screen memory at address $5.F060.
The program fills screen memory with consecutive character addresses starting at $4.0000—similar to the monochrome bitmap example from the last Digest. It fills color memory with $FF00, so that any pixel from the image using color $FF actually uses palette entry $FF. Recall that a pixel valued as $FF is actually colored with the palette entry in color memory for the character, so color memory needs to be filled with high bytes of $FF to match the intent of the image.
Because some color memory bits are treated as character attributes (blink, underline, reverse) by default, you must disable character attributes in this mode. You can do this by clearing the ATTR register, bit 5 of $D031. All registers at this address trigger the “hot register” mechanism, so it’s important to do this early in the program, or disable hot registers per the instructions in the Compendium. (To save space here, we’ll discuss hot registers another time.)
Of course, both the Python script and the BASIC loader could be expanded to accommodate images of any dimension and palette size. You could even invent a file format that includes dimensions, palette, and image data in a single file, ready to use with a general purpose image viewer based on FCM character graphics.
A general purpose image converter would be nice, but I find it useful to know how to customize the conversion process. For example, a program using NCM graphics would need to know more about what should go into color memory, or how 16 x 8 NCM characters are organized on screen. A game with tiles or animations would need to know how the sprite sheet is arranged. Tools like Aseprite combined with custom data management are a part of a powerful retro gamedev workflow.
We’re making great progress on our character graphics series, but there’s still some ground to cover! The MEGA65 has more tricks up its sleeve for more colors, fancier text rendering, and arcade graphics techniques. It’ll take a few more issues to cover it all.
A quick note about the newsletter schedule. You may have noticed that I haven’t quite kept up a monthly cadence over the last few months. I know you’re OK with it, and I’m OK with it too, I just want to acknowledge it. As long as this Digest is a community resource, I owe it to you to at least send out community news on a regular basis. I’m planning one more issue in the 2025 calendar year. For 2026, I’m considering adjusting the format to post community news and feature articles separately. Expect some experimentation.
This Digest is supported by cool people. If you’re a cool person, consider becoming a supporter for the new year. Visit: ko-fi.com/dddaaannn
Thanks all!
— Dan
Super Extended Attribute Mode. Dan’s MEGA65 Digest for September 2025.
I just got back from the Vintage Computer Festival Midwest 2025 in Schaumburg, Illinois, USA. I ran another two-day MEGA65 exhibit, this time in collaboration with YouTuber retroCombs. I also gave another talk, which was recorded and will be uploaded soon. It was great to meet up with friends old and new, and talk to many people interested in the MEGA65.
I’d make this Digest issue another trip review, but I have tarried too long on the next article in our character graphics series, and I shall tarry no longer! We’ll save the VCF photos for next month.
That said, we have some major project announcements this month. It’s a jam-packed Digest, so let’s get started!
Who Remembers the Commodore 65?
The talk I gave at Vintage Computer Festival West 2025 in Mountain View, California, Who Remembers the Commodore 65?, is now up on YouTube. I had a good time with this, and hope to explore this thesis in more detail someday. I hope you like it!
Mega-IP, by xlar54
xlar54 has released an initial version of Mega-IP, a TCP/IP networking stack for the MEGA65 intended for use from BASIC 65 programs. This release includes a simple telnet BBS client using the library. Mega-IP even supports incoming connections, for potentially hosting a BBS or other services from a MEGA65. See the Github repo for instructions and source code.
Amazing work and thoughtfully designed. Looking forward to what else comes from this!
Super Mega Assembler, by 0x30507DE
Bobby Tables (aka 0x30507DE) has released the first edition of Super Mega Assembler, a fork of Mega Assembler by grubi, using an all-new assembler engine implemented in machine code for speed. SMA is “self-hosting,” a milestone for any assembler or compiler written in its own language that says it is capable of building its own code. This on-device assembly language programming environment includes the original text editor.
Visit the #super-mega-assembler channel in the Discord to ask questions, or to read Bobby’s developer notes.
MegaPET, a Commodore PET core for the MEGA65
Rhialto’s Commodore PET core MegaPET is now officially released! The core supports every known version of the PET, and comes packed with features. Check out the documentation for details.
It’s been a real pleasure watching Rhialto’s steady updates of this project on the Discord. Congrats on the launch!
King’s Quest I, running on the MEGA65
King’s Quest, the 1984 classic graphic adventure game by Roberta Williams and published by Sierra On-Line, runs on a game engine known as Adventure Game Interpreter (AGI). AwesomeDolphin has developed Mega65-AGI, a MEGA65 version of the engine. You can now play the first game in the King’s Quest series on your MEGA65!
Similar to kibo’s MEGASPUTM, which plays Maniac Mansion by LucasArts, Mega65-AGI requires that you obtain the original King’s Quest data files to play the game. You can purchase the original game from GOG.com, and use the included Python script to merge the resource files into a playable D81 disk image.
Congrats to AwesomeDolphin on this major accomplishment!
More that’s new on Filehost:
Speaking of MEGASPUTM, there’s a new version that can play the Spanish language version of Maniac Mansion with the appropriate game data files.
Even more arcade cores from muse! Check ’em out: Pitfall 2, Wonder Boy in Monster Land, and Choplifter. (Yes, there were arcade cabinet versions of these games!)
Alpha Maze, by Drex. A 3D maze generator with nice color gradients.
Slidepuzzle 65, by BOBELE, the classic sliding tile puzzle.
Heli-Demo, by SirGeldi, a short demo of a helicopter flying over smooth scrolling text. Use a joystick in port 2 to move the helicopter, and press the button to fire, Choplifter-style!
Haiku - The Duel, by mk9. Select from a library of lines to construct a haiku, and try to outscore your computer opponent with the best poem.
Filehost watchers have noticed that we have uploaded a new version of the MEGA65 platform. This is not a new release that you need to care about: the MEGA65 team is making a routine update to the mainboard to adjust for parts availability, and this inevitably requires small changes to the core. Neither the mainboard nor the release package have new features or bug fixes, and you don’t need to upgrade. We just do a new release when any supported board needs a change.
We are hoping to do a new major platform release sometime soon. Watch the usual spaces for updates.
Back in July, we played with mixing red, green, and blue light to make new colors in the system palette. We saw that we could use 16 possible amounts of each component, from none (0) to most (15), for a total of 16 x 16 x 16 = 4,096 possible colors.
Here are all of the colors we can make with 16 levels of green, and none of the red or blue:
SA and CA are the starting addresses for screen and color memory, respectively. The FOR loop populates the first 16 palette entries with the 16 possible green colors, and draws rectangles in the first 16 character positions, one for each color. We’re using BASIC 10’s PALETTE command, which accepts the values 0 to 15 for each of the red, green, and blue amounts. PALETTE RESTORE brings back the default system palette before exiting.
The MEGA65 improves upon the Commodore 65’s 4,096 colors by allowing you to add fractional amounts of each color component. To each of these possible greens, you could add just a smidge more green to get a new shade, up to 16 possible smidgens of green, for a total of 256 possible green values in roughly the same range.
There’s a lot going on in this example, but take a moment to notice that this displays 16 intermediate shades of green above each of the major 16 green values. In line 40, the C variable is the major shade as before, and the S is the smidgen of green that we’re adding. The brightest green in this example is just a bit brighter than the brightest green in the previous example, because we’ve added 15 smidgens.
We can’t use the BASIC 10 PALETTE command here, because it only supports the “major” values 0 to 15. Instead, this example POKEs directly to palette memory. The greens of the palette entries are at addresses $D200-$D2FF. Each value is an 8-bit byte: 4 bits for the major value, and 4 bits for the minor value.
The MEGA65 has to do something clever to stay backwards compatible with the C65, which only supports the major value POKEd to the palette as the numbers 0 to 15. The MEGA65 stores the fractional (minor) color value in the upper bits of the byte. For example, if you want a palette entry to be 7 smidgens above the 3rd major green, you would store the 7 in the upper four bits (the upper “nybble”) and the 3 in the lower four bits:
This explains the unusual (S*16)+C in line 51: (S*16) pushes the smidgen value four binary places to the left, and +C adds the major value to the lower four binary places. Doing this for every possible major value (FOR C=0 TO 15) and every possible minor value (FOR S=0 TO 15) gets us every possible green.
Another way to think of this is, there are 256 possible green values, stored as a byte value 0 to 255, with the two nybbles swapped. The green of magnitude $37 (= 55) is stored in palette memory as the value $73 (= 115).
There are 4 fractional bits available for green and blue, and 3 fractional bits available for red. The “least significant” red bit, in bit position 4 after the nybble swap, is reserved for a different purpose. With 23 bits to describe a color, that’s 2^23 or 8,388,608 possible colors, for each palette entry.
The Commodore 65 was intended to have a feature where its video output could be combined with another video signal, using an analog video synchronization technique called genlock. Back in the day, this technique was used to generate titles or graphics that appeared over a video camera recording or live broadcast.
Imagine using a Commodore 65 to generate the text that appears at the bottom of the screen of the nightly news. The C65 would produce a full screen image with the text at the bottom and the rest of the screen painted in a palette entry with this “transparency” bit set. When merged with the video camera signal, the news anchors would appear in the transparent portion.
We actually do have a future use for the transparency bit on the MEGA65, specifically for the MEGAphone project. In general, for the most future-proofed results, keep bit 4 of red palette values written to $D100-$D1FF clear.
In Character Study, we said that each character can be assigned a five-bit value that identifies the color of the character from the first 32 entries of the palette. But if characters can only use up to 32 possible colors, how did the example above display 256 different shades of green?
By using a new character graphics mode, of course! The magic is on line 35, and the original text mode is restored on line 95. Let’s take a closer look.
In Taste the Rainbow we introduced Low-res Multicolor Character Mode, or MCM. In this mode, characters can be either in high resolution (8 pixels across) with a single foreground color, or in low resolution (4 double-wide pixels across) with a choice of a character-specific foreground color or two screen-wide alternate colors for each pixel. MCM is enabled by setting bit 4 of register $D016.
For the character-specific foreground color, Low-res Multicolor Mode ignores color bits 4 through 7, and character attributes are disabled. In this new Full-palette Low-res Multicolor Mode, all eight color bits identify the foreground palette entry, from 0 to 255. To enable Full-palette Low-res Multicolor Mode, set bit 6 of register $D063 (FCOLMCM), in addition to bit 4 of register $D016.
One oddity of this mode is that it still behaves like MCM: if color bit 3 is set, the character image is rendered in bit-pair low resolution. This means that half of the palette entries render in low-res mode, and the other half in hi-res mode. In the greens example, we used the block glyph at screen code 160, which looks the same in both modes. To see the difference color bit 3 makes, change line 60 to replace the 160 screen code with, say, 65. Half of the shapes look different.
I can’t think of a reason why we couldn’t also have a full-palette non-MCM mode, using just the FCOLMCM register bit 6 of $D063, replacing attribute bits for color bits without the MCM feature. I filed this as a feature request. (I could be forgetting a detail that prevents this from being a good idea. It’s difficult to keep track of all of the video mode combinations!)
All of the character graphics modes we’ve seen so far have given us new ways to draw pictures, but they have insisted that we trade away features such as resolution or available characters in exchange for more colors. Tradeoffs are necessary because all of these modes use the same amount of memory for a given screen size: one byte of screen memory and one byte of color memory for each character. If we want to go further—and further we must go!—we need a screen mode that lets us exchange memory for features.
Super Extended Attribute Mode (or “SEAM”) is one of the VIC-IV’s big upgrades to Commodore character graphics. Enabling it doubles the amount of memory used for each character: 16 bits (or two bytes) of screen memory and 16 bits of color memory. This both expands the possible size of the character set and makes room for additional rendering features on a character-by-character basis (the “super-extended” attributes).
We’ll stay in BASIC for the rest of this Digest just to make it easy to experiment. Similar to other ways we’ve tried messing with the VIC-IV registers, the KERNAL screen editor doesn’t know anything about SEAM, so you can’t use its cursor-like printing features in these modes. The only way to take advantage of SEAM is to write directly to screen and color memory. In practice, these modes are easier to use from machine language, where we can do high-speed manipulation of memory.
Let’s set up a program that enables SEAM, waits for a keypress, then disables SEAM and returns to the READY prompt. We can put our experiments in the middle.
Step 1: Set the CHR16 register, bit 0 of register D054. The VIC-IV reads two consecutive bytes per character, of both screen memory and color memory.
Step 2: Set the line step according to the desired horizontal resolution. The LINESTEP register at D058-D059 is the number of bytes that make up each line of characters. In normal 80-column text mode, this value is 80, one byte per character. SEAM doubles the number of bytes per character, so if we’re staying in 80-column mode, the new value is 160. This may feel superfluous, but the LINESTEP register will come back in a big way in a later Digest!
Recall that 80-column text mode is controlled by the H640 register, D031 bit 7. You could switch to 40-column mode by clearing this bit, then set LINESTEP to 80, for forty columns of 16-bit screen and color codes.
Step 3: Set SCRNPTR D060-D063 to a memory location where we can put 80x25x2 bytes of screen memory. Let’s use $4.0000 for now.
Recall that the WPEEK() function takes an address, and returns the 16-bit value stored in two bytes, in little endian format. Similarly, the WPOKE command takes an address and a 16-bit value, then writes the value as two bytes. Line 30 uses two WPEEK() calls to read the 32-bit SCRNPTR address into two variables. Line 40 sets SCRNPTR to address $0004.0000, using two WPOKE calls. The original SCRNPTR setting is restored in line 970, as the program is exiting.
If you run this program now (and you should!), you’ll see whatever is sitting in screen and color memory, interpreted as two bytes per character. Press a key to restore normal text mode and the original SCRNPTR value.
Step 4: Clear the screen. We’ll explain how this works in a moment, but for now, add this to the program to start from a clean slate.
This FOR loop visits every character position in memory: 80 columns, 25 rows, 2 bytes per character.
Line 60 writes a space character (screen code 32) to each character position in screen memory, which we relocated to $4.0000. It uses WPOKE to treat “32” as two bytes, with the least significant byte (32) first and the most significant byte (0) second.
Line 70 and 80 write two bytes to color memory: the first byte is a 0, and the second byte is the color white (1) with no character attributes.
On exit, line 960 revises color memory for our normal text display, with all white text. We’re reusing the same color memory for both our normal display and the CHR16 display, but they use different data formats, so we need to put things back in order before switching off CHR16. Another option would be to relocate color memory the way we did screen memory to preserve the colors of the original normal-text display.
In CHR16 mode, each character gets two bytes of screen memory. This includes 13 bits for the screen code, and 3 bits for another feature that we’ll ignore for now. The screen code bits are arranged with the 8 least significant bits in the first byte, and the 5 most significant bits at the bottom of the second byte. As long as you’re not using the unmentioned feature, you can treat this as a 13-bit number stored in little endian format, and leave the last three bits set to zero.
In normal text mode, screen codes are 8 bits (one byte) wide, for a character set of 256 possible characters. In CHR16 mode, you can write any byte value from 0 to 255 to the first byte, with zero as the second byte, to see these characters as usual. Here’s our familiar PETSCII uppercase set, as four rows of 64 characters:
Notice that we can’t use the BASIC T@& special array as we did last month, because BASIC knows nothing about custom screen memory locations or CHR16 mode. Instead, we calculate the address and screen code for each location. We used a similar technique to POKE the PETSCII screen codes in an earlier Digest. Here, we use WPOKE and increment two bytes at a time to write the full CHR16 screen memory value.
8-bit screen codes can describe 256 possible characters. SEAM expands this to 13 bits, for a whopping 8,192 characters in a single character set! The easiest way to see this in action is to update the program to display more rows:
Check it out: lowercase letters and PETSCII graphics characters on the same screen! As usual, the VIC uses the character set memory pointed to by CHARPTR D068-D06A, and locates the image data for a given character by counting up by 8 bytes per character, up to the given screen code. With CHARPTR pointing at the PETSCII font, screen codes 0 to 255 refer to the PETSCII uppercase + graphic characters, and codes 256 to 511 refer to PETSCII lowercase + uppercase, because both sets are next to each other in memory.
But that’s not all! There are plenty more characters available beyond 511. For now, the memory the VIC sees is filled with non-character data, so it looks like random snow. A game or application can install all manner of game graphics, alternate typefaces, or frames of animation in this memory. Thanks to CHR16 mode, they can all live happily on the same screen.
That is, if you’re willing to spend the memory. To use all 8,192 possible characters in a single character set, you need to dedicate 8,192 x 8 = 65,536 bytes—the entire available RAM of a Commodore 64—to character set data. If you don’t need that much, just don’t use all of the screen codes.
Similar to screen memory, CHR16 mode doubles the amount of color memory used to describe each character. Only the second of the two bytes is used for actual color and attribute data, and this byte alone provides no additional color or character attribute features. This may be disappointing to hear at first, but the remaining eight bits are used for graphics features so powerful that we’ll have to discuss them in a later Digest.
As with normal text mode, the second byte of character memory can select from the first 32 palette entries for a character’s foreground color, using bits 0, 1, 2, 3, and 6. And as with normal text mode, bit 4 is a blink effect, bit 5 is a reverse effect, and bit 7 gives the character an underline.
Here’s a quick way to see all of the characters displayed in 32 colors with all of the attribute combinations. It’s a bit garish, but it gets the point across.
Note that this cannot be combined with the Full-palette Low-res Multicolor Mode we used for the 256 greens earlier. There’s another way to get more color in SEAM. We’ll take a good look at that in the next Digest.
With as many as 8,192 screen codes at our disposal, we can have all manner of graphics tiles on the screen in any combination. Imagine Crossroads with hundreds of enemy types.
There’s another thing we can do with such a large character set. On a screen 80 characters across and 25 characters down, there are 2,000 characters visible at any time. With CHR16 mode, we can put a different character in every screen location.
Our test program was displaying characters 64 per row just to keep the PETSCII character set tidy. To completely fill the screen with unique characters requires only a modest change to the loop:
What’s the big deal? With the screen filled with the first 2,000 characters of the character set, the character set is the screen. We can draw individual pixels onto the display by updating the character set!
One more flashback to the previous Digest: The character set describes what each character looks like as a monochrome set of pixels. Each character is eight pixels across and eight pixels down, represented as eight bytes from top to bottom, most significant bit on the left. You can store the character set anywhere in the first 384 KB of memory. The CHARPTR register D068-D06A stores the starting address.
For a character set of 2,000 characters and eight bytes per character, we need 16,000 bytes, or 15 KB. We’ve put 2,000 x 2 = 4,000 bytes of screen memory for our tapestry at address $4.0000, so we can put the character set immediately after it at address $4.0FA0.
Here’s one way to clear the character set so every character is an empty space:
Now for the tricky bit. This 640 x 200 pixel screen is organized into little 8 x 8 pixel stacks. To light up a specific pixel, we need to figure out which bit in which byte represents the pixel, then update that byte. Feel free to try this little math problem on your own.
Here’s what I came up with:
If this looks confusing, notice that Y >> 3 is the same as INT(Y / 8), that is, divide Y by 8 and drop the remainder. Y AND 7 is the same as the remainder that was dropped when dividing Y by 8. With 8 as the divisor, it’s faster to do this with bitwise math, and is how you would do it in machine language. 7-(X AND 7) is the bit number of the byte to change, with 7 being the leftmost bit.
Now that we can draw a dot at a given set of coordinates, we can do something fun, like this:
We’ve filled the screen with the entire character set, and are drawing pixels directly onto the character set, for a bitmap graphics display. Pretty neat!
Thanks for sticking with me through this series on character graphics. Each new concept is built on the last, and we still have at least two more major topics to cover.
This marks the 40th issue and the three year anniversary of Dan’s MEGA65 Digest. I’m grateful to everyone for their support throughout this project. I couldn’t do this without you.
If you’re finding this newsletter valuable, and I hope you are, consider becoming a supporter of the Digest. Visit: ko-fi.com/dddaaannn
Don’t touch that dial! More to come!
— Dan
So Many Things. Dan’s MEGA65 Digest for August 2025.
I haven’t been slacking, I swear! In fact, so much MEGA65 stuff has been going on in the last couple of months that I haven’t had time to work on feature articles for the Digest. I’m excited to have had the opportunity to contribute to both the Commodore International and Compute!’s Gazette revival projects. And I was able to present a talk and run an exhibit at Vintage Computer Festival West at the Computer History Museum in Mountain View, California.
Meanwhile, multiple talented people have released MEGA65 games and videos in the last month, so many that I haven’t had a chance to play them all until now. Let’s catch up a bit! We’ll pick up our color graphics series next issue, fingers crossed.
We’ve all thought about it. What would you do if you could rescue the original Commodore name from getting mired in the portfolios of private equity firms? Would you found a company with the community’s best interests at heart? Would you help foster the creativity of hundreds, and bring the joys of recreational computing to thousands? Would you take the best modern retro computing has to offer, and make it more accessible to the world?
Perifractic of the YouTube series Retro Recipes has gone and done just that. After many months of intense effort, Commodore International is now live. Not only that, they’re taking pre-orders for the Commodore 64 Ultimate, a modern all-in-one C64 with a vintage feel and modern conveniences. (Kind of like another project I’m rather fond of.) They’ve also got some pretty neat logo merch.
The team is a real who’s-who of Commodore veterans and modern retro developers. I’m grateful to be listed as a partner, though my contributions are all volunteer in my off hours.
Peri is gradually releasing a series of teaser-heavy videos laying the groundwork for the project, with more to come. Check these out if you haven’t already:
Congratulations to Perifractic and the team on this initial launch, and best wishes for the company’s success!
COMPUTE!’s Gazette, the venerable home computing magazine from the ’80s and ’90s, is also being reborn. Edwin Nagle acquired the rights to the name, and has started a new magazine all about modern retro computing across multiple platforms, old and new. The new Gazette has published its first issue, including a feature article on the new Commodore. You can buy the single issue as either a print + digital bundle or digital-only, or you can start a subscription.
I’m thrilled to announce that Gazette includes a regular column about the MEGA65, by me! I am syndicating Dan’s MEGA65 Digest feature articles with the magazine, starting with the first issue. Going forward, the column will include existing and new material, edited for the Gazette audience. The MEGA65 will have a regular presence in the magazine, in more ways than one.
Congrats to Edwin, the team at COMPUTE! Publishing, and contributors for making this happen!
retroCombs has completed his video series on the MEGA65 User’s Guide, with a video all about chapter 7: transferring files.
The full series dedicates a video to each chapter of the User’s Guide, providing useful video demonstrations and commentary:
Don’t miss more retroCombs MEGA65 videos for even more fun!
The YouTube channel Level Up with Jeffrey covers vintage and modern retro home computer games, with entertaining playthroughs and discussion. In the last year, Jeffrey has taken a shine to the MEGA65, in particular running Commodore 64 games in the MEGA65’s “GO64” compatibility mode (not just the C64 core). It’s great to be reminded that GO64 mode is actually quite capable. I tend to steer people toward the C64 core just to avoid disappointment with the occasional compatibility issue, but GO64 mode has workflow advantages with the MEGA65 Freezer, and is just neat from a technical perspective.
All of Jeffrey’s back catalog is fun to watch. As a programmer, I especially enjoyed his recent diversion from form, Fixing C64 Frantic Freddie: For MEGA65, in which Jeffrey uses the MEGA65’s built-in programming tools to dig into the C64 classic’s machine code. This video is a follow-up to 8-Bit Show and Tell’s recent Frantic Freddie code dive using Super Snapshot on the C64. Both videos are gold for fans of the game, or of screenfuls of assembly language.
Can’t wait for me to get around to finishing my Digest series on advanced graphics? Other MEGA65 developers have been documenting their own journeys for your benefit.
Here’s a look ahead at common abbreviations used to describe VIC-IV graphics features:
RetroCogs, the author of the upcoming side-scrolling shooter First Shot, has published two new articles on VIC-IV graphics: Setting up FCM and NCM and Using RRB for Pixies. RetroCogs has previously published a few other blog articles, MEGA65 graphics tutorials in Github, and the beginnings of a MEGA65 game development framework called GameShell65.
ubik has published a BASIC65 full-color image demo, a demonstration of full-screen images rendered with Full-Color Mode character graphics. Also check out this nifty tutorial in C for the MEGA65, with a Python script for converting images from the PNG format for use with this code.
Gurce has been doing his own SEAM experiments, in Eleven/BASIC 65, including the beginnings of a graphics editor and experiments with pixies and bounding box collision detection. Gurce reports that these advanced graphics modes are little bit too CPU intensive for performing large animations in pure BASIC, but BASIC examples are useful to understand the ideas.
I still intended to document my own journey learning these features here in this Digest. I’m grateful to everyone who has published articles and sample code, so I can compare notes.
GRAVMAN, a MEGA65-themed gravity-lander game by Robhawk (RobH on Discord). RUN "GRAVMAN.PRG" to start. Joystick fire to thrust upward, left and right to thrust sideways. Collect MEGA65 pieces—and pages of this Digest!—and land safely on the green pad. Level editor included (RUN "LEVEL-EDITOR"), and don’t miss the well-commended BASIC source code.
Commando B65, by SirGeldi, a tribute game to Commando for the C64, using character graphics and a scrolling playfield. Music by Gurce. Use a joystick in port 2 to move, and move + fire to shoot in the direction of movement.
HAMURABI 65, by BOBELE. A MEGA65 version of a simple economic simulation, based on the game published in 101 BASIC COMPUTER GAMES by David Ahl of Digital Equipment Corporation in 1975.
Mitosis, by ubik. A strategy board game for two players, similar to Ataxx. Claim territory by adding your pieces to the board, and mutate your opponent’s neighboring pieces.
So many people are pushing their projects forward this summer (or winter as the case may be), and sharing updates with the community. Rhialto is making steady progress on the PET core (Discord link), including in-progress SuperPET extensions, still in “pre-release” status; download the latest. sy2002 has released an experimental update to the C64 core (Discord link) specifically for use with IDE64, a modern mass storage cartridge device. TwoBitRetro continues to revise the beta version of Pascal65. Boris is keeping the alternate cores documentation up to date. xlar54 continues work on an Internet-capable networking library (see #weeip on Discord). ctalkobt is working on an assembler with text editor (see #vim). Bobby Tables is working on an all-new fast assembler engine for Mega Assembler (see #super-mega-assembler). And of course Paul is still working on the MEGAphone and posting updates to his developer blog. And these are just projects I know about from Discord discussions.
You’re all crushing it! Keep up the good work!
I’ve been looking forward to the Vintage Computer Festival West all year, and it did not disappoint. This two-day event at the Computer History Museum in Mountain View, California, USA, received over 3,500 attendees. It featured two full days of talks, a large exhibition floor, and three rooms of consignment sales.
This year’s event celebrated the 40th anniversary of the Amiga with a special exhibit of early Amiga prototypes from 1984, multiple Amiga-related talks, and a special ticketed event recreating the famous Amiga launch event at Lincoln Center, including a bit of fun with stand-ins for Andy Warhol and Debbie Harry.
I had the honor of being the first speaker of the show. I was busy working on the talk all the way up to the last minute, including on breaks during the 14 hour drive down from Seattle. It went pretty well. I wish I had more time to fill out my thesis with research, but it was sufficient for a casual talk at a volunteer event. VCF West will upload talk videos at a later date, and I’ll be sure to mention it when they do.
I didn’t get to see much of the show otherwise, as I was also an exhibitor, and my booth had almost full-day attendance on both days. I was grateful to meet for the first time in person multiple people I already know from Discord communities, including MEGA65 owners. I also met more than one former Commodore employee, and more than one professional author of 1980’s computer books and magazine articles.
The highlight of the event for me was meeting Paul Lassa, one of the original developers of the Commodore 65. We talked for a long while about working for Commodore, the challenges of the C65 project, and the present and future of the MEGA65 project. I’m very pleased to know that Paul owns a MEGA65 and reads this newsletter. As such, I’ll try not to embarrass him further. 😅 I’m grateful that Paul sought out my exhibit and was generous with his time. Thank you Paul!
I was also delighted to briefly meet Kenneth Dyke, a graphics software engineer with a long career at Apple that also worked on the Amiga at Commodore. Kenneth also wrote the 6502 CPU Verilog implementation that the MEGA65 uses to power its Matrix Mode debugger. He was walking around the show wearing a backpack containing two Commodore 65 prototypes. I’m not sure why. We got them out and took some pictures.
I had brief moments to run around the floor and take some quick pictures. This is not a good representation of the show overall, but who doesn’t love pictures of old computers!
See magic-1.org, a website served by the Magic-1 homebrew computer.
“I love deadlines. I love the whooshing noise they make as they go by.” — Douglas Adams
What’s next? I still have a bunch of work to finish for Commodore, which will probably eat up my hobby time for August. Then I’m off to Schaumburg, Illinois, for VCF Midwest 2025, September 13 and 14, where I will be giving another talk and running another MEGA65 exhibit. They have two speaker tracks this year, and thanks to generous volunteers, both tracks will be recorded. My speaker slot for this one is Saturday at 10:30 am in the secondary venue.
I’m eager to get back to MEGA65 ROM and documentation work, not to mention the Digest. I’d love to write a game someday. Man, too much to do.
— Dan
From the publisher's feed