Exploring Atari VCS/2600 Adventure – part 4

See also: part 1, part 2, part 3, part 4 … and more to come…

Objectively speaking

Welcome back to the world of Atari Adventure! After spending some time figuring out how the rooms were drawn, it’s time to look at how the game objects are drawn.

Adventure contains many objects that can be displayed:

  1. Bat
  2. Bridge
  3. Castle gate (or Portcullis – “a strong, heavy grating sliding up and down in vertical grooves, lowered to block a gateway to a fortress or town.”)
  4. Chalice
  5. Created by Warren Robinett (author’s name)
  6. Dot (for getting in to the easter egg room)
  7. Dragon (red, green and yellow)
  8. Key
  9. Magnet
  10. Number “1” (for game select screen)
  11. Number “2”
  12. Number “3”
  13. Sword

Some objects have multiple frames. For instance, the bat has two: wings up, and wings down. The dragon has three: open mouth, closed mouth, dead. The dragons can also be drawn facing left, or facing right.

I also found an entry for something called “Surround,” which appears to be the the square around the player in the invisible mazes.

In the ROM disassembly, it looks like these objects are just stored as bytes that represent them:

GfxChallise:
  .byte $81 ;X      X                                                                  
  .byte $81 ;X      X                                                                  
  .byte $C3 ;XX    XX                                                                  
  .byte $7E ; XXXXXX                                                                   
  .byte $7E ; XXXXXX
  .byte $3C ;  XXXX
  .byte $18 ;   XX
  .byte $18 ;   XX
  .byte $7E ; XXXXXX
  .byte $00 

Above, the game-winning chalice appears to be 8×9.

The dragon is much larger:

GfxDrag0:
  .byte $06 ;     XX 
  .byte $0F ;    XXXX 
  .byte $F3 ;XXXX  XX 
  .byte $FE ;XXXXXXX 
  .byte $0E ;    XXX 
  .byte $04 ;     X
  .byte $04 ;     X
  .byte $1E ;   XXXX
  .byte $3F ;  XXXXXX
  .byte $7F ; XXXXXXX
  .byte $E3 ;XXX   XX
  .byte $C3 ;XX    XX
  .byte $C3 ;XX    XX
  .byte $C7 ;XX   XXX
  .byte $FF ;XXXXXXXX
  .byte $3C ;  XXXX
  .byte $08 ;    X
  .byte $8F ;X   XXXX
  .byte $E1 ;XXX    X
  .byte $3F ;  XXXXXX
  .byte $00

But it is still represented as 8 pixels wide. The code to display it must magnify it to make it larger on screen.

Even the bridge, the widest object displayed in the game, is represented as 8-bits wide. This is the first thing we will need to dig in to… What controls how large these small object representations are drawn?

Also, it appears every object is terminated with a $00 rather than having the length at the start. For example, instead of this:

10 READ N:FOR I=1 TO N:READ A$:PRINT A$:NEXT:END
20 DATA 10,a,b,c,d,e,f,g,h,i,j

…it works like this:

10 READ A$:IF A$="0" THEN END ELSE PRINT A$:GOTO 10:END
20 DATA a,b,c,d,e,f,g,h,i,0

Since both would have taken the same amount of data storage space in the ROM, I am betting the code to parse that data may have been smaller to loop and check for 0 versus loading a size and counting down.

Also, this presents a restriction for the graphics — none can contain an “empty” row in the graphic ($00). Because of this, each line (byte) must have at least one pixel set. This explains the dots in the easter egg signature!

;Object #4 : State FF : Graphic
GfxAuthor:
 .byte $F0    ;XXXX
 .byte $80    ;X
 .byte $80    ;X
 .byte $80    ;X
 .byte $F4    ;XXXX X
 .byte $04    ;     X
 .byte $87    ;X    XXX
 .byte $E5    ;XXX  X X
 .byte $87    ;X    XXX
 .byte $80    ;X
 .byte $05    ;     X X
 .byte $E5    ;XXX  X X
 .byte $A7    ;X X  XXX
 .byte $E1    ;XXX    X
 .byte $87    ;X    XXX
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $F0    ;XXXX
 .byte $01    ;       X
 .byte $40    ; X
 .byte $E0    ;XXX
 .byte $40    ; X
 .byte $40    ; X
 .byte $40    ; X
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $E0    ;XXX
 .byte $80    ;X
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $20    ;  X
 .byte $20    ;  X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $01    ;       X
 .byte $01    ;       X
 .byte $88    ;   X   X
 .byte $A8    ;X X X
 .byte $A8    ;X X X
 .byte $A8    ;X X X
 .byte $F8    ;XXXXX
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $F0    ;XXXX
 .byte $01    ;       X
 .byte $80    ;X
 .byte $E0    ;XXX
 .byte $8F    ;X   XXXX
 .byte $89    ;X   X  X
 .byte $0F    ;    XXXX
 .byte $8A    ;X   X X
 .byte $E9    ;XXX X  X
 .byte $80    ;X
 .byte $8E    ;X   XXX
 .byte $0A    ;    X X
 .byte $EE    ;XXX XXX
 .byte $A0    ;X X
 .byte $E8    ;XXX X
 .byte $88    ;X   X
 .byte $EE    ;XXX XXX
 .byte $0A    ;    X X
 .byte $8E    ;X   XXX
 .byte $E0    ;XXX
 .byte $A4    ;X X  X
 .byte $A4    ;X X  X
 .byte $04    ;     X
 .byte $80    ;X
 .byte $08    ;    X
 .byte $0E    ;    XXX
 .byte $0A    ;    X X
 .byte $0A    ;    X X
 .byte $80    ;X
 .byte $0E    ;    XXX
 .byte $0A    ;    X X
 .byte $0E    ;    XXX
 .byte $08    ;    X
 .byte $0E    ;    XXX
 .byte $80    ;X
 .byte $04    ;     X
 .byte $0E    ;    XXX
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $80    ;X
 .byte $04    ;     X
 .byte $0E    ;    XXX
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $00

Those rows would have been empty, but having a $00 there would have prevented there rest from being drawn. Interesting!

Draw something

For today’s installment, I’ll be using Color BASIC on a CoCo emulator to write a simple program to plot these objects on the screen. I copied all of the assembly definitions into an editor then did a search and replace to turn those .byte instructions into incredibly inefficient one-number-per-line DATA statements like this:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81 ' X      X   
2860 DATA &H81 ' X      X   
2870 DATA &HC3 ' XX    XX   
2880 DATA &H7E '  XXXXXX    
2890 DATA &H7E '  XXXXXX   
2900 DATA &H3C '   XXXX     
2910 DATA &H18 '    XX      
2920 DATA &H18 '    XX      
2930 DATA &H7E '  XXXXXX    
2940 DATA &H00

Unfortunately, READ/DATA in Color BASIC does not allow that. It will READ the first value, then give an ?SN ERROR when it tries to read the next because the parser doesn’t handle the apostrophe “REM” marker!

So I changed it to this:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81:REM X      X   
2860 DATA &H81:REM X      X   
2870 DATA &HC3:REM XX    XX   
2880 DATA &H7E:REM  XXXXXX    
2890 DATA &H7E:REM  XXXXXX   
2900 DATA &H3C:REM   XXXX     
2910 DATA &H18:REM    XX      
2920 DATA &H18:REM    XX      
2930 DATA &H7E:REM  XXXXXX    
2940 DATA &H00

This also fails — and is a problem I only recently discovered when writing my Base-64 articles.

I guess I can’t have this code be as bulky and inefficient as I wanted. I removed them so it was just the DATA statements:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81
2860 DATA &H81
2870 DATA &HC3
2880 DATA &H7E
2890 DATA &H7E
2900 DATA &H3C
2910 DATA &H18
2920 DATA &H18
2930 DATA &H7E
2940 DATA &H00

Oh well. Who needs comments anyway — especially in BASIC where they just bloat the code and slow it down.

A quick-and-dirty routine allowed me to see it was working:

Atari Adventure graphics rendered on a CoCo :)

…but there was no way I was going to be able to fit the “Created by Warren Robinett” graphic on a low-res 64×32 screen. To solve this, I hacked together a version that used a “high resolution” screen:

Atari Adventure graphics rendered in “high resolution” on a CoCo :)

It looks like the data is pretty straight forward. The dragons would have to be drawn reversed to make them face right, and the bridge looks like it needs an X scaling factor added to make it as wide as it is in the game. But, not bad for a first attempt.

Just for fun, here is the brute-force hacky nasty Color BASIC code I quickly put together to do this:

