Super Extended Attribute Mode
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?
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

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

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

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 on the MEGA65

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!
Featured Files
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.
Is that a new release package?
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.
Eight million colors

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:
10 BACKGROUND 0:BORDER 0:SCNCLR
20 SA=WPEEK($D060)+WPEEK($D062)*65536
30 CA=$1F800
40 FOR C=0 TO 15
50 PALETTE COLOR C,0,C,0
60 POKE SA+C,160 : REM solid block character
70 POKE CA+C,C
80 NEXT C
90 GETKEY A$
100 PALETTE RESTORE
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.
10 BACKGROUND 0:BORDER 0:SCNCLR
20 SA=WPEEK($D060)+WPEEK($D062)*65536
30 CA=$1F800
35 SETBIT $D063,6:SETBIT $D016,4
40 FOR C=0 TO 15:FOR S=0 TO 15
50 POKE $D100+(C*16)+S,0
51 POKE $D200+(C*16)+S,(S*16)+C : REM see explanation
52 POKE $D300+(C*16)+S,0
60 POKE SA+(C*80)+S,160 : REM solid block character
70 POKE CA+(C*80)+S,(C*16)+S
80 NEXT S:NEXT C
90 GETKEY A$
95 CLRBIT $D063,6:CLRBIT $D016,4
100 PALETTE RESTORE

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:
3 = %0011
7 = %0111
%0111 0011 = $73 = 115
POKE $D200+P, $73
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.
About that reserved red bit

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.
Full-palette Low-res Multicolor Mode
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.
35 SETBIT $D063,6:SETBIT $D016,4
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!)
Introducing Super-Extended Attribute Mode
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.
Setting up SEAM
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.
10 SETBIT $D054,0
900 GETKEY A$
990 CLRBIT $D054,0
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!
20 WPOKE $D058,160
980 WPOKE $D058,80
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.
30 O1=WPEEK($D060):O2=WPEEK($D062)
40 WPOKE $D060,$0000:WPOKE $D062,$0004
970 WPOKE $D060,O1:WPOKE $D062,O2
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.
50 FOR P=0 TO 80*25*2-1 STEP 2
60 WPOKE $40000+P,32 : REM SCREEN CODE
70 POKE $FF80000+P,0
80 POKE $FF80000+P+1,1 : REM COLOR + ATTR
90 NEXT P
960 FOR P=0 TO 80*25-1:POKE $FF80000+P,1:NEXT P
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.
Using CHR16 screen codes

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:
100 FOR R=0 TO 3
110 FOR C=0 TO 63
120 WPOKE $40000+(R*80+C)*2,R*64+C
130 NEXT C
140 NEXT R

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:
100 FOR R=0 TO 24

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.
Using CHR16 color and attribute 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.
121 POKE $FF80000+((R*80)+C)*2,0
122 POKE $FF80000+((R*80)+C)*2+1,R*64+C AND 255
950 PALETTE RESTORE

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.
The MEGA65’s not-so-secret bitmap mode
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:
100 FOR R=0 TO 24
110 FOR C=0 TO 79
120 WPOKE $40000+(R*80+C)*2,R*80+C
130 NEXT C
140 NEXT R
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.
150 WPOKE $D068,$0FA0:POKE $D06A,$04
940 WPOKE $D068,$1000:POKE $D06A,$00
Here’s one way to clear the character set so every character is an empty space:
160 FOR I=0 TO 15999:POKE $40FA0+I,0:NEXT I
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:
190 X = 300
200 Y = 100
210 AD = $40FA0 + ((Y >> 3) * 80 + (X >> 3)) * 8 + (Y AND 7)
220 SETBIT AD, 7-(X AND 7)
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:
190 FOR X = 0 TO 639
200 Y = INT(SIN(X/640 * 2 * π) * 100) + 100
230 NEXT X
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