Don't get me wrong. I love AI. I also hate AI. It's been great to me for writing C# utilities for work, or SQL queries, reviewing code, writing scripts in languages I don't even know (LUA) -- yeah that looks right, let's put that in production.
But for Commodore? Spoilers -- it's really horrible. I'll get back to that later.
My "fun" task for the day? I want to finally program high resolution graphics on my C128 VDC 80 column screen. It's well capable of 80x25. And I bought my C128D with 64K RAM specifically knowing it could do that high resolution graphics well. The original only came with 16K, and when it arrived COD at my door in 1987, I refused it and sent it back because of 11th hour nearly buyers remorse, delaying my gratification to wait a few more weeks for the newer C128D to arrive with more RAM.
The C64 could only do 320x200 graphics because it's a 40 column machine. The C128 had 80 columns and thus twice the resolution because it included two graphics chips and had two separate video outputs. BASIC 7.0 finally supported 320x200, but not graphics specific to the 80 column chip. I even purchased GEOS 128 with mouse and 1750 memory expansion, and saw the fulfillment of 640x200 graphics.
But was I fulfilled? No.
I am a programmer. Features don't exist until I code the programs that make them exist. I enjoy the process of learning APIs and languages, and developing new skills.
Later in 1988 I investigated the VDC (C128's 80 column video display controller) closer, with the guide of some magazine articles, and close scrutiny of the Commodore 128 Programmer's Guide which had a mere 4 pages of a minimalistic datasheet. I remember wanting to know how to do it. But as far as I got with changing and programming the VDC registers was a 40 column mode, and an 80x50 interlaced text mode. The former was released as RGB64, and the later was released as node-m (a unique terminal program). Graphics abilities unique to the C128 were not accomplished by me. Not in the 80s.
Now it's nearly 40 years later. VDC graphics programming is still a challenge I want to conquer. It's 2026, in the golden age of AI, technical assistants available at our fingertips.
Could AI provide me a working sample? No. I went round and round with it for hours. Gave up, and came back another weekend day for hours. Did it work? No. Was I close? Maybe.
One web link from AI gave a hint though. The reference was Commodore 128 Programming Secrets. The sample program on page 274, Figure 5-4, was short, just 25 lines, and didn't look very complicated. Obviously this was a joke, you can't achieve such a lofty goal as complicated VDC graphics in just 25 lines. There weren't even very many VDC register writes. But I typed it in, and once I figured out some decent parameters to feed it at runtime (10, -10, 1, -1), the results were presented on screen.
640x200 monochrome graphics sample
- Initialize raster graphics mode, giving up text screen, attributes, and character images
- Write to screen memory as bytes going across, then down. First and high bits are on the left, last and low bits are on the right. In other words, use math.
- Follow VDC register rules. And use block fill feature for speed.
VDC rules
- Access to VDC is through two C128 memory-mapped locations:
- D600 is generally used to store a register number (and read status)
- D601 is used to store a register value
- VDC is controlled through its registers.
- Access to VDC RAM (16K or 64) is only through the registers as well.
- Want to write to a register? Store the register index in D600, then wait for the high bit to be set on D600 before storing the desired value in D601.
- Want to read a register? store the register index in D600, then wait for the high bit to be set on D600 before reading the value from D601.
- Want to write to memory? At some point you have to set the address using registers 18, 19. After that you can set the value in register 31.
- For a block fill, you must write one memory value first, then you can write a count of the remaining slots you also want to fill. If you want to store 100 bytes total, you store 99 here.
- Values in registers 18 (high), 19 (low) auto-increment, so either reading or writing various values to consecutive bytes doesn't require rewriting the address. But reading from the address, and writing back to it will require rewriting the address, otherwise it has jumped forward by one.
- Screen memory is pointed to by registers 12, 13.
- Attribute memory is pointed to by registers 20, 21.
- Character images is pointed to by register 28 (high 3 bits). Shift those bits down and multiply by $2000 to get the VDC RAM address, or in reverse shift up.
- VDC defaults to 16K RAM operation. 64K mode will scramble how the data is addressed, so would require reinitializing the screen, attributes, and characters.
- Graphics mode (bit 7 of register 25) means that the VDC will render to the monitor line and byte by byte or bit by bit from screen memory.
- 640x200 monochrome requires 16000 bytes, so not enough left for screen, attributes, characters.
- Other graphic modes are possible including using attributes for choosing different colors for cells, and even changing the dimensions of the cell (allows for more color changes). I'm not versed in this yet.
- There are plenty of registers you should not change unless you understand them well, or are willing to take a risk.
- C128D includes 8568 chip with the addition of an IRQ line when command completed (not wired to anything), and Sync Polarity options in a new register 37.
- Attributes are different than C64 color memory. These are whole bytes! The lower 4 bits are RGBI (red/green/blue/intensity). The upper 4 bits are (character set, reverse, underline, and blink). The character set bit option lets you have 512 different characters on screen at any one time. And the reverse bit is useful for game developer, but not necessary for the Commodore character sets as they already have reverse in their latter half of their sets.
- Attributes are optional. See AttrEnable bit 6 in register 25 for turning it off (0).
Tricks
- BANK 15 (shown as the first F digit below in the monitor listing) is the default bank when C128 starts up. This memory map has access to I/O and KERNAL ROM.
- C128 KERNAL already has routines for writing/reading to/from the VDC registers. These can be leveraged from BASIC as needed, especially useful for prototyping.
SYS DEC("CDDA"),,R : RREG V : REM Read VDC Register
SYS DEC("CDCC"),V,R : REM Write VDC register - C128 KERNAL has a routine to reset character images in the VDC RAM. JSR FF62. (Thanks AI for that tidbit scraped off the web.)
- SCNCLR is a good way to reset screen and attribute memory. Similar to GRAPHIC 5,1 and PRINT CHR$(147).
. fcdca a2 1f ldx #$1f
. fcdcc 8e 00 d6 stx $d600
. fcdcf 2c 00 d6 bit $d600
. fcdd2 10 fb bpl $cdcf
. fcdd4 8d 01 d6 sta $d601
. fcdd7 60 rts
. fcdd8 a2 1f ldx #$1f
. fcdda 8e 00 d6 stx $d600
. fcddd 2c 00 d6 bit $d600
. fcde0 10 fb bpl $cddd
. fcde2 ad 01 d6 lda $d601
. fcde5 60 rtsArguing with AI
- I had to explain I didn't have BASIC 8.0, only 7.0
- Explaining GRAPHIC 5 doesn't actually have anything to do with graphics
- No, the processor doesn't have direct access to VDC RAM
- Variable names cannot have underscores
- Variable names must be two letters at most to differentiate from each other
- Spaces are not required between keywords and variable names
- Just arguing about nonsense that the AI had completely backwards.
- AI putting in comments REM Completely Fixed Version
- Explaining the right way of doing things, and AI insisting it was right instead
- Explaining the right way of doing things, and AI treating me as I had led it astray in the first place, that it was my fault
- No you can't prompt for values on the same screen trying to draw graphics. But you can use the 40 column screen simultaneously using GRAPHIC 0
- No, that order of code is correct, if we change it, it will fail worse
- Telling me there are two bugs, then listing three bugs
- Outright hallucinations
Short prompt
Please provide me a Commodore 128 BASIC 7.0 working example of drawing a circle on the VDC 640x200 display in monochrome bitmapped mode using any facilities in a stock C128 available. Please provide a BASIC listing output in lowercase text only that will be accepted by a Commodore 128.
LLMs tested:
AI results
110 d = 3 - (radius << 1) 120 rem draw circle using Bresenham's algorithm 130 while x <= y 140 rem plot eight points of the circle 150 poke cx + x, cy + y : poke cx - x, cy + y
30 def fnw(v,r):bank15:sys52684=0:rem placeholder10 graphic 5,1
20 color 0,2,1
30 circle 1,320,100,60,60
40 getkey a$
50 graphic 0,1Resulting in the following, because CIRCLE doesn't work in 80 column mode, because it's a text mode, not a graphics mode. No GRAPHIC command will put you in a graphics mode using VDC.?NO GRAPHICS AREA ERROR IN 30
10 graphic 5,1
20 color 0,1
30 color 1,2
40 circle 1,320,100,80,80
50 char 1,1,23,"press any key to exit"
60 do : loop until inkey$<>""
70 graphic 0,1
80 end100 rem-----------------------------------------------------------------------
110 rem vdc 640x200 monochrome
120 rem copyright (c) 2026 by david r. van wagner
130 rem mit license
140 rem
150 rem davevw.com
160 rem github.com/davevw
170 rem mit license
180 rem-----------------------------------------------------------------------
190 def fnac(x)=2*atn(sqr(1-x*x)/(1+x))
200 trap 900
210 graphic5,1:fast
220 for i=1 to 21:print chr$(17);:next
230 data 1,2,4,8,16,32,64,128:dimbi(7):fori=0to7:readbi(i):next
240 print"screen";:r=12:gosub 1040:gosub 1050:sc=ad
250 print"attrs ";:r=20:gosub 1040:gosub 1050:at=ad
260 print"chars ";:r=28:gosub 1000:ad=int(v/32)*8192:gosub1050:cr=ad
270 for i=1 to 1000:next:scnclr
280 r=25:gosub 1000:v=v and (255-64) or 128:gosub 1010
290 ad=sc:gosub1060:wait dec("d600"),128:f=0:c=16000:gosub 1070
300 for x=0 to 639:a=fnac((x-320)/321):y=int(99*sin(a)+100):gosub1080
310 y=199-y:gosub 1080:next x
320 getkey a$
900 r=25:gosub 1000:v=v and 127 or 64:gosub 1010:sys dec("ff62"):scnclr
910 if er<>-1 then print err$(err)" at line "el
999 end:rem ******* reusable subroutines *******
1000 sys(dec("cdda")),,r:rreg v:return:rem ** read vdc reg r into v **
1010 sys(dec("cdcc")),v,r:return:rem ** write vdc reg r value v **
1020 sys(dec("cdcf")),v:return:rem ** write next byte **
1030 sys(dec("cddd")):rreg v:return:rem ** read next byte **
1040 gosub 1000:ad=v*256:r=r+1:gosub 1000:ad=ad+v:return:rem ** get address **
1050 printusing" ##";r-1;:printusing" ##";r;:printusing" #####";ad:return:****
1060 r=18:v=ad/256:gosub1010:r=19:v=adand255:gosub1010:return:rem *update addr*
1070 i=0:do while i<c:rem ** block fill (begin) **
1071 r=31:v=f:gosub 1010
1072 v=c-i-1:if v>0 then begin
1073 :ifv>255thenv=255
1074 :r=30:gosub1010
1075 bend
1076 i=i+v+1
1077 wait dec("d600"),128
1078 loop
1079 return:rem ** block fill (end) **
1080 ad=sc+int(y)*80+int(x/8):gosub1060:rem ** plot (begin) **
1081 r=31:gosub1000:n=v or bi(7-(xand7)):gosub1060:r=31:v=n:gosub1010
1082 return:rem ** plot (end) **Conclusion
You have to be pretty smart to use AI effectively. I may have failed myself this round using AI. Throwing too much faith into AI is also a mistake.