10 REM AdvObjs.bas
20 POKE 65395,0
30 FOR A=0 TO 7:BT(A)=2^A:NEXT
40 CLS0:XS=1:YS=1:Y=1
50 PMODE 1,1:PCLS:SCREEN 1,0
60 READ V:IF V=-1 THEN 120 ELSE IF V=0 THEN 90
70 X=XS:FOR A=7 TO 0 STEP-1:IF V AND BT(A) THEN PSET(X*2,Y*2,C)
80 X=X+1:NEXT:Y=Y+1:GOTO 60
90 XS=XS+9:IF XS>120 THEN XS=1:YS=YS+32
100 C=C+1:IF C=1 THEN C=2 ELSE IF C>3 THEN C=0
110 Y=YS:GOTO 60
120 GOTO 120
130 ' Object #0A : State FF : Graphic  
140 ' GfxBridge:
150 DATA &HC3
160 DATA &HC3
170 DATA &HC3
180 DATA &HC3
190 DATA &H42
200 DATA &H42
210 DATA &H42
220 DATA &H42
230 DATA &H42
240 DATA &H42
250 DATA &H42
260 DATA &H42
270 DATA &H42
280 DATA &H42
290 DATA &H42
300 DATA &H42
310 DATA &H42
320 DATA &H42
330 DATA &H42
340 DATA &H42
350 DATA &HC3
360 DATA &HC3
370 DATA &HC3
380 DATA &HC3
390 DATA &H00
400 ' Object #5 State #1 Graphic :'1'  
410 ' GfxNum1:
420 DATA &H04
430 DATA &H0C
440 DATA &H04
450 DATA &H04
460 DATA &H04
470 DATA &H04
480 DATA &H0E
490 DATA &H00
500 ' Object #0B : State FF : Graphic  
510 ' GfxKey:
520 DATA &H07
530 DATA &HFD
540 DATA &HA7
550 DATA &H00
560 ' Object #5 State #2 Grphic :
570 ' GfxNum2:
580 DATA &H0E
590 DATA &H11
600 DATA &H01
610 DATA &H02
620 DATA &H04
630 DATA &H08
640 DATA &H1F
650 DATA &H00
660 ' Object #5 State #3 Graphic :'3'  
670 ' GfxNum3:
680 DATA &H0E
690 DATA &H11
700 DATA &H01
710 DATA &H06
720 DATA &H01
730 DATA &H11
740 DATA &H0E
750 DATA &H00
760 ' Object #0E : State 03 : Graphic  
770 ' GfxBat1:
780 DATA &H81
790 DATA &H81
800 DATA &HC3
810 DATA &HC3
820 DATA &HFF
830 DATA &H5A
840 DATA &H66
850 DATA &H00
860 ' Object #0E : State FF : Graphic  
870 ' GfxBat2:
880 DATA &H01
890 DATA &H80
900 DATA &H01
910 DATA &H80
920 DATA &H3C
930 DATA &H5A
940 DATA &H66
950 DATA &HC3
960 DATA &H81
970 DATA &H81
980 DATA &H81
990 DATA &H00
1000 ' Object #6 : State #00 : Graphic  
1010 ' GfxDrag0:
1020 DATA &H06
1030 DATA &H0F
1040 DATA &HF3
1050 DATA &HFE
1060 DATA &H0E
1070 DATA &H04
1080 DATA &H04
1090 DATA &H1E
1100 DATA &H3F
1110 DATA &H7F
1120 DATA &HE3
1130 DATA &HC3
1140 DATA &HC3
1150 DATA &HC7
1160 DATA &HFF
1170 DATA &H3C
1180 DATA &H08
1190 DATA &H8F
1200 DATA &HE1
1210 DATA &H3F
1220 DATA &H00
1230 ' Object 6 : State FF : Graphic    
1240 ' GfxDrag1:
1250 DATA &H80
1260 DATA &H40
1270 DATA &H26
1280 DATA &H1F
1290 DATA &H0B
1300 DATA &H0E
1310 DATA &H1E
1320 DATA &H24
1330 DATA &H44
1340 DATA &H8E
1350 DATA &H1E
1360 DATA &H3F
1370 DATA &H7F
1380 DATA &H7F
1390 DATA &H7F
1400 DATA &H7F
1410 DATA &H3E
1420 DATA &H1C
1430 DATA &H08
1440 DATA &HF8
1450 DATA &H80
1460 DATA &HE0
1470 DATA &H00
1480 ' Object 6 : State 02 : Graphic    
1490 ' GfxDrag2:
1500 DATA &H0C
1510 DATA &H0C
1520 DATA &H0C
1530 DATA &H0E
1540 DATA &H1B
1550 DATA &H7F
1560 DATA &HCE
1570 DATA &H80
1580 DATA &HFC
1590 DATA &HFE
1600 DATA &HFE
1610 DATA &H7E
1620 DATA &H78
1630 DATA &H20
1640 DATA &H6E
1650 DATA &H42
1660 DATA &H7E
1670 DATA &H00
1680 ' Object #9 : State FF : Graphics  
1690 ' GfxSword:
1700 DATA &H20
1710 DATA &H40
1720 DATA &HFF
1730 DATA &H40
1740 DATA &H20
1750 DATA &H00
1760 ' Object #0F : State FF : Graphic  
1770 ' GfxDot:
1780 DATA &H80
1790 DATA &H00
1800 ' Object #4 : State FF : Graphic   
1810 ' GfxAuthor:
1820 DATA &HF0
1830 DATA &H80
1840 DATA &H80
1850 DATA &H80
1860 DATA &HF4
1870 DATA &H04
1880 DATA &H87
1890 DATA &HE5
1900 DATA &H87
1910 DATA &H80
1920 DATA &H05
1930 DATA &HE5
1940 DATA &HA7
1950 DATA &HE1
1960 DATA &H87
1970 DATA &HE0
1980 DATA &H01
1990 DATA &HE0
2000 DATA &HA0
2010 DATA &HF0
2020 DATA &H01
2030 DATA &H40
2040 DATA &HE0
2050 DATA &H40
2060 DATA &H40
2070 DATA &H40
2080 DATA &H01
2090 DATA &HE0
2100 DATA &HA0
2110 DATA &HE0
2120 DATA &H80
2130 DATA &HE0
2140 DATA &H01
2150 DATA &H20
2160 DATA &H20
2170 DATA &HE0
2180 DATA &HA0
2190 DATA &HE0
2200 DATA &H01
2210 DATA &H01
2220 DATA &H01
2230 DATA &H88
2240 DATA &HA8
2250 DATA &HA8
2260 DATA &HA8
2270 DATA &HF8
2280 DATA &H01
2290 DATA &HE0
2300 DATA &HA0
2310 DATA &HF0
2320 DATA &H01
2330 DATA &H80
2340 DATA &HE0
2350 DATA &H8F
2360 DATA &H89
2370 DATA &H0F
2380 DATA &H8A
2390 DATA &HE9
2400 DATA &H80
2410 DATA &H8E
2420 DATA &H0A
2430 DATA &HEE
2440 DATA &HA0
2450 DATA &HE8
2460 DATA &H88
2470 DATA &HEE
2480 DATA &H0A
2490 DATA &H8E
2500 DATA &HE0
2510 DATA &HA4
2520 DATA &HA4
2530 DATA &H04
2540 DATA &H80
2550 DATA &H08
2560 DATA &H0E
2570 DATA &H0A
2580 DATA &H0A
2590 DATA &H80
2600 DATA &H0E
2610 DATA &H0A
2620 DATA &H0E
2630 DATA &H08
2640 DATA &H0E
2650 DATA &H80
2660 DATA &H04
2670 DATA &H0E
2680 DATA &H04
2690 DATA &H04
2700 DATA &H04
2710 DATA &H80
2720 DATA &H04
2730 DATA &H0E
2740 DATA &H04
2750 DATA &H04
2760 DATA &H04
2770 DATA &H00
2780 ' Object #10 : State FF : Graphic  
2790 ' GfxChallise:
2800 DATA &H81
2810 DATA &H81
2820 DATA &HC3
2830 DATA &H7E
2840 DATA &H7E
2850 DATA &H3C
2860 DATA &H18
2870 DATA &H18
2880 DATA &H7E
2890 DATA &H00
2900 ' Object #11 : State FF : Graphic  
2910 ' GfxMagnet:
2920 DATA &H3C
2930 DATA &H7E
2940 DATA &HE7
2950 DATA &HC3
2960 DATA &HC3
2970 DATA &HC3
2980 DATA &HC3
2990 DATA &HC3
3000 DATA &H00
3010 ' Object #1 States 940FF (Graphic)
3020 ' GfxPort01:
3030 DATA &HFE
3040 DATA &HAA
3050 'GfxPort02:
3060 DATA &HFE
3070 DATA &HAA
3080 'GfxPort03:
3090 DATA &HFE
3100 DATA &HAA
3110 'GfxPort04:
3120 DATA &HFE
3130 DATA &HAA
3140 'GfxPort05:
3150 DATA &HFE
3160 DATA &HAA
3170 'GfxPort06:
3180 DATA &HFE
3190 DATA &HAA
3200 'GfxPort07:
3210 DATA &HFE
3220 DATA &HAA
3230 'GfxPort08:
3240 DATA &HFE
3250 DATA &HAA
3260 'GfxPort09:
3270 DATA &H00
3280 DATA -1

It ain’t pretty, but it’s mine!

I suppose next I should get back to looking at how the rooms are connected. And there’s some interesting stuff in here, including rooms that “change” as the game is played.

Stay tuned…

Benchmarking the CoCo keyboard – part 6

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

Let’s jump right in with a mystery… This code:

0 REM arrowbench.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IF PEEK(&H155)=&HF7 THEN IF Y>. THEN Y=Y-1
40 IF PEEK(&H156)=&HF7 THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(&H157)=&HF7 THEN IF X>. THEN X=X-1
60 IF PEEK(&H158)=&HF7 THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…reports 2139.

This code, using variables instead of five constants…

0 REM arrowbench2.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…reports 2890 – slower.

My previous BASIC benchmarks have shown that looking up variables should be faster than parsing HEX values, at least when there are a small amount of variables to parse through.

But in this case, it is slower.

And this code, which just adds some GOTOs to skip additional checking when a key is matched (it will not support diagonals):

0 REM arrowbench8.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IF PEEK(&H155)=&HF7 THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(&H156)=&HF7 THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(&H157)=&HF7 THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(&H158)=&HF7 THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…prints 2186 (a bit slower than the first, since it always has to skip the extra characters at the end of each line).

Yet this:

0 REM arrowbench9.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1:GOTO90
90 NEXT:PRINT TIMER

…reports 1431 – faster.

A quick test using the TRON command shows that the second version using variables does not always run lines 50 and 60. It is sometimes getting a value back that triggers that extra code, hitting the GOTO and skipping the other lines.

But why does this happen when using variables, but not when using hard-coded constant values? This test program seems to show they display the same values:

5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
20 PRINT HEX$(PEEK(U))" "HEX$(PEEK(D))" "HEX$(PEEK(L))" "HEX$(PEEK(R)),HEX$(PEEK(&H155))" "HEX$(PEEK(&H156))" "HEX$(PEEK(&H157))" "HEX$(PEEK(&H158))
30 GOTO 10

What am I not seeing? Where is my typo?

Please help.

UPDATE: I found my typo. And it’s a stupid one. Do you see what I did wrong?

To be continued…

CoCo MC6847 VDG chip “draw black” challenge responses.

Recently, I was annoyed to find that there did not seem to be any way to set a black pixel on the CoCo’s normal green background. I have since been schooled in the simplest way to make this work, which I will share after a long digressing ramble.

Never the Same Color Twice

The CoCo’s MC6847 VDG chip provides nine colors. Commenter Jason wrote:

“It always bothered me that the CoCo had nine colors in semi-graphics modes. The number nine should raise a red flag for anyone who is familiar with computers and the tendencies for things to be powers of two.

“It’s interesting that seven of the eight colors are from the NTSC test pattern (https://en.wikipedia.org/wiki/SMPTE_color_bars) which leads me to believe they’re all a particular frequency distance from each other. This would make the circuitry simpler.”

– Jason

I suppose it’s actually eight foregrounds colors with a black background, which matches the black border of the screen. There was even a test pattern program included in one of Radio Shack’s quick reference guide that I still have:

Color Adjustment Test Display

5 FOR X = 0 TO 63
10 FOR Y = 0 TO 31
15 C = INT(X/8+1)
20 SET(X,Y,C)
25 NEXT Y,X
30 GOTO 30

That produces the following output, showing the eight possible colors (plus the black background):

Color Adjustment Test Display, Radio Shack TRS-80 Color Computer Quick Reference Guide, page 55.

And here is the NTSC test pattern Jason referenced:

By Denelson83 – Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=1067498

This made me want to alter the program to make it render something with the matching colors in the correct order (and with the Xroar emulator set to not emulate a crappy 1980s RF modulated TV signal):

The 7 NTSC color bar colors that the CoCo produces.

The extra color that is not shown is orange. I wonder why those eight colors (plus black) were chosen? And what makes the colors used by the two PMODE high res graphics screens? I’ll have to revisit that in the future.

But I digress…

You can’t, even if you SET your mind to it

My original article was written because I noticed you couldn’t SET a black pixel on a normal CoCo text screen. Even though the manual listed nine colors, with zero being black, attempting to do SET(X,Y,0) would result in that pixel being set to the green background color instead of black — the same as SET(X,Y,1). While other colors acted as you expected…

CoCo SET command.

SET seemed to be treating color 0 (black) as 1 (green). Because reasons.

In order to SET a black pixel on the normal text screen, extra code would be needed.

Ciaran Anscomb

Xroar emulator author Ciaran Anscomb was the first to respond with his GOSUB routine to achieve the desired effects:

I mean I think you’re making it harder by asking for it to work with
plain CLS and not CLS1, but in that case:

100 IFPEEK(1024+INT(Y/2)*32+INT(X/2))<128THENPOKE1024+INT(Y/2)*32+INT(X/2),143
110 RESET(X,Y):RETURN

– Ciaran Anscomb

His method would PEEK the character value at the 2×2 block that was being SET and, if that value was less than 128, it would change it to 143 and then use RESET… And it works:

10 CLS
20 FOR A=0 TO 31
30 X=A:Y=A:GOSUB 100
40 NEXT
50 GOTO 50
99 ' Ciaran Anscomb
100 IFPEEK(1024+INT(Y/2)*32+INT(X/2))<128THENPOKE1024+INT(Y/2)*32+INT(X/2),143
110 RESET(X,Y):RETURN

Jim Gerrie

BASIC programmer extroidinaire Jim Gerrie provided his take on this routine:

100 SET(X,Y,1):SET(X-(X/2-INT(X/2)=.),Y,1):SET(X-(X/2-INT(X/2)=.),Y-(Y/2-INT(Y/2)=.),1):SET(X,Y-(Y/2-INT(Y/2)=.),1)
101 RESET(X,Y):RETURN

– Jim Gerrie

His version sets some pixels to color 1, and some to color 0 (using the “.” shortcut), based on some math with X and Y and divisions and integer conversions and … well, stuff I don’t grasp.

His also works! But as it draws, you can see it blipping surrounding pixels in the 2×2 block on then off. And while it passes the test case which drew a diagonal line, it doesn’t allow for setting arbitrary pixels near each other. They turn into full blocks.

However, he also added a second attempt:

I know that my first suggestion above is a bit of a cheat. Here’s a more robust suggestion:

10 CLS
20 FOR A=0 TO 31
30 X=A:Y=A:GOSUB 100
40 NEXT
50 GOTO 50
100 IFPOINT(X,Y)<.THENXX=(X/2-INT(X/2)=.)-(X/2-INT(X/2)>.):YY=(Y/2-INT(Y/2)=.)-(Y/2-INT(Y/2)>.):SET(X,Y,1):SET(X-XX,Y,1):SET(X-XX,Y-YY,1):SET(X,Y-YY,1)
101 RESET(X,Y):RETURN

– Jim Gerrie

This version passes the test as well, and looks like it better handles setting pixels at any position without impacting pixels around it.

What’s the POINT?

Ciaran made use of PEEK to detect what was on the screen before adding something new, and Jim figured out what pixels to set back to the background color. Neither did it the way I was expecting — using POINT:

POINT (X,Y) Tests whether specified graphics cell is on or off, x (horizontal) = 0-63; y (vertical) = 0-31. The value returned is -1 if the cell is in a text character mode; 0 if it is off, or the color code If it is on, See CLS for color codes.

IF POINT(10,10) THEN PRINT "ON" ELSE PRINT "OFF"

I expected I’d see folks use this to see if a pixel was set, and handle accordingly. Somehow. But as I read this description (from the Quick Reference Guide), I see that note that says “The value returned is -1 if the cell is in a text character mode.”

Text character mode? It’s just the background, isn’t it?

All green backgrounds are not the same

And that takes me back to Ciaran’s code:

IF PEEK(1024+INT(Y/2)*32+INT(X/2))<128 . . .

Less than 128 is a text character. The graphics blocks (2×2) start as 128. If the square is a text character then set it to 143. So what is that? That is a 2×2 graphics block that has all pixels set to the green color. And that green color is the same color as the background screen. Which isn’t 143 when you use CLS. Try this:

CLS:PRINT PEEK(1024)

If you clear the screen then PEEK to see what value is at the top left character (1024), it returns 96. 96 is the space character (yeah, ASCII is 32, but values in screen memory aren’t ASCII).

Ciaran’s code sees if it’s anything (including that green space), set it to 143, which is a green block that looks the same. Try this:

CLS 1:PRINT PEEK(1024)

That will print 143. Yet, visually, CLS and CLS 1 look the same. But, CLS is filling the screen with the space text character (96) and CLS 1 fills it with the green graphics character (143)! CLS 0-8 fill the screen with solid graphics characters, and CLS with no parameter is the space.

Now, I knew about character 143 looking like the normal space but not being one, because we used to use this as a cheap “copy protection” method. On DISK, you could save out a file like this:

SAVE "HELLO.BAS"

…and you’d get a file on BASIC called HELLO.BAS. But, if you did this:

SAVE "HELLO"+CHR$(143)+".BAS"

…Disk BASIC would write out a file called HELLO(char 143).BAS. When you did a DIR they would look the same, but you couldn’t do a LOAD”HELLO.BAS” to get the one with the CHR$(143) in it. Unless you exempted the disk directory bytes you would not know there was an “invisible” character at the end of the “HELLO” filename.

Sneaky. And I did this with some of my own programs.

But years later, when the CoCo 3 came out, it’s 40 and 80 column screen did NOT support the 2×2 graphics block characters, and this trick was no longer as sneaky since you would see “HELLO(some funky character).BAS” in the directory listing and know something weird had been done.

But I digress, again…

Why do it the hard way, anyway?

It turns out, even though I knew about the “add CHR$(143)” trick, I had forgotten (or never knew/realized) that CLS and CLS 1 filled the screen with different characters. And, if the screen has a graphics character at the position, RESET will then work to change that pixel back to black.

Ciaran got me exploring this because in his e-mail he added:

If you allow CLS1, the problem solves itself :)

– Ciaran Anscomb

I had to follow up and ask what he meant by this. And, well, nevermrind, then. All I needed to do was start the program with CLS 1 instead of CLS 0, and then I could use RESET() to set individual black pixels on the screen.

I could have a subroutine that expect X, Y and C (for color) and if the color was 0 (black), do a RESET, else do a SET:

0 REM RESET
10 CLS 1
20 FOR A=0 TO 31
30 X=A:Y=A:C=0:GOSUB 100
40 NEXT
50 GOTO 50
100 IF C=0 THEN RESET(X,Y) ELSE SET(X,Y,C)
110 RETURN

And that works fine on any CLS 0-8 screen. Remember, SET(X,Y,0) never gives you a black pixel. 0 seems to mean “background color” instead, while RESET(X,Y) seems to mean “set to black”.

To me, this is a bit counterintuitive, since today I would expect “reset” to mean “set to background color” but this isn’t a graphics mode — it’s just graphics characters on a screen, so the only way BASIC could have done this is if it remembered what the CLS # value was, and made RESET set to that color. Which would be extra ROM space for a simple enhancement that most could work around with RESET instead.

And that, my friends, is how a rabbit hole works.

Until next time…

Exploring Atari VCS/2600 Adventure – part 3

See also: part 1, part 2, part 3, part 4 … and more to come…

Defining the invisible

When we last left off, I was trying to figure out what all the bits did in the room definition attribute byte:

;Offset 4 : Bits 5-0 : Playfield Control                                                                           
;            Bit 6 : True if right thin wall wanted.                                                               
;            Bit 7 : True if left thin wall wanted.  

In the disassembly I was looking at (created in 2006 or earlier), it did not go in to details about what “Playfield Control” was for. By some trial, I was about to work out which bits represented the right half of a room to be drawn Mirrored or Reversed, as well as the bits that defined drawing a thing left or right wall line:

Bit 0 - Right half of screen is Reversed.
Bit 1 - ?
Bit 2 - ?
Bit 3 - Right half of screen Mirrored.
Bit 4 - ?
Bit 5 - ?
Bit 6 - Thin right wall.
Bit 7 - Thin left wall.

There were a few bits left over, and I knew the game had rooms that where “invisible” mazes where you only saw the portion of the maze directly around the player:

Atari Adventure “invisible” maze.

This screenshot is from game variation 2 and 3, and it is below the room to the left of the easter egg room (or, from the yellow castle, down, right, then down). By roaming around the room, and trying to match up its shape with the source code, I believe it is this location:

MazeEntry:
  .byte $F0,$FF,$0F ;XXXXXXXXXXXXXXXX        RRRRRRRRRRRRRRRR
  .byte $00,$30,$00 ;      XX                        RR
  .byte $F0,$30,$FF ;XXXX  XX    XXXXXXXXRRRRRRRRR   RR  RRRR
  .byte $00,$30,$C0 ;      XX          XXRR          RR
  .byte $F0,$F3,$C0 ;XXXXXXXX  XX      XXRR      RR  RRRRRRRR
  .byte $00,$03,$C0 ;          XX      XXRR      RR
  .byte $F0,$FF,$CC ;XXXXXXXXXXXX  XX  XXRR  RR  RRRRRRRRRRRR

By looking at the room definition data for an entry that uses these graphics, I find room 10:

LFE75: .byte <MazeEntry,>MazeEntry,$08,$08,$25,$03,$09,$09,$09

Its attributes are $25, which is the bit pattern 00101001. Bit 5 is being used, and it wasn’t in my earlier room examples, so I believe that is for “invisible”:

Bit 0 - Right half of screen is Reversed.
Bit 1 - ?
Bit 2 - ?
Bit 3 - Right half of screen Mirrored.
Bit 4 - ?
Bit 5 - Invisible.
Bit 6 - Thin right wall.
Bit 7 - Thin left wall.

For all 30 rooms defined in the ROM, I only see bits 0, 3, 5, 6 and 7 ever used. (Distinct attribute values are: $21, $24, $25, $61, and $a1. Bits 0-3 can be $1=0001, $4=1000 or $5=1001, and bits 4-7 can be $2=0010, $6=0110 or $a=1010. It seems to check out, but please double check me. I make many mistakes when writing these things and could be a … bit … off.)

This gives me five types of rooms to render.

Color me bad

There is also a color value (when in Color mode) and a black and white color value (when in Black and White mode). The Atari had a switch to alter the colors the games used so they were easier to view on a black and white TV set. Later in the console’s life, not all games continued to support this switch.

For Adventure, I see various values in the ROM, but no reference to what color they generate. Some are in definitions called “Yellow Castle” or “Red Maze”, but most are not described with a color. Instead, I look at the translated translated Adventure code by Peter David Hirschberg:

https://github.com/peterhirschberg/adventure/blob/master/src/adventure.ts

Yes, translates translated. Back around 2006, he converted the Adventure assembly code to C++ and wrote wrapper code to allow it to run on a modern PC under the title “Adventure: Revisited.” More recently, he took that converted C code and converted it to TypeScript. (I had to look up just what that was. It’s a Microsoft superset of JavaScript.) Because of this, you can now play his conversion inside a web browser:

http://peterhirschberg.com/#/AdventurePlay

In his TypeScript source, he fills in some of the gaps with new comments including this nice color table:

const COLOR_BLACK=0
const COLOR_LTGRAY=1
const COLOR_WHITE=2
const COLOR_YELLOW=3
const COLOR_ORANGE=4
const COLOR_RED=5
const COLOR_PURPLE=6
const COLOR_BLUE=7
const COLOR_LTCYAN=8
const COLOR_CYAN=9
const COLOR_DKGREEN=10
const COLOR_LIMEGREEN=11
const COLOR_OLIVEGREEN=12
const COLOR_TAN=13
const COLOR_FLASH=14

But, since the original ROM code used hardware-specific values, these numbers do not map to what the assembly code used for those colors. If I wanted to parse the actual data bytes in the ROM code (rather than converting it to a modern enumerated lookup table), I’d need to know which value represented which color. Fortunately, he also updated the room definition structures to use the above labels so it’s obvious:

let roomDefs: ROOM[] = [
  { graphicsData: roomGfxNumberRoom,
    flags: ROOMFLAG_NONE,
    color: COLOR_PURPLE,
    roomUp: 0x00, roomRight: 0x00,
    roomDown: 0x00, roomLeft: 0x00 }, // 0 - Number Room

  { graphicsData: roomGfxBelowYellowCastle,
    flags: ROOMFLAG_LEFTTHINWALL,
    color: COLOR_OLIVEGREEN,
    roomUp: 0x08, roomRight: 0x02,
    roomDown: 0x80, roomLeft: 0x03 }, // 1 - Top Access

  { graphicsData: roomGfxBelowYellowCastle, flags: ROOMFLAG_NONE,
    color: COLOR_LIMEGREEN,
    roomUp: 0x11, roomRight: 0x03,
    roomDown: 0x83, roomLeft: 0x01 }, // 2 - Top Access
...

Compare those three entries with the same three in the disassembly (and note the order is a bit different):

RoomDataTable:                                                                                                                   
LFE1B: .byte <NumberRoom,>NumberRoom,$66,$0A,$21,$00,$00,$00,$00   

LFE24: .byte <BelowYellowCastle,>BelowYellowCastle,$D8,$0A,$A1,$08,$02,$80,$03                   

LFE2D: .byte <BelowYellowCastle,>BelowYellowCastle,  $C8,$0A,$21,$11,$03,$83,$01

And we can assume that $66 is PURPLE, $DB is OLIVE GREEN, and $C8 is LIME GREEN. This would let us do our own Atari VCS to Whatever color table for displaying that data.

Of course… I could have just played the game and gone to each room, looked at the color on the screen, then figured it out that way, but he already did the work and I was lazy.

And I know what some of you are thinking… I could have just looked at his source code to figure out the attributes bits. But, alas, I could not. He already converted them. He translated those down to just:

const ROOMFLAG_NONE          = 0x00
const ROOMFLAG_MIRROR        = 0x01 // bit 0 - 1 if graphics are mirrored, 0 for reversed
const ROOMFLAG_LEFTTHINWALL  = 0x02 // bit 1 - 1 for left thin wall
const ROOMFLAG_RIGHTTHINWALL = 0x04 // bit 2 - 1 for right thin wall

…and he must have figured out that Invisible rooms all use a specific color, because he handles the invisible rooms this way:

function Surround()
{
  // get the playfield data
  const currentRoom: ROOM = roomDefs[objectBall.room]
  if (currentRoom.color == COLOR_LTGRAY)
  {
    // Put it in the same room as the ball (player) and center it under the ball
    objectSurround.room = objectBall.room
    objectSurround.x = (objectBall.x-0x1E)/2
    objectSurround.y = (objectBall.y+0x18)/2
  }
  ...

He rewrote the game in a manner that makes sense, rather than trying to do a literal translation of machine-specific assembly code in to C. Not that anyone would ever try such a literal translation

It doesn’t look like he bothered to support Black and White color mode, either ;-) and neither will I.

With the graphics data, room attributes, and colors figured out, the only thing left in the room definition structure are the room exits. As I mentioned in the previous installment, those don’t all seem to be obvious.

We’ll kick that can down the road a bit, since there are a few more fun things to do before having to think again.

Until next time…

Benchmarking the CoCo keyboard – part 5

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

By now, many of you have realized that I have no idea what I am doing. It’s through great comments that this series is evolving into something hopefully useful. For example, MC-10 programmer extraordinaire Jim Gerrie left this comment:

20 CLS:FORZ=.TO1STEP.IF(PEEK(344)ANDPEEK(343)ANDPEEK(342)ANDPEEK(341))<>255THENK=PEEK(135):PRINT@32*Y+X,” “;:X=X+(X>.ANDK=8)-(X<31ANDK=9):Y=Y+(Y>.ANDK=94)-(Y<14ANDK=10):NEXT ELSENEXT:END

I think the POKES are not needed for Coco3 or anything below BASIC 1.2 on the Coco 2.

Jim Gerrie

This reminded me of a cryptic code sample he posted earlier this year on Facebook:

1 PCLEAR4:SCREEN1,1:PCLS
7 POKE341,255:POKE342,255:POKE343,255:POKE344,255:IFNOT((PEEK(341)ANDPEEK(342)ANDPEEK(343)ANDPEEK(344))=255)THENK=PEEK(135):X=X+(K=8ANDX>.)-(K=9ANDX<255):Y=Y+(K=94ANDY>.)-(K=10ANDY<191)
8 PSET(X,Y):GOTO7

You can see the keyboard KEYBUF memory locations (341-344) being used, as well as the “last key pressed” location (135). Typing in the above code snippet produces a black PMODE 4 graphics screen with a dot you can move around using the arrow keys.

My head spins just trying to figure out the logic of the use of NOT, AND and logical comparisons (a > b). The end result is an all-in-one routine that adds or subtracts values to X and Y coordinates. Clever.

Since this code is basically reading a key that is being held down, it only gets back one value (such as Up, Down, Left or Right). It does not support diagonal movement.

Here’s my much longer version that will support diagonals:

0 REM ahkeybd.bas
10 PCLEAR4:SCREEN1,1:PCLS:X=0:Y=0
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IF PEEK(&H155)=&HF7 THEN IF Y>. THEN Y=Y-1
40 IF PEEK(&H156)=&HF7 THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(&H157)=&HF7 THEN IF X>. THEN X=X-1
60 IF PEEK(&H158)=&HF7 THEN IF X<&HFE THEN X=X+1
80 IF PEEK(&H159)=&HF7 THEN PRESET(X,Y) ELSE PSET(X,Y)
90 GOTO 20

Also, SPACE can be used to erase. But it is still really slow.

Make go faster

Let’s benchmark my version, which is already sped up by using hex constants. I’ll take out the part that deals with the SPACE bar, and remove unneeded spaces.

0 REM arrowbench.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IFPEEK(&H155)=&HF7 THENIFY>.THENY=Y-1
40 IFPEEK(&H156)=&HF7 THENIFY<&HDF THENY=Y+1
50 IFPEEK(&H157)=&HF7 THENIFX>.THENX=X-1
60 IFPEEK(&H158)=&HF7 THENIFX<&HFE THENX=X+1
90 NEXT:PRINT TIMER

This produces 2139.

I should be able to speed it up further by replacing the PEEK values with variables:

0 REM arrowbench2.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IFPEEK(U)=V THENIFY>.THENY=Y-1
40 IFPEEK(D)=V THENIFY<&HDF THENY=Y+1
50 IFPEEK(L)=V THENIFX>.THENX=X-1
60 IFPEEK(R)=V THENIFX<&HFE THENX=X+1
90 NEXT:PRINT TIMER

This actually slows down to 2864. I did not expect that. Okay, no variables for my version, then.

Now let’s benchmark Jim’s much smaller (and far more clever) routine:

0 REM arrowbench3.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE341,255:POKE342,255:POKE343,255:POKE344,255:IFNOT((PEEK(341)ANDPEEK(342)ANDPEEK(343)ANDPEEK(344))=255)THENK=PEEK(135):X=X+(K=8ANDX>.)-(K=9ANDX<255):Y=Y+(K=94ANDY>.)-(K=10ANDY<191)
90 NEXT:PRINT TIMER

This prints 3609. So far, it looks like mine is faster. But let’s do the same optimizations to Jim’s code.

Let’s convert the decimal constants into hex:

0 REM arrowbench4.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF:IFNOT((PEEK(&H155)ANDPEEK(&H156)ANDPEEK(&H157)ANDPEEK(&H158))=&HFF)THENK=PEEK(&H87):X=X+(K=&H8 ANDX>.)-(K=&H9 ANDX<&HFF):Y=Y+(K=&H5E ANDY>.)-(K=&HA ANDY<&HBF)
90 NEXT:PRINT TIMER

This lowers it to 2120! That’s slightly faster than my version. I did not expect that.

Even though using variables was slower in my version, let’s see what happens with Jim’s:

0 REM arrowbench5.bas
5 V=&HFF:U=&H155:D=&H156:L=&H157:R=&H158:P=&H87
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V:IFNOT((PEEK(U)ANDPEEK(D)ANDPEEK(L)ANDPEEK(R))=V)THENK=PEEK(P):X=X+(K=&H8 ANDX>.)-(K=&H9 ANDX<&HFF):Y=Y+(K=&H5E ANDY>.)-(K=&HA ANDY<&HBF)
90 NEXT:PRINT TIMER

To my surprise, this drops it even further to 1715. It is now twice as fast as the original version that used decimal constants!

But why did mine get slower when I swapped out the same variables? I *speculate* that the processing of things like AND and NOT and comparisons may be alot faster on variables than whatever it has to do when encountering constants, but that’s a benchmark digression for another time.

To test that theory, let me change the POKEs back to hex and see if that slows down or speeds up. We’ll just use the variables in the AND/NOT stuff.

0 REM arrowbench6.bas
5 V=&HFF:U=&H155:D=&H156:L=&H157:R=&H158:P=&H87
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF:IFNOT((PEEK(U)ANDPEEK(D)ANDPEEK(L)ANDPEEK(R))=V)THENK=PEEK(P):X=X+(K=&H8 ANDX>.)-(K=&H9 ANDX<&HFF):Y=Y+(K=&H5E ANDY>.)-(K=&HA ANDY<&HBF)
90 NEXT:PRINT TIMER

Nope. This slows down to 1947. For whatever reason, the variables sped up Jim’s, but slowed down mine.

I suppose we could try changing the remaining hex constants to variables and see what that did, but for now, I’ll just say:

Nicely done, Jim!

Can we find a way to do this but also support diagonals?

To be continued…

Kudos to Saleae’s logic analyzer and excellent support

Just a quick shoutout to the team at Saleae. They make some excellent low-cost logic analyzers that I first heard about via a coworker at a former job. When I started my current position, I was introduced to them formally since we had a few we used to analyzed I2C communication.

I’ve used their Logic hardware/software many times over the past year, but only recently started trying their new Alpha release. I was blown away with the new capabilities it gave the device, such as a “real time” display of traffic as it was captured on the wire, as well as being able to trigger on specific data. Very useful!

When one of our units started misbehaving, their tech support quickly diagnosed it as a hardware problem and had a replacement unit shipped out and in our hands a few days later — excellent post-sale service!

And most recently, their support let me know the software didn’t support a display mode I was wanting, but pointed me to their extension support so I could achieve what I needed to do. Even though I didn’t know Python (the language their extensions use), I was able to take their template code and quickly modify it for my basic needs.

That, of course, led me down the rabbit hole and tonight I fleshed out my extension so it not only shows the I2C address like I wanted, but identifies the various components of our internal I2C communication protocol. Spiffy!

Saleae logic analyzer software with a custom extension I put together in a day.

I am very impressed with their hardware, software, and support. And, they offer special pricing for students and enthusiasts. I see a Logic unit of my very own in the not-to-distant future.

Thanks, Saleae, for a great product and great support.

(And one day maybe I’ll actually learn how to spell Saleae correctly.)

CoCo MC6847 VDG chip “draw black” challenge

The 1980 Radio Shack Color Computer (and the Color Computer 2 revisions) used a Motorola MC6847 Video Display Generator chip. This video chip was used in a variety of other systems, and one can easily recognize it by its 32×16 text mode characters with the square letter “O”. I recently spotted it in a YouTube video by Atari Archives discussing the Atari VCS Hangman cartridge (see 5:04 if this direct link doesn’t work):

I was unfamiliar with the AFP Imagination Machine mentioned in the video, but CoCo Crew co-host John Linville confirmed it indeed used the same CoCo VDG chip. The “nuclear green” background color and blocky low-resolution “semigraphics” do stand out.

I also stumbled upon it in a list of unreleased Atari products. A company named Unitronics was planning an Atari VCS Expander System that would allow loading games from a built-in tape deck. In a screen shot for the device (which looked like a cassette player that plugged into the top of the Atari), the nuclear green CoCo screen can be clearly seen:

http://www.ataricompendium.com/game_library/unreleased/unreleased.html#unitronics

The screenshot in the first picture appears similar to that of Radio Shacks’ Color Computer – same color scheme and maximum of 32 characters per line.

http://www.ataricompendium.com/game_library/unreleased/unreleased.html

And, there was even going to be a conversion of the 1980 CoCo ROM-Pak Space Assault and a screen shot was used of the CoCo version

http://www.ataricompendium.com/game_library/unreleased/unreleased.html#spaceassault

The screenshot shown in a brochure for the Expander System (picture #1) is actually the Radio Shack Color Computer game Space Assault (picture #2).  The game was licensed to Tandy by Image Producers Inc. in 1981.  Perhaps Unitronics was going to license the game for the Expander.  If the game shown in the brochure is actually running on the Expander, that would mean the Expander used the same graphics chip as the CoCo – the MC6847 VDG chip.

http://www.ataricompendium.com/game_library/unreleased/unreleased.html

Until recently, I had no idea anything but the Radio Shack CoCo and MC-10s, as well as the UK’s Dragon computers (and, I guess, the French MC-10 clone, Alice) used the VDG chip.

64x32x8 … technically.

Ignoring using solid color character blocks in the 32×16 text mode, the lowest resolution graphics mode in the VDG was a 64×32 mode that could use 8 colors plus black. It did this by dividing each text block of the 32×16 text screen into a 2×2 graphics character. They weren’t true pixels, but instead where just characters made with all possible 2×2 variations in eight different colors. You could PRINT them using CHR$:

This reminds me of how the Commodore VIC-20 worked with its extended PETSCII character set, but instead of all the weird lines and shapes and card suit symbols, it was 2×2 graphics blocks with different colors. Much like the standard VIC-20 text mode, each text position could only contain one color plus black. Thus, while you could have eight colors on the screen, you could never have more than one color (plus black) in a character’s 2×2 block.

Rendering graphics in this mode was tricky, since you could not have more than a single color (plus black) in any 2×2 block.

In BASIC, you could set the top left pixel to yellow using:

SET(0,0,2)

…but you would see it would change all the other pixels in the 2×2 block to black:

CoCo SET command.

And if you tried to set two pixels side-by-side to different colors:

SET(0,0,2):SET(1,0,5)

…it would turn any other set pixel in that 2×2 block to the new color:

CoCo SET command. All pixels in a 2×2 block must be the same color. So there.

I am sure I learned this limitation when I first started playing with a CoCo in Radio Shack back in 1982.

Because of this “all pixels must be the same color” effect of the SET command, doing a random pixel plotting program…

0 REM 64x32.bas
10 POKE 65495,0:CLS 0
20 SET(RND(64)-1,RND(32)-1,RND(8)-1)
30 GOTO 20

…starts out as you might expect…

CoCo Random SET.

…but after awhile, every pixel has been set so new sets will change everything in that 2×2 block to the same color:

Random SET(x,y,c) after some time…

I know I wrote this program at that Radio Shack ;-)

Black is not a color

The early “Getting Started With Color BASIC” manual that came with the CoCo described the SET() command as being able to use the following colors:

  • 0 – black
  • 1 – green
  • 2 – yellow
  • 3 – blue
  • 4 – red
  • 5 – buff
  • 6 – cyan
  • 7 – magenta
  • 8 – orange

But that manual was a bit incorrect. While you can try to SET(x,y,0), you won’t get black. The SET() command treats 0 and 1 as the same green color.

In fact, from what I can see, there is no way to set just a black pixel on the green text screen other than setting all the other pixels in that 2×2 block to the background screen green (color 1).

0 REM setblack.bas
10 CLS
20 SET(1,0,1):SET(0,1,1):SET(1,1,1)
30 GOTO 30

I guess the SET() command was really designed to work on a black screen (CLS 0). In fact, when viewing the Color BASIC disassembly in the “Color BASIC Unravelled” book, I even see it checks for this specifically:

CHANGE COLOR NUMBERS FROM 0-8 TO (-1 TO 7)
BRANCH IF SET(X,Y,0)

It looks like they could have allowed it to support this, based on how the code checks what’s in the character initially. Maybe it was an oversight, or maybe it was just a lack of ROM space.

Regardless of the reason … I recently wanted to draw a black pixel on the green screen, and found doing so quite challenging.

The “draw black” challenge

Given a clear txt screen (CLS without any color), create a BASIC subroutine starting at line 100 that will plot a single black pixel using variable X and Y. Basically, make it act as if SET(X,Y,0) would actually set a black pixel like the BASIC manual implied.

It will be called like this:

10 CLS
20 FOR A=0 TO 31
30 X=A:Y=A:GOSUB 100
40 NEXT
50 GOTO 50

That above code would draw a diagonal black line from the top left of the screen down to the middle bottom of the screen.

To erase a pixel, we’d just use SET(X,Y,1) to place a green pixel there.

Is there a clever way to do this? Leave your efforts in the comments, or send them to me via e-mail.

Have fun!

Exploring Atari VCS/2600 Adventure – part 2

See also: part 1, part 2, part 3, part 4 … and more to come…

How the rooms are defined

In the previous installment, I introduced how the playfields were encoded in the Atari Adventure game. I had converted the assembly data into C code and made a command-line program that would print out the room graphics.

Atari Adventure screen graphics decoded in C.

I then recreated the process in Microsoft Color BASIC on a Radio Shack Color Computer emulator.

My command-line C program was displaying on an 80-column Windows command prompt window (or Mac OS X terminal) so it had plenty of room to render the 40 pixel wide playfields. The CoCo’s 32-column screen could not, so I changed the BASIC version to use low-resolution (64×32) text-mode graphics. This also let me use colors, though the CoCo only had 8 foreground colors to work with in this mode, with some restrictions from the Motorola MC6847 video display chip.

Atari Adventure screen graphics plotted in Color BASIC.

The end result was a proof-of-concept showing I was decoding the cartridge data properly, even if I couldn’t render it exactly as the Atari VCS/2600 would have.

There is much more I need to explore. For instance, each room has a definition data structure that describes things like which graphics data to use, how it is displayed (right half of the screen reversed vs mirrored, thin line on the left or white wall), its color, as well as which rooms are connected to it (Up, Left, Down and Right). Here is an example of the yellow castle data structure bytes:

LFEB4: .byte <CastleDef,>CastleDef,$1A,$0A,$21,$06,$03,$02,$01

The first entry (CastleDef) is a two byte pointer to the graphics data elsewhere in the ROM:

;Castle Definition                                                                                                 
CastleDef:
  .byte $F0,$FE,$15 ;XXXXXXXXXXX X X X      R R R RRRRRRRRRRR                                      
  .byte $30,$03,$1F ;XX        XXXXXXX      RRRRRRR        RR                                      
  .byte $30,$03,$FF ;XX        XXXXXXXXXXRRRRRRRRRR        RR                                      
  .byte $30,$00,$FF ;XX          XXXXXXXXRRRRRRRR          RR                                      
  .byte $30,$00,$3F ;XX          XXXXXX    RRRRRR          RR                                      
  .byte $30,$00,$00 ;XX                                    RR                                      
  .byte $F0,$FF,$0F ;XXXXXXXXXXXXXX            RRRRRRRRRRRRRR   

Note that the comments above are misleading. The graphics data only describes the left side of the images (the “X” characters in the comment). The right is created as the room is displayed, based on a bit in the fourth byte of the data. Here are what all the bytes mean:

;Room Data
;Offset 0 : Low byte room graphics data.
;Offset 1 : High byte room graphics data
;Offset 2 : Color
;Offset 3 : B&W Color
;Offset 4 : Bits 5-0 : Playfield Control
;            Bit 6 : True if right thin wall wanted.
;            Bit 7 : True if left thin wall wanted.
;Offset 5 : Room Above
;Offset 6 : Room Left
;Offset 7 : Room Down
;Offset 8 : Room Right

Looking at that data again, we can describe is as:

  • CastleDef – 2 byte pointer to graphics data.
  • $1A – Color.
  • $0A – B&W Color (the color to use with the Atari color switch is set to Black and White).
  • $21 – Attributes for how to display the room.
  • $06 – Room above (up).
  • $03 – Room left.
  • $02 – Room down.
  • $01 – Room right.

This early disassembly did not specifically describe what offset 4’s bits 5-0 mean, but one of them makes the room mirror (both sides look the same) versus the default of reversed. (Odd description. To me, a mirror reverses an image. It’s more like Duplicate versus Mirror in my mind. But I digress…)

The castle is a standard Reversed room:

XXXXXXXXXXX X X X      R R R RRRRRRRRRRR                                      
XX        XXXXXXX      RRRRRRR        RR                                      
XX        XXXXXXXXXXRRRRRRRRRR        RR                                      
XX          XXXXXXXXRRRRRRRR          RR                                      
XX          XXXXXX    RRRRRR          RR                                      
XX                                    RR                                      
XXXXXXXXXXXXXX            RRRRRRRRRRRRRR  

It’s attributes of $21 are the bit pattern 00100001.

One of the black castle mazes is Mirrored. Here is the room that contains the secret “dot” which is used to access the hidden easter egg room:

;Black Maze #3
BlackMaze3:
  .byte $F0,$F0,$FF ;XXXXXXXX    XXXXXXXXMMMMMMMM    MMMMMMMM
  .byte $30,$00,$00 ;XX                  MM
  .byte $30,$3F,$FF ;XX    XXXXXXXXXXXXXXMM    MMMMMMMMMMMMMM
  .byte $00,$30,$00 ;      XX                  MM
  .byte $F0,$F0,$FF ;XXXXXXXX    XXXXXXXXMMMMMMMM    MMMMMMMM
  .byte $30,$00,$03 ;XX          XX      MM          MM
  .byte $F0,$F0,$FF ;XXXXXXXX    XXXXXXXXMMMMMMMM    MMMMMMMM

Note how that “box” is made up by the Mirroring of the left half. Neat! That room is defined as:

LFED8: .byte <BlackMaze3,>BlackMaze3,$08,$08,$24,$13,$16,$13,$14

Its attribute of $24 is the bit pattern of 00101000.

And in the game, the room below and to the right of the castle has a thin right wall:

Atari Adventure room with a thin right wall.

If I understand which room this it, this is the data that draws it:

;Left of Name Room 
LeftOfName:
  .byte $F0,$FF,$FF ;XXXXXXXXXXXXXXXXXXXXRRRRRRRRRRRRRRRRRRRRRRRR                                  
  .byte $00,$00,$00
  .byte $00,$00,$00
  .byte $00,$00,$00
  .byte $00,$00,$00
  .byte $00,$00,$00

;Below Yellow Castle
BelowYellowCastle:
  .byte $F0,$FF,$0F ;XXXXXXXXXXXXXXXX        RRRRRRRRRRRRRRRRRRRR   **Line Shared With Above Room ----^ 

Note a clever technique programmer Warren Robinett used to save three bytes of ROM space. The room definition points to the “LeftOfName” data and a room is 21 bytes. The bottom of that room (a wall with an opening in the middle) is the same as the top of another room, so the definition uses three bytes for the next room data for the last three bytes of the first room. Clever!

The definition of the “Left Of Name” room (because the hidden easter egg room with Mr. Robinett’s name is to the right of this room) is:

LFE36: .byte <LeftOfName,>LeftOfName,$E8,$0A,$61,$06,$01,$86,$02

Its attribute of $61 is the bit pattern 01100001.

And down and to the left of the castle is a room with a thin left wall:

Atari Adventure room with a thin left wall.

Grrr. That Yorgel the yellow dragon ate me while I was trying to take this screen shot.

If I am reading things correctly, I believe this uses the same graphics data as the room below the yellow castle (opening at top, wall at bottom, no walls on left or right):

LFE24: .byte <BelowYellowCastle,>BelowYellowCastle,$D8,$0A,$A1,$08,$02,$80,$03

And its attributes of $A1 is the bit pattern 11000001.

So we have:

  • 00100001 – Reversed (castle).
  • 00101000 – Mirrored (maze).
  • 01100001 – Reversed and thin right wall.
  • 10100001 – Reversed and thin left wall.

Thus, looking back at the bit definitions:

;Offset 4 : Bits 5-0 : Playfield Control
;            Bit 6 : True if right thin wall wanted.
;            Bit 7 : True if left thin wall wanted.

It looks like we have:

  • Bit 0 – Right half of screen is Reversed.
  • Bit 1 – ?
  • Bit 2 – ?
  • Bit 3 – Right half of screen Mirrored.
  • Bit 4 – ?
  • Bit 5 – ?
  • Bit 6 – Thin right wall.
  • Bit 7 – Thin left wall.

It will take some more code exploring to see what bits 1, 2, 4 and 4 are used for, but understanding what controls Reverse and Mirrored was needed to properly draw the screens. One of those bits is probably used for rendering the “invisible” mazes, but I haven’t gotten to those yet.

Query: What happens if both bit 0 (Reverse) and bit 3 (Mirrored) are on? Why would those be separate bits? Perhaps there was an efficiency reason for checking for a set bit (less instructions than checking for a clear bit?) or perhaps bit 3 is something else. I guess I need to do more checking in to this, too.

The last bit of data (I’m assuming you can figure out “Color” and “B&W Color” entries) is the bytes that show which room is up, left, down or right. This is used by the game engine so it knows which room to display when the player moves off the current screen.

I’ll discuss that in a future installment. It’s not as straightforward as it seems. Here’s a quick teaser:

The “LeftOfName” room (solid walls with an opening at the bottom) is defined as:

LFE36: .byte <LeftOfName,>LeftOfName,$E8,$0A,$61,$06,$01,$86,$02

The room exists are defined as up ($06), left ($01), down ($86) and right ($02). But there are only 30 rooms ($00 to $1e) so there can be no room $86 (134). Maybe it actually means $06 with the high bit set (100000110). But room $06 is commented as “Bottom of Blue Maze” and looks like this:

BlueMazeBottom:
XXXXXXXX  XX  XX        RR  RR  RRRRRRRR
XX    XX      XX        RR      RR    RR
XXXX  XXXXXXXXXX        RRRRRRRRRR  RRRR
XXXX                                RRRR
XXXXXXXX                        RRRRRRRR
      XX                        RR                                            
XXXXXXXXXXXXXXXXXXXXRRRRRRRRRRRRRRRRRRRR 

…and that most definitely isn’t the room below the “Left of Name” room.

And, this room is different between game 1 (“Small Kingdom”) and games 2 and 3. In game 1, it’s a room with an opening at the top, and in games 2 and 3 it is part of an invisible maze.

This will have to be figured out.

Until then…

Benchmarking the CoCo keyboard – part 4

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

NOTE: I have a really great comment to share with you from a recent installment, but it deserves an article of it’s very own. Thank you, Jim, for the assembly sample. I’ll be getting to that soon…

Meanwhile, William Astle once again comments with some explanations to move this topic forward:

The KEYBUF thing is actually a lot more straight forward than it looks. It basically holds the result of reading FF00 for each keyboard column as of the last time KEYIN ran. More specifically, the last time it got to actually reading that column since it stops as soon as it finds a new key press and, so, it won’t necessarily update all eight entries in KEYBUF every time. It will when no new keys are pressed, though.

Basically, what it does is this:

Read the data from FF00
EOR it with the relevant byte in KEYBUF; this sets any bit where the value has changed since the last read; that is, a key was pressed or released. Exclusive OR yields a 1 if its two inputs are different and a 0 if the two inputs are the same.
AND the result of (2) with the KEYBUF value. This will keep only bits where the previous result was a “1” (not pressed). That ignores any keys that were released or are held down (previous state being a “0” in both of those cases).
Store the value read from FF00 into the KEYBUF byte using a copy of the original read from FF00.
If The result of (3) is nonzero, it stops reading the PIA and decodes the new keypress, does the debounce thing, etc.

KEYIN also masks off the joystick comparator input so you won’t see that in KEYBUF. Further, when reading the column with SHIFT in it, it masks that as well. So you can’t test for shift by looking in KEYBUF.

Basically, what the POKEs do is trick KEYIN into thinking that no keys in those particular columns were previously pressed by setting the bits to “1”.

As a side note, you can detect keys that are currently pressed by PEEKing KEYBUF. As long as Basic is doing its BREAK check, KEYIN is getting called at least once before every statement so KEYBUF gets updated.

William Astle

I’m glad there’s someone around here who understands this stuff. But it was his last paragraph that caught my attention. If we can just PEEK those values, INKEY$ isn’t even necessary (offering a slight speedup, perhaps).

Let’s look at what’s inside those eight KEYBUF locations. To make them fit on a 32 column screen, I will print them out as HEX values:

0 REM keyboard.bas
10 FOR A=338 TO 345:POKE A,&HFF
20 PRINT HEX$(PEEK(A))" ";
30 NEXT:PRINT:GOTO 10

Run this and you will see eight columns of hex values that represent the KEYBUF status of most of the keys. And by POKEing each value to 255 (&HFF for speed) before they are read, it allows detecting the key as long as it is being held down. You can now see the status of the arrow keys, including if you pressed two at the same time (Up+Left).

Let’s display this in bits so we can visualize which bits we care about:

0 REM keyboard2.bas
5 POKE 65495,0:CLS:FOR BT=0 TO 7:BT(BT)=INT(2^BT):NEXT
10 PRINT@0,;:FOR A=338 TO 345:POKE A,&HFF:PRINT A;:V=PEEK(A)
20 FOR BT=7 TO 0 STEP-1:IF V AND BT(BT) THEN PRINT "1"; ELSE PRINT "0";
30 NEXT:PRINT:NEXT:GOTO 10

Running this will display eight lines representing the bits in each of those memory locations. Here is what it looks like when all four arrow keys are held down at the same time:

CoCo Keyboard Matrix with all four arrow keys held down.

I see that SPACE is also in its own column, likely by design for this very purpose – games. One would want to be able to detect ARROWS and SPACE independently which would not be possible if any of them used the same column.

With this in mind, we can try to just read those keys (Up, Down, Left, Right and Space) as fast as possible.

Read those keys as fast as possible

To be nice and flexible, we should take the PEEK of one of those values and AND off the bit(s) we care about and act upon that. This allows detecting just the key we want even if something else in that column is also being held down. Since we are going for speed, we’ll just say the user isn’t allowed to do that. Press Up and that’s okay. Press Up and S (another key in the same column) and the player won’t move. Maybe we can improve that later.

Using the first keyboard.bas listing as reference, it looks like it will be easy to detect these five keys by checking the PEEK value against &HF7. As long as only the Up, Down, Left, Right or Space is held down in that column, the value will be &HF7. This gives us something like this:

0 REM arrows.bas
10 CLS:L=16+32*8
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF:D=.
30 IF PEEK(&H155)=&HF7 THEN D=-&H20
40 IF PEEK(&H156)=&HF7 THEN D=&H20
50 IF PEEK(&H157)=&HF7 THEN D=D-&H1
60 IF PEEK(&H158)=&HF7 THEN D=D+&H1
70 IF L+D<&H1FF THEN IF L+D>=. THEN L=L+D
80 IF PEEK(&H159)=&HF7 THEN PRINT@L,"O"; ELSE PRINT@L,"X";
90 GOTO 20

For speed, decimal values have been replaced with hex, and zeros have been replaced with “.” (yeah, it’s weird, but it’s faster). We could further optimize by removing spaces and combining some of the lines (and cheating by replacing the GOTO loop with a FOR/NEXT/STEP 0 hack), but for now, we’ll start with this.

When you run this program, it prints an “X” in the center of the screen. Using the arrow keys you can move the X around, leaving a trail. Hold down space, and you leave a trail of Os. There is limited checking to make sure you don’t try to print off the top or bottom of the screen.

We are now directly PEEKing the KEYBUF keyboard rollover table looking for those specific keys. We have key repeat, and the ability to detect multiple keys at the same time (such as UP+LEFT+SPACE).

Here’s what the code is doing:

  • Line 10 clears the screen and sets the PRINT@ location variable to the center of the screen.
  • Line 20 resets the four KEYBUF column values of the arrow keys. We don’t do this with the SPACE since it doesn’t seem to be needed (why not???). The movement delta value is set to 0 (this will be added to the PRINT@ location later).
  • Line 30-40 checks for UP and DOWN, and set the delta (movement) value to either -32 (moving up) or +32 (moving down).
  • Line 50-60 checks for LEFT and RIGHT, and ADD them to any existing delta value. This means UP could have set it to -32 and RIGHT could have added +1 to that, resulting in a delta of -31 (up and to the right).
  • Line 70 checks to see if the result of current location plus new delta is within the screen by being less than PRINT@511 or greater or equal to PRINT@0. If valid, it adds the delta to the location variable. Note we are using the “IF THEN IF” optimization trick that I learned about in this video from Robin @ 8-bit Show and Tell.
  • Line 80 checks for SPACE and then PRINTs an “X” or an “O” depending on if space is pressed or not.
  • Line 90 goes back and does it all again.

Here we have a very simple routine for moving something around the screen using PRINT@ coordinates (0-511). For a game, we would probably want to change this to using POKE screen locations (1024-1535) so we could PEEK to detect walls or enemies and such.

Since the title of this article series has the word “benchmark” in it, I suppose the next thing we should do it look at different ways to do this and find the ones that are faster.

To be continued…

Exploring Atari VCS/2600 Adventure – part 1

See also: part 1, part 2, part 3, part 4 … and more to come…

I have been on an Atari Adventure kick lately, which started after I played the game on a friend’s ATGames Legends Ultimate Arcade awhile back. Ignoring the weirdness of playing an Atari VCS game on something that resembles a 1980s arcade machine, it was like stepping back in time to when I lived in Mesquite, Texas (around 1980) and got to play it on a friend’s Atari.

Side note 1: I’ll be sharing the tale of growing up during the video game revolution of the 70s and 80s in a upcoming lengthy article series.

Side note 2: I also have an upcoming series about trying to code the Adventure game logic in Color BASIC on the CoCo.

Since that first exposure to Adventure on an actual Atari, I’d seen the game a few other times.

Indenture for the PC

There was the Indenture clone for PCs in the early 1990s. You can play it in a browser here:

https://archive.org/details/IndentureV1.71996CraigPellAdventure

It was a nice flashback after not seeing the game in over a decade. The author, Craig Pell, even added new levels with many more rooms. The original had 29 screens (well, 30 counting a hidden one) but Indenture has a level with 300.

Since it was a recreation, it does not accurately recreate the gameplay of the original. But, it’s still great fun.

Stella Atari emulator

Next was an encounter with an early DOS version of the Atari emulator Stella. This allowed a modern computer to play the game pretty much exactly as it was on an original Atari. (Though, without using an Atari joystick, it never felt quite real.)

https://stella-emu.github.io/

Atari Flashback 2

When I learned about the Atari Flashback 2 machine coming out, I was intrigued enough with this mini recreation of the Atari to actually buy one. Unlike the original Flashback unit, which was a Nintendo NES chipset with reprogrammed Atari games, the Flashback 2 was an actual re-engineered Atari machine. It could even be hacked to add a cartridge connector and play real Atari cartridges! And, it game with Adventure. Though I really only powered it on a handful of times before donating it to the Glenside Color Computer Club to be auctioned off at a CoCoFEST!

Atari’s Greatest Hits

In the years that followed, various software packages were released containing officially licensed Atari games running in some form of emulator. Atari’s Greatest Hits was sold for many consoles and computers. I had the edition that came out for iPhone and iPads:

Atari’s Greatest Hits on an old iPad

It was great fun to play Adventure again, but a pain to do so using virtual touch controls on a tablet screen. Fortunately, the iOS version supported the iCade controllers and I even hacked up an interface to use a “real” Atari joystick on it (the joystick from my Atari Flashback 2). Here is is on my original first generation iPad:

Teensy 2.0 as an Atari 2600 joystick interface for iOS

That, and the Flashback 2, were the closest I’ve come to the real Adventure experience, due to accurate emulation and a real (replica) controller.

Warren Robinett speaks

My recent re-interest in Adventure was enhanced after watching this 2015 presentation by the game’s original author, Warren Robinett. He details the history of how the game was designed, and some insights in to how the code worked:

Warren Robinett’s postmortem presentation on how he wrote Atari Adventure

It was this video that got me interested in howthe game worked, rather than just how to play it.

Adventure Revisited port

I was unable to find any dedicated “Everything You Want To Know About How Atari Adventure Worked” website, but I did find a 2006 version (winventure.zip) by Peter Hirschberg. In contained a disassembly of the original Atari Adventure assembly code. He also translated that assembly into C++ and wrote new code to emulate machine-specific things like collision detection and the display. I don’t know where I found the original zip file, but here is the current version:

https://sourceforge.net/projects/adventurerevisited/

Because this version was based on the actual ROM assembly code, it should play much more accurately than the Indenture rewrite from 1991. I haven’t tested this myself since I’ve been busy playing it in an Atari emulator.

Side note: I just realized this is the guy who did the Adventure game for the iPhone back in 2008!

Dissecting Adventure

Thanks to the disassembly of the original source, and the rewritten version in C++, I was able to start looking at how the game worked. The first thing I did was look at how all the game levels we represented. Each screen was represented by 21 bytes of ROM code!

;Castle Definition                                                                                                 
 CastleDef:
  .byte $F0,$FE,$15  ;XXXXXXXXXXX X X X      R R R RRRRRRRRRRR
  .byte $30,$03,$1F  ;XX        XXXXXXX      RRRRRRR        RR
  .byte $30,$03,$FF  ;XX        XXXXXXXXXXRRRRRRRRRR        RR
  .byte $30,$00,$FF  ;XX          XXXXXXXXRRRRRRRR          RR
  .byte $30,$00,$3F  ;XX          XXXXXX    RRRRRR          RR
  .byte $30,$00,$00  ;XX                                    RR
  .byte $F0,$FF,$0F  ;XXXXXXXXXXXXXX            RRRRRRRRRRRRRR

Above is the data that is used to draw the castles in the game (yellow, white and black). There was another table that defined which set of graphics data to use, as well as what attributes such as “how” to draw it (more on that in a moment), what color to draw it, and what screens were connected to it on each side (up, right, down and left).

The screen was represented by 20 bits stored as three 8-bit bytes with four unused bits. Those bits represent the left side of the screen, then they are either reversed to create the other half of a symmetrical screen, or they are mirrored (draw the left side on the right) which was used in some of the mazes). It is amazing that the entire screen was defined by only 21 bytes! (And, since there were four unused bits for each three bytes, it could have been compressed further down to 17 bytes, though the extra code needed to handle this might not have fit in to the 2K of ROM space the game used.)

Decoding the data in C

For fun, I thought I’d try to convert those data bytes in to a C program and see if I could decode and display them. Here is the castle:

Atari Adventure screen graphics decoded in C.

My first attempt at the decoder wasn’t perfect (note that it’s missing the floor of the castle room), but it showed I was on the right track.

Decoding the data in Color BASIC

I next converted the bytes into Color BASIC DATA statements, then wrote a similar program to decode them:

Atari Adventure screen graphics decoded in Color BASIC.

The CoCo 1’s 32-column screen isn’t wide enough to display 40 ASCII characters, so I was only drawing half the image as a proof-of-concept.

I next converted the PRINT text to SET(x,y,c) plotting commands. This would let me draw on the low-resolution 64×32 8-color screen.

Atari Adventure screen graphics plotted in Color BASIC.

I made a simple program that let me enter a room number and then it would plot the data on the screen. Above is a screen shot from an Atari emulator on the left, and the CoCo screen on the right. Though the aspect ration doesn’t match, at least it shows the graphics are accurate.

This 64×32 “graphics” mode is actually made up of special text characters that represent a 2×2 graphics block. Those blocks can contain one color plus a black background. Because of this limitation, a screen block can either be the green/orange background color with or without a text character on it, or a black block with 1-4 of it’s 2×2 pixels set to a single color. Because of this limitation, graphics need to be specifically designed.

Since each Adventure screen used only one color for the graphics, I thought this might work out well. But, if I wanted to change the background color, that might present a problem since unless the graphics took up a full character block, they would always have the unused pixels set to black. I did a quick test and it looked like this:

Atari Adventure screen graphics plotted in Color BASIC.

Above you can see that certain blocks of the castle do not use up a full 2×2 block, so the unused pixels are set to black. I think this gives it a rather interesting 3-D effect, though that was not the intent. Here’s one of the mazes:

Atari Adventure screen graphics plotted in Color BASIC.

I think it looks pretty cool, though not accurate to the original.

The ROM code also contains the data that makes up the objects in the game, such as the dragons. Here’s a dragon with its mouth open:

;Object 6 : State FF : Graphic
 GfxDrag1:
  .byte $80                  ;X
  .byte $40                  ; X
  .byte $26                  ;  X  XX
  .byte $1F                  ;   XXXXX
  .byte $0B                  ;    X XX
  .byte $0E                  ;    XXX
  .byte $1E                  ;   XXXX
  .byte $24                  ;  X  X
  .byte $44                  ; X   X
  .byte $8E                  ;X   XXX
  .byte $1E                  ;   XXXX
  .byte $3F                  ;  XXXXXX
  .byte $7F                  ; XXXXXXX
  .byte $7F                  ; XXXXXXX
  .byte $7F                  ; XXXXXXX
  .byte $7F                  ; XXXXXXX
  .byte $3E                  ;  XXXXX
  .byte $1C                  ;   XXX
  .byte $08                  ;    X
  .byte $F8                  ;XXXXX
  .byte $80                  ;X
  .byte $E0                  ;XXX
  .byte $00

When I get some time, my next goal is to render all of those game characters, similarly to how I displayed my old VIC-20 game’s customer character set.

To be continued…