Spiraling in Color BASIC and 6809 Assembly – part 1

See also: part 1.

It seems each 80s computer system had certain styles to programs that ran on them. There was a certain “look” to the loading screens of many Commodore 64 games, for example.

On the Radio Shack Color Computer, programs often made use of the low-resolution 64×32 8-color semigraphics to create title screens. Graphical games would often drop back to text mode between levels, presenting information on the 32×16 “nuclear green” text screen.

Some programmers would create transitions between screens, such as wiping left to right with a solid color. One of my favorite transitions was a spiral pattern, where the outside drew towards the center of the screen.

Here is an example of that type of effect, albeit done quite slowing in Color BASIC by a program I wrote for this article:

spiralbas.bas

The above video shows the spiral routine being used to spiral in the full 32×16 screen (in orange), then three more spirals done at different sizes, locations and colors, just to test the routine.

The program looks like this:

0 REM SPIRAL.BAS

10 CLS
15 ' X=START MEM LOC
20 X=1024
25 ' XS=XSTEPS (WIDTH)
30 XS=32
35 ' YS=YSTEPS (HEIGHT)
40 YS=16
45 ' B=CHAR TO POKE
50 B=255
60 GOSUB 100

70 X=1024
71 XS=18
72 YS=8
73 B=175 '143+32
74 GOSUB 100

75 X=1294 '1024+14+32*8
76 XS=18
77 YS=8
78 B=207 '143+64
79 GOSUB 100

80 X=1157 '1024+5+32*4
81 XS=22
82 YS=8
83 B=239 '143+96
84 GOSUB 100

99 GOTO 99

100 ' RIGHT
110 A=XS
120 POKE X,B
130 A=A-1
140 IF A=0 THEN 170
150 X=X+1
160 GOTO 120
170 X=X+32
180 YS=YS-1
190 IF YS=0 THEN 600

200 ' DOWN
210 A=YS
220 POKE X,B
230 A=A-1
240 IF A=0 THEN 270
250 X=X+32
260 GOTO 220
270 X=X-1
280 XS=XS-1
290 IF XS=0 THEN 600

300 ' LEFT
310 A=XS
320 POKE X,B
330 A=A-1
340 IF A=0 THEN 370
350 X=X-1
360 GOTO 320
370 X=X-32
380 YS=YS-1
390 IF YS=0 THEN 600

400 ' UP
410 A=YS
420 POKE X,B
430 A=A-1
440 IF A=0 THEN 470
450 X=X-32
460 GOTO 420
470 X=X+1
480 XS=XS-1
490 IF XS=0 THEN 600

500 GOTO 100

600 RETURN

If you wanted to try this yourself, without using a real Color Computer or even having an emulator installed on your computer, you could:

  1. Save the above BASIC code to a text file.
  2. Go to the online XRoar emulator: http://www.6809.org.uk/xroar/online/
  3. Select “Machine:” of Tandy CoCo (NTSC) (or PAL if you prefer). It will even run on a Dragon, so the default machine is fine.
  4. Click its “Load…” button then browse/select the text file you just saved.
  5. From the emulator, type “CLOAD” and the program will load as if it was loading from a cassette tape.
  6. Type “RUN” and see it in all it’s 32×16 text mode glory.

The worst code is bad code.

This program is small, and it’s written in a rather odd way. While there were some BASICs that only allowed one command per line, Microsoft Color BASIC was not one of those. You could pack lines together (which reduced code size and improved speed). You will also notice it is missing using commands like FOR/NEXT. This was intentional, since this program was written like this to match a 6809 assembly implementation that I will be sharing later in this article series.

I suppose if BASIC did not have FOR/NEXT, this would be okay:

0 REM DO THIS 10 TIMES, SLOWLY
10 A=10
20 PRINT "HELLO"
30 A=A-1
40 IF A>1 THEN 20

But this is slow because it is doing variable math (A=A-1) and a comparison (A>1) each time through. Using FOR/NEXT would be much faster:

0 ROM DO THIS 10 TIMES, FASTER
10 FOR A=1 TO 10
20 PRINT "HELLO"
30 NEXT

The RIGHT/DOWN/LEFT/UP routines could be made about three times faster by changing them to FOR/NEXT loops:

100 ' RIGHT
    110 FOR A=X TO X+XS-1
    120 POKE A,B
    160 NEXT
    170 X=A+31
    180 YS=YS-1
    190 IF YS=0 THEN 600

    200 ' DOWN
    210 FOR A=X TO X+32*(YS-1) STEP 32
    220 POKE A,B
    260 NEXT
    270 X=A-33
    280 XS=XS-1
    290 IF XS=0 THEN 600

    300 ' LEFT
    310 FOR A=X TO X-XS+1 STEP -1
    320 POKE A,B
    360 NEXT
    370 X=A-31
    380 YS=YS-1
    390 IF YS=0 THEN 600

    400 ' UP
    410 FOR A=X TO X-32*(YS-1) STEP -32
    420 POKE A,B
    460 NEXT
    470 X=A+33
    480 XS=XS-1
    490 IF XS=0 THEN 600

If I set TIMER=0 at the start of the first version, and print TIMER at the end, it prints around 973 (just over 16 seconds).

The FOR/NEXT version shows 360 (about 6 seconds). “Almost” three times faster.

spiralbas2.bas

And, by packing lines together and doing some other tricks, it could be made even faster.

So, as you can see, doing it the slow way wouldn’t make sense if this article was just about doing the spiral in BASIC.

In the next installment, I will share the 6809 assembly version, unless there are enough “here is a faster way” comments to this section that I need to share them, first.

Until next time…

IF AND/OR THEN versus IF THEN IF – part 2

See also: part 1 and part 2.

Updates:

  • 2022-08-03 – Added note about “THEN ELSE” per a comment left by William Astle.

Please see the first part to understand what this is about, and why I blame Robin at 8-Bit Show and Tell for leading me down this rabbit hole.


This week, JohnD over at the CoCo Discord chat server mentioned that the Getting Started with Extended Color BASIC manual actually used “THEN IF” in an example. Indeed, on page 190 you find this example for the POS function:

THEN IF in Getting Started with Extended Color BASIC, page 190.

Meanwhile on Twitter, FUED responded to this as follows:

I still think IF THEN X and having the second condition in another X line number with its own single IF to be the fastest for action, otherwise, the IF THEN IF will save some memory if speed is not required. If speed is a must, IFs should be avoided and ON GOTO be used instead.

– @FUED_hq on Twitter

This, of course, made me want to run some benchmarks.

Skip to the end, my darling.

When Color BASIC begins parsing a line, it has to continue parsing every byte of that line even if it determines the rest of the line does not need to be executed. This means line like this are very slow when the condition is not met:

IF A=42 THEN a really long line of other stuff here

If BASIC determines that A is not 42, it still has to scan the rest of the line just in case there is an ELSE there. Even if there is not, it still has to keep scanning to know where the line ends so it can find the next line. Color BASIC do skip line data when scanning forward (i.e., GOTO xxx) — each line entry has a line number and length of that line — but it does not remember any of this once it decides it needs to parse the current line.

In part 1 I demonstrated how using “IF this AND that THEN” could be made faster by using “IF this THEN if that THEN”:

IF A=1 AND B=2 AND C=3 THEN this is slower

IF A=1 THEN IF B=2 THEN IF C=3 THEN this is faster

This is because BASIC no longer needs to do the “logical/mathematical” AND comparisons and retain the results as it moves through the line. It can just start skipping forward looking for an ELSE or end of line.

BUT, no matter what, it still has to scan the rest of the line. As FUED points out, shorter lines could be faster. Here are two examples:

30 IF A=1 THEN IF A=2 THEN IF A=3 THEN PRINT

30 IF A=1 THEN 40 ELSE 60
40 IF A=2 THEN 50 ELSE 60
50 IF A=3 THEN PRINT
60 ...

In the first version, no matter if A is “1” or “not 1”, it still will have to scan the rest of the line.

In the second version, if A is not 1 it will scan to the end of the line then start skipping lines as it looks for line 60 without needing to scan through any of the lines it is skipping.

Depending on what is faster — scanning to the end of a longer line, versus scanning a short line and skipping lines — this may be a simple way to make things faster.

Benchmark, anyone?

Here is a simple benchmark:

10 TIMER=0
20 FOR I=1 TO 1000

30 IF A=1 THEN IF A=2 THEN IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

Running that prints 324.

Now we try a version with short lines that will just skip any lines it doesn’t need to run:

10 TIMER=0
20 FOR I=1 TO 1000

30 IF A=1 THEN 40 ELSE 60
40 IF A=2 THEN 50 ELSE 60
50 IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

Running that prints 330 – just a tad slower.

This means that the overhead of skipping those lines is just a tad more than scanning to the end of one longer line.Scanning forward looking for ELSE or end of line must take less work than looking at line number entries and skipping ahead. Bummer.

But is that why it’s a tad slower? I think the main thing is it has to convert the line numbers (“60” in this case) from those two ASCII characters to BASIC’s floating point representation of them. That is probably way more overhead than just skipping bytes looking for an ELSE token.

To test, I reversed the logic to reduce the number of numbers we have to convert:

10 TIMER=0
20 FOR I=1 TO 1000

30 IF A<>1 THEN 60
40 IF A<>2 THEN 60
50 IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

This version gives me 315 – faster than the original! And, it’s smaller code, trading an “=” and “ELSE 60” for “<>”.

This means the real thing to consider is: Which takes more time? Scanning to the end of a long line, converting bytes in to a number, or skipping lines?

This is something that could be benchmarked and then we could predict which is better to use.

But for now, I’ll just leave this here for additional comments from readers who know more about how this works than I do.

UPDATE: In a comment left by William Astle, he mentioned you could also do “IF A=1 THEN ELSE 60” as valid Syntax. This effectively creates something similar to how C might do:

if (a == 1)
{
   // Nothing to do
}
else
{
   // Something to do
}

Looking at that in BASIC, that makes sense, though I’ve never seen it done and never used it like that. So let’s add this to the mix, going back to the version using “=” and original logic:

0 'THENBENCH.BAS
10 TIMER=0
20 FOR A=1 TO 1000

30 IF A=1 THEN ELSE 60
40 IF A=2 THEN ELSE 60
50 IF A=3 THEN PRINT

60 NEXT:PRINT TIMER,TIMER/60

This gives me 315, basically matching the “<>” version without THEN. Thus, these seem comparable:

IF A<>1 THEN 60

IF A=1 THEN ELSE 60

I expect it’s just parsing a byte for the ELSE token, versus a byte for the extra character (“>”) being similar in speed. And speaking of speed, removing the spaces in those IF lines reduces it to 310, versus 307 for the “<>” version. I think this is because the “THEN ELSE” started out with four spaces versus the “<>” version only having three.

For better benchmarks, testing the code itself, all spaces should be removed, but I usually don’t do that, just for readability in these articles.

Until next time…

Color BASIC RAM hooks – part 2

See Also: part 1 and part 2.

Updates:

  • 2022-08-02 – Minor assembly optimization using “TST” to replace “PSHS B / LDB DEVNUM / PULS B”, contributed by L. Curtis Boyle in the comments.

Since I wrote part 1, I have learned a bit more about using the Color BASIC RAM hooks. One thing I learned is that the BREAK CHECK RAM hook cannot be used to disable BREAK. This is because other parts of the BASIC ROM jump directly to the break check and do not call the RAM hook. Ah, well. If I really need to disable the break key, at least I know how to do it thanks to the 500 POKES, PEEKS ‘N EXECS for the TRS-80 Color Computer book.

I did want to revisit using the CONSOLE OUT RAM hook and do something perhaps almost useful. The MC6487 VDG chip used in the Color Computer lacks true lowercase, and displays those characters as inverse uppercase letters. Starting with later model CoCo’s labeled as “TANDY” instead of “TRS-80”, a new version of the VDG was used that did include true lowercase, but by default, BASIC still showed them as inverse uppercase.

I remembered having a terminal program for my CoCo 1 that would show all text in uppercase. This made the screen easier to read when calling in to a B.B.S. running on a system that had real lowercase. I thought it might be fun to make a quick assembly program that would intercept all characters going to the screen and translate any lowercase letters to uppercase.

Let’s start by looking at the code:

* lwasm consout2.asm -fbasic -oconsout2.bas --map

* Convert any lowercase characters written to the
* screen (device #0) to uppercase.

DEVNUM equ $6f
RVEC3 equ $167      console out RAM hook

    org $3f00

init
    lda RVEC3       get op code
    sta savedrvec   save it
    ldx RVEC3+1     get address
    stx savedrvec+1 save it

    lda #$7e        op code for JMP
    sta RVEC3       store it in RAM hook
    ldx #newcode    address of new code
    stx RVEC3+1     store it in RAM hook

    rts             done

newcode
    * Do this only if DEVNUM is 0 (console)
    *pshs b          save b
    *ldb DEVNUM      get device number
    *puls b          restore b
    tst DEVNUM      is DEVNUM 0?          
    bne continue    not device #0 (console)
uppercase
    cmpa #'a        compare A to lowercase 'a'
    blt continue    if less than, goto continue
    cmpa #'z        compare A to lowercase 'z'
    bgt continue    if greater than, goto continue
    suba #32        a = a - 32
continue
savedrvec rmb 3     call regular RAM hook
    rts             just in case...

The first thing to point out are the EQUates at the start of the code. They are just labels for two locations in BASIC memory we will be using: The CONSOLE OUT RAM hook entry, and the DEVNUM device number byte. DEVNUM is used by BASIC to know what device the output is going to.

Device Numbers

Devices include:

  • -3 – used by the DLOAD command in CoCo 1/2 Extended Color BASIC
  • -2 – printer
  • -1 – casette
  • 0 – screen and keyboard
  • 1-15 – disk

The BASIC ROM will set DEVNUM to the device being used, and routines use that to know what to do with the date being written. For example:

Device 0?

Device #0 may seem unnecessary, since it is assumed if #0 is not present:

PRINT "THIS GOES TO THE SCREEN"
PRINT #0,"SO DOES THIS"

Or…

10 INPUT "NAME";A$
10 INPUT #0,"NAME";A$

But, it is very useful if you are writing code that you want to be able to output to the screen, a printer, a cassette file, or disk file. For example:

10 REM DEVICE0.BAS
20 DN=0
30 PRINT "OUTPUT TO:"
40 PRINT"S)CREEN, T)APE OR D)ISK:"
50 A$=INKEY$:IF A$="" THEN 50
60 LN=INSTR("STD",A$)
70 ON LN GOSUB 100,200,300
80 GOTO 30

100 REM SCREEN
110 DN=0:GOSUB 400
120 RETURN

200 REM TAPE
210 PRINT "SAVING TO TAPE"
220 OPEN"O",#-1,"FILENAME"
230 DN=-1:GOSUB 400
240 CLOSE #-1
250 RETURN

300 REM DISK
310 PRINT "SAVING TO DISK"
320 OPEN"O",#1,"FILENAME"
330 DN=1:GOSUB 400
340 CLOSE #1
350 RETURN

400 REM OUTPUT HEADER TO DEV
410 PRINT #DN,"+-----------------------------+"
420 PRINT #DN,"+   SYSTEM SECURITY REPORT:   +"
430 PRINT #DN,"+-----------------------------+"
440 RETURN

That is a pretty terrible example, but hopefully shows how useful device number 0 can be. In this case, the routine at 400 is able to output to tape, disk or screen (though in the case of tape or disk, code must open/create the file before calling 400, and then close it afterwards).

Installing the new code.

The code starts out by saving the three bytes currently in the RAM hook:

init
    lda RVEC3       get op code
    sta savedrvec   save it
    ldx RVEC3+1     get address
    stx savedrvec+1 save it

The three bytes are saved elsewhere in program memory, where they are reserved using the RMB statement in the assembly source:

savedrvec rmb 3     call regular RAM hook

More on that in a moment. Next, the RAM hook bytes are replaced with three new bytes, which will be a JMP instructions (byte $7e) and the two byte location of the “new code” routine in memory:

    lda #$7e        op code for JMP
    sta RVEC3       store it in RAM hook
    ldx #newcode    address of new code
    stx RVEC3+1     store it in RAM hook

    rts             done

There is not much to it. As soon as this code executes, the Color BASIC ROM will start calling the “newcode” routine every time a character is being output. After that RAM hook is done, the ROM continues with outputting to whatever device is selected.

Color BASIC came with support for screen, keyboard and cassette.

Extended BASIC used the RAM hook to patch in support for the DLOAD command (which uses device #-3).

Disk BASIC used the RAM hook to patch in support for disk devices.

And now our code uses the RAM hook to run our new code, and then we will call whatever was supposed to be there (which is why we save the 3 bytes that were in the RAM hook before we change it).

Now we look at “newcode” and what it does.

Most printers print lowercase.

Since a printer might print lowercase just fine, our code will not want to uppercase any output going to a printer. Likewise, we may want to write files to tape or disk using full upper or lowercase. Also, you can save binary data to a file on tape or disk. Translating lowercase characters to uppercase would be a bad thing if the characters being sent were actually supposed to be raw binary data.

Thus, DEVNUM is needed so the new code will ONLY translate if the output is going to the screen (device #0). That’s what happens here:

newcode
    * Do this only if DEVNUM is 0 (console)
    tst DEVNUM      is DEVNUM 0? 
    bne continue    not device #0 (console)

If that value at DEVNUM is not equal to zero, the code just skips the lowercase-to-uppercase code.

uppercase
    cmpa #'a        compare A to lowercase 'a'
    blt continue    if less than, goto continue
    cmpa #'z        compare A to lowercase 'z'
    bgt continue    if greater than, goto continue
    suba #32        a = a - 32

For characters going to device #0, A will be the character to be output. This code just looks at the value of A and compares it to a lowercase ‘a’… If lower, skip doing anything else. If it wasn’t lower, it then compares it to lowercase ‘z’. If higher, skip doing anything. Only if it makes it past both checks does it subtract 32, converting ‘a’ through ‘z’ to ‘A’ through ‘Z’.

Lastly, when we are done (either converting to uppercase, or skipping it because it was not the screen), we have this:

continue
savedrvec rmb 3     call regular RAM hook
    rts             just in case...

The double labels — continue and savedrvec — will be at the same location in memory. I just had two of them so I was brancing to “continue” so it looked better than “bra savedrvec”, or better than saving the vector bytes as “continue”.

By having those three remembered (RMB) bytes right there, whatever was in the original RAM hook is copied there and it will now be executed. When it’s done, we RTS back to the ROM.

When this code is built and ran, it immediately starts working. Here is a BASIC loader that will place this code in memory:

10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16165,182,1,103,183,63,38,190,1,104,191,63,39,134,126,183,1,103,142,63,24,191,1,104,57,13,111,38,10,129,97,45,6,129,122,46,2,128,32,16169,16169,57,-1,-1

If you RUN that code, you can then to a CLEAR 200,&H3F00 to protect it from BASIC, and then EXEC &H3F00 to initialize it. Nothing will appear to happen, but if you try to do something like this:

PRINT "Lowercase looks weird on a CoCo"

…you will see “LOWERCASE LOOKS WEIRD ON A COCO”. To test it further, switch to lowercase (SHIFT-0 on a real CoCo, or SHIFT-ENTER on the XRoar emulator) and type a command like LIST.

If the code is working, it should still type out as “LIST” on the screen, and then give a “?SN ERROR” since it’s really lowercase, and BASIC does not accept lowercase commands.

Neat, huh?

No going back.

One warning: There is no uninstall routine. Once it’s installed, do not run it again or it will replace the modified RAM hook (that points to “newcode”) with a new RAM hook (which points to “newcode”) and then at the end of the newcode routine it will then jump to the saved RAM hook that points to “newcode”. Enjoy life in the endless loop!

To make this a better patch, ideally the code should also reserve a byte that represents “is it already installed” and check that first. The first time it’s installed, that byte will get set to some special value. If it is ran again, it checks for that value first, and only installs if the value is uninitialized. It’s not perfect, but it would help prevent running this twice.

An uninstall could also be written, which would simple restore the savedrvec bytes back in the original RAM hook.

But I’ll leave that as an exercise for you, if you are bored.

Until next time…

Migrating from Perforce to Git (BitBucket)

I am placing this information here in case it saves someone else two days of web searching. These steps cover how to do it on Windows and thus have several extra steps that are not needed for Linux and macOS users.

Prerequisites

  1. Perforce installed (with the p4 command line tools).
  2. Git installed: https://git-scm.com/download/win
  3. Python installed: https://www.microsoft.com/store/productId/9PJPW5LDXLZ5

It is assumed you have Perforce set up and authenticated so the p4 command line tool works. It is also assumed you have a remote Git repository like BitBucket also authenticated. I won’t cover those details.

Test the “git p4” command, and fix if needed.

From a command prompt, type “git p4” and see if it works. This relies on a python script called “get-p4” that may need to be edited to work with your version of Python. If you see this:

C:\>git p4
fatal: 'p4' appears to be a git command, but we were not
able to execute it. Maybe git-p4 is broken?

…you will need to edit the file and update the first line to change the reference to “python2” to just be “python”. The file is located here:

C:\Program Files\Git\mingw64\libexec\git-core\git-p4

Change the first line:

#!/usr/bin/python2

…to just be:

#!/usr/bin/python

The original is apparently designed to run only on systems where the python executable is called “python2”.

Step 1 – Clone Perforce in to a local Git repository

Create a directory that will hold your Perforce depot directories, and change in to it:

md FromPerforce
cd FromPerforce

Run the following command (where “xxx” is one of your Perforce depots — such as //depot or //Users or whatever you have). The @all will clone all revisions, and –import-labels is supposed to bring down all tags (this doesn’t seem to work for me):

git p4 clone --import-labels //XXX@all

When finished, you now have a subdirectory that is a local Git repository of all those Perforce files, including version history and commit comments.

Change in to this directory for the next step:

cd XXX

Step 2 – Create a fresh Git repository on BitBucket (or whever)

If you do not already have a Git repository on your remote server (BitBucket in this example), create one. You will need to know the URL of this repository, which is something like:

https://myusername@bitbucket.org/accountname/repositoryname.git

In BitBucket, you get this from the Clone button (it will populate a box with a git clone command line, and you can copy the URL from there).

Step 3 – Pull down the Git repository and rebase, and push the new stuff

git remote add origin https://username@bitbucket.org/accountname/XXX.git

git pull origin master --rebase

git push -u origin --all

(You can use “git remove remote origin” if you need to try this again, due to a type in the URL or something.)

Once that is done, you should be able to see all those Perforce files and their history in BitBucket.

Color BASIC RAM hooks – part 1

See Also: part 1 and part 2.

7/27/2022 NOTE: William Astle left a comment pointing out that my RAM hook example should be using a JSR instead of a JMP. This has not been updated or corrected yet, but I will.

When the CoCo was released in 1980, it came with an 8K ROM containing Color BASIC. If the CoCo was expanded to at least 16K of RAM, a second 8K ROM could be added which contained Extended Color BASIC. If a disk controller were plugged in, that controller added a third 8K ROM containing Disk Extended Color BASIC.

Each ROM provided additional features and commands to what was provided in the original 8K Color BASIC.

To allow this, Color BASIC has a series of RAM hooks that initially point to routines inside Color BASIC, but can be modified by additional ROM code to point somewhere else. For example, Extended Color BASIC would modify these RAM hooks to point to new routines provided by that ROM.

According to the disassembly in Color BASIC Unravelled, there are 25 RAM hooks starting in memory at $15E (350). Each hook is 3 bytes long, which allows it to contain a JMP instruction followed by a 16-bit address.

As an example, there is a vector for “CONSOLE OUT” at $167 (359). On a Color BASIC system, that RAM hook contains $39 $39 $39 (57 57 57). That is the opcode for an RTS instuction, so effectively it looks like this:

rts
rts
rts

In Color BASIC is a subroutine called PUTCHR. Any time BASIC wants to output a character to the device (such as the screen), it loads the character in register A then calls this routine. Here is an example that outputs a question mark:

LB9AF  LDA  #'?     QUESTION MARK TO CONSOLE OUT
LB9B1  JMP  PUTCHR  JUMP TO CONSOLE OUT

The first thing this PUTCHR routine does is JMP to the RAM hook location, which is named RVEC3 (RAM hook vector 3) to run any extra code that might be needed. It looks like this:

* CONSOLE OUT
PUTCHR JSR RVEC3
       ...rest of routine...
       RTS

Since Color BASIC just had “RTS” there, PUTCHR would JMP to the RAM hook bytes and immediately return back, then continue with the output.

The code in Color BASIC knows about outputting to several device numbers. Device #0 (default) is the screen. Device #-1 is the cassette. Device #-2 is the printer.

When Extended Color BASIC came along, it added device #-3 for use with the DLOAD command. (I don’t think I ever knew this, but I did use DLOAD to download my first CoCo terminal program.) Since Color BASIC knew nothing about this device number, these RAM hooks were used to add the new functionality,

Extended Color BASIC modifies the three bytes of this RAM hook to be $7e $82 $73. That represents:

jmp $8273

This jumps to a routine in the Extended Color BASIC ROM called XVEC3 (Extended Vector 3?). This is new code to check to see if we are using the DLOAD command (which outputs over the serial port, but not as a printer).

* CONSOLE OUT RAM HOOK
XVEC3 TST  DEVNUM   CHECK DEVICE NUMBER
      LBEQ L95AC    BRANCH IF SCREEN
      PSHS B        SAVE CHARACTER
      LDB  DEVNUM   *GET DEVICE NUMBER AND
      CMPB #-3      *CHECK FOR DLOAD
      PULS B        GET CHARACTER BACK
      BNE  L8285    RETURN IF NOT DLOAD
      LEAS $02,S    *TAKE RETURN OFF STACK & GO BACK TO ROUTINE
                    *THAT CALLED CONSOLE OUT
      RTS

When Disk Extended Color BASIC is added, the vector is modified to point to a new routine called DVEC3 (Disk Vector 3?) located at $cc1c. (For Disk BASIC 1.0, it is at $cb4a.) That code will test to see if we are outputting to a disk device and, if not, it will long branch to XVEC3 in the Extended ROM. I find this curious since it would seem to imply that this location could never change, else Disk BASIC would break.

DVEC3 TST  DEVNUM   CHECK DEVICE NUMBER
      LBLE XVEC3    BRANCH TO EX BASIC IF NOT A DISK FILE
      ...rest of routine...
      RTS

Thus, with Color, Extended and Disk BASIC ROMs installed, a program wanting to output a character, such as “?”, is doing something like this:

LDA #'?
JMP PUTCHR (Color BASIC ROM)
 \
   PUTCHR JSR RVEC3 (RAM hook)
    \
      RVEC3 JMP DVEC3 (in Disk BASIC)
       \
         DVEC3 TST DEVNUM
         LBLE XVEC3 (in Extended BASIC)
           \
             XVEC3 ...handle ECB
           /
         ...handle rest of DECB...
       /
     /
   ...handle rest of CB...

…or something like that. There’s alot of branching and jumping going on, to be sure.

So what?

This means we should also be able to make use of these RAM hooks and patch our own code in to the process. Since the only thing we can alter is the hook itself, our code has to save the original RAM hook, then point the hook to our new code. At the end of our new code, we jump to where the original RAM hook went, allowing normal operations to continue. (Or, we could have made it jump to the ROM hook first, and after the normal ROM things are done, then run our new code…)

To test this, I made a simple assembly routine to hijack the CONSOLE OUT RAM hook located at $167.

RVEC3 equ $167      console out RAM hook

    org $3f00

init:
    lda RVEC3       get op code
    sta saved       save it
    ldx RVEC3+1     get address
    stx saved+1     save it
    
    lda #$7e        op code for JMP
    sta RVEC3       store it in RAM hook
    ldx #new        address of new code
    stx RVEC3+1     store it in RAM hook

    rts             done

new:
    inc $400
saved rmb 3

In the init routine, the first thing we do is load the first byte (which would be an RTS or a JMP) from the RAM hook and save it in a 3-byte buffer.

We then load the next two bytes (which would be a 16-bit address, or RTS RTS for Color BASIC).

Next we load A with the value of a JMP op code ($7e). We store it in the first byte of the RAM hook vector.

We then do the same thing for the two byte address.

The RTS is the end of the code which hijacked the RAM hook. We have now pointed the RAM hook to our “new” routine.

At new, all we do is increment whatever is in memory location $400 (the top left character of the 32-column screen). Right after that INC is our 3-byte “saved” buffer where the three bytes that used to be in the RAM hook are saved. This make our code do the INC and then the same three bytes that would have been done by the original RAM hook before we hijacked it.

On Color BASIC, the RAM hook starts out with 57 57 57 (RTS RTS RTS), so the new routine would appear as:

new:
   inc $4000
   rts
   rts
   rts

For Extended Color BASIC, where the RAM hook is turned in to JMP $827e, it would become:

new:
   inc $4000
   jmp $827e

When we EXEC the init code, out INC routine is patched in. From that point on, any output causes the top left character of the screen to increment. This will happen for ANY output, even to a printer or cassette file, since this code does not bother checking for the device type.

Here is a BASIC loader program, generated by LWTOOLS:

10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16154,182,1,103,183,63,27,190,1,104,191,63,28,134,126,183,1,103,142,63,24,191,1,104,57,124,4,0,-1,-1

Load that in to a CoCo (or emulator), and RUN it to get the code in memory starting at $3f00.

After RUN, you should be able to EXEC &H3f00 and the hook will be installed.

Now, for something real, you’d want to find a safe place to store the new hook code. At the very least, we should have a CLEAR 200,&H3f00 or similar in this program to ensure BASIC doesn’t overwrite the assembly code. This should be enough for a simple proof-of-concept.

The RAM hooks we have available include:

  • OPEN COMMAND
  • DEVICE NUMBER VALIDITY CHECK
  • SET PRINT PARAMETERS
  • CONSOLE OUT
  • CONSOLE IN
  • INPUT DEVICE NUMBER CHECK
  • PRINT DEVICE NUMBER CHECK
  • CLOSE ALL FILES
  • CLOSE ONE FILE
  • PRINT
  • INPUT
  • BREAK CHECK
  • INPUTTING A BASIC LINE
  • TERMINATING BASIC LINE INPUT
  • EOF COMMAND
  • EVALUATE AN EXPRESSION
  • RESERVED FOR ON ERROR GOTO CMD
  • ERROR DRIVER
  • RUN
  • ASCII TO FLOATING POINT CONV.
  • BASIC’S COMMAND INTERP. LOOP
  • RESET/SET/POINT COMMANDS
  • CLS
  • SECONDARY TOKEN HANDLER
  • RENUM TOKEN CHECK
  • EXBAS’ GET/PUT
  • CRUNCH BASIC LINE
  • UNCRUNCH BASIC LINE

There are many possibilities here, including adding new commands.

Until next time…

C and size_t and 64-bits

Do what I say and nobody gets hurt!

At my day job, I work on a Windows application that supervises high power solid state microwave generators. (The actual controlling part is done by multiple embedded PIC24-based boards, which is a good thing considering the issues Windows has given us over the years.)

At some point, we switched from building a 32-bit version of this application to a 64-bit version. The compiler started complaining about various things dealing with “ints” which were not 64-bits, so the engineer put in #ifdef code like this:

#ifdef _NI_mswin64_
    unsigned __int64 length = 0;
#else
    unsigned int length = 0;
#endif

That took care of the warnings since it would now use either a native “int” or a 64-bit int, depending on the target.

Today I ran across this and wondered why C wasn’t just taking care of things. A C library routine that returns an “int” should always expect an int, whether that int is 16-bits (like on Arduino), 32-bits or 64-bits on the system. I decided to look in to this, and saw the culprits were things like this:

length = strlen (pathName);

Surely if strlen() returned an int, it should not need to be changed to an “unsigned __int64” to work.

And indeed, C already does take care of this, if you do what it tells you to do. strlen does NOT return an int:

size_t strlen ( const char * str );

size_t is a special C data type that is “whatever type of number it needs to be.” And by simply changing all the #ifdef’d code to actually use the data type the C library call specifies, all the errors go away and the #ifdefs can be removed.

size_t length = 0;

A better, more stricter compiler might have complained about using an “int” to catch something coming back as “size_t.”

Oh wait. It did. We just chose to solve it a different way.

Until next time…

Color BASIC and VARPTR

In this article, I want to talk a bit about how Color BASIC variables are stored in memory. This will help explain what the VARPTR (“variable pointer”) command is used for, and also be useful in the future when I get around to exploring how the Extended BASIC GET and PUT graphics commands work.

But first…

What’s in a (variable) name?

In Color BASIC, variable names (both string and numeric) start with a letter and may be followed by a second letter or a number. Valid variable names are:

  • A to Z
  • A0 to A9 through Z0 to Z9.
  • AA to ZZ

String variables follow the same convention, just with a $ after the name (A$, AB$, F5$, etc.).

What’s in a (long variable) name?

While BASIC to allow you to type longer variable names, only the first two characters are used. Thus, a variable such as:

LONGVARIABLE=1

…will actually let you…

PRINT LONGVARIABLE

…and see that the value is 1. However, any other variable starting with the first two characters (“LO”) would overwrite this variable (since it would be the same variable).

Color BASIC variables may be very long, but only the first two characters are used.

Each time you declare a new variable, you will see memory decrease by seven bytes:

Each Color BASIC numeric variable takes 7 bytes of memory.

Without looking at the Color BASIC Unravelled BASIC ROM disassembly, we can speculate that two of those bytes are probably for the variable name, with the other five used for … whatever it takes to have a variable. (And with looking at those books, we can see exactly how this works. But exploring is more fun, so let’s do that instead…)

Thanks for the (variable) memory.

The Microsoft BASIC found in the Radio Shack Color Computer has an interesting command called VARPTR. It returns the memory address of a specified variable. In an earlier series on interfacing BASIC with assembly, I discussed VARPTR to some detail, but all we need to know here is that a standard numeric variable is stored as five bytes of memory.

Thus, if you use VARPTR to get the location of a numeric variable, you can print the five bytes at that location and see the raw data that BASIC uses to represent that floating point value:

Color BASIC VARPTR on a numeric variable.

Above, we see that the numeric variable “A” was located in memory at 9868 at that moment in time. The five bytes that represent “A=1” appear to be 129, 0, 0, 0, 0. All numbers in Color BASIC are floating point values, with the bytes representing an Exponent, Mantissa and Sign. I’d try to explain, but then my head would explode. (Though I do plan to figure it out and write about it at some point.)

For now, let’s just go with “numbers take up five bytes” and move on.

String me along.

String variables (variable names that end in $) are a bit different. The text of the string is stored elsewhere in string memory, and the five bytes that VARPTR points you to contain information on how to get to where the actual string lives:

Above you see the five bytes are 7, 0, 127, 248 and 0. The first byte is the length of the string. In this case, “ABCDEFG” is 7 bytes long, so the first byte is 7. The next byte is not used for a string, and is always 0. The third and fourth bytes are the memory location where the string text is actually stored. The fifth byte is not used and is always 0.

See my earlier article for more examples of this.

What’s in a (variable) name (revisited)?

We know that each variable takes up 7 bytes of memory, and that VARPTR returns the location of the 5 bytes that are the actual variable. So where is the name? Directly before the 5-bytes. If you look at the two bytes before the VARPTR address, you will see the name bytes:

The 65 is the ASCII character for “A”, and since it was only one digit, the second location is 0. If the variable had been named “AA”, those two bytes would be 65 65.

And, if the variable is a string, the second byte will have the high bit set (128 added to it).

If the variable had been AA$, those two bytes would have been 65 193 (65+128).

Hip, hip array!

Arrays are another variable type that I have not explored until shortly before writing this article. The DIM command can be used to declare an array of 1 to n numbers (or strings). It is a base-0 value (meaning elements count from 0 on up) so if you wanted ten entries, you could DIM A(9):

10 DIM A(9)
20 FOR I=0 TO 9
30 A(9)=RND(100)
40 NEXT
50 FOR I=0 TO 9
60 PRINT I,A(I)
70 NEXT

The above code would declare room for 10 entries in the A array, then load each entry (0 to 9) with a random number. It then does another loop to print the contents of all ten array entries, showing the random numbers that were stored there.

When I first looked at this, I noticed declaring an array seemed to take 7 bytes of memory plus 5 bytes per entry, versus a normal variable taking just 7 bytes (2 bytes for the name plus 5 bytes of the number).

Above, you see we started with 22823 bytes free. We declared DIM A(0) to hold one entry and memory went down to 22811 — 12 bytes. Then doing a CLEAR to erase all variables and declaring the A array to hold two entries consumed 17 bytes. We know that 2 bytes is the variable name, and each value would take 5 bytes, so it looks like the array has 2 bytes for the name, 5 bytes for “something special to an array”, then 5-bytes for each entry.

CLEARing memory then declaring the A array to hold three entries (0-2) confirmed this, since it now took 22 bytes (7 bytes plus 15 for the three numbers).

But VARPTR still ends up pointing to something that looks just like a normal variable – 5 bytes that represent that variable or string. Here’s that normal variable again:

Color BASIC VARPTR on a numeric variable.

And here’s the same, but for an array variable:

So where are the other bytes? Just like the name, they are just before the value that VARPTR returns. To see them, we’d look at the 7 bytes just before what VARPTR returns.

A WARNING ABOUT VARPTR AND ARRAYS

The BASIC program is stored in memory, followed by variables, and then arrays. At the very end of memory is string storage. If you get a VARPTR to an array, and then create a new variable, the new variable will be inserted in variable memory, and array storage will be relocated. This can and will render the VARPTR value incorrect! To avoid this, make sure you declare any variables you plan to use before doing the VARPTR!

In this example, notice the use of DIM to declare “I”, since “I” will be used in a FOR/NEXT loop AFTER the VARPTR is obtained. If it had not been re-declared, the VARPTR would have been printed, then “I” would have been dynamically created, causing arrays to be moved up in memory. Everything would have been off by the 7 bytes that got inserted by allocating the new variable “I”.

65 is the ASCII value for “A”, the name of our variable. Just like normal variables, if the array is a string, the second byte of the name will have the high bit set, which adds 128 to it. DIM AA(0) would be 65 65, but DIM AA$(0) would be 65 193 (65+128).

  • Byte 1 – first letter of variable name
  • Byte 2 – second letter of variable name (add 128 if it’s a string)

Next is a 0, then 12. These values increased based on how large the array was, going up by 5 each time a new element was added. With only one entry, 12 seemed to be the size of the 7-byte header, plus a 5-byte variable. Therefore…

  • Bytes 3 and 4 – memory used by the array, from start of header to end of array data.

The next byte seems to increase based on the number of DIMensions. DIM A(0) would show 1. DIM A(0,0) would show two. Therefore…

  • Byte 5 – number of dimensions. DIM A(0)=1, DIM A(0,0)=2, DIM A(0,0,0)=3

The next two bytes seemed to change based on the size of the array — as in, DIM A(0) would give 1, and DIM A(99) would give 100:

  • Bytes 6 and 7 – number of elements in the array.

And then I realized this information was only correct for a single dimension array, such as DIM A(9).

What happens with a multi-dimensioned array, like DIM A(9,9)? Each dimension gets another two bytes added to the header. DIM A(0) would have a 7 byte header. DIM A(0,0) would have a 9 byte header. DIM A(0,0,0) would have an 11 byte header, and so on.

Here is what I see when I do DIM A(1,2,3):

We see three sets of two bytes each — 0 4, 0 3, and 0 2. That corresponds to our DIM A(1,2,3) since using base-0 that is 2 (0-1), 3 (0-2) and 4 (0-3). But, it’s in reverse order.

That tells us after the 2 byte name, and 2 byte size, the 1 byte number of dimensions will be 2 bytes containing the size of each array in the reverse order they were declared.

  • Bytes 6 and 7, 8 and 9, 10 and 11, etc. – number of elements in each array, in reverse order for some reason.

Good to know. Using that above example, maybe it looks like this:

DIM A(1,2,3)

 NAME    SIZE  #D   third  second first   data....
 |  |    |  |  |    |  |   |  |   |  |     |
[65][0] [x][x] [3] [0][4] [0][3] [0][2] [.....][.....]

The order of the data bytes was the next thing I wanted to figure out. If it was just a single dimension array, it would be simple. But with multiple dimensions I was curious what order they were stored.

I did a test where I made a two dimensional array, each holding two elements. This 2×2 array would hold four numbers. By checking the address of each entry, I was able to determine the order:

This showed me that the elements for the first array would be first, which surprised me because the order of the “how big is each array” entries was reversed.

I tested with a three dimensional array, each with two elements. This 2x2x2 array could hold eight elements. Again I saw that the first dimension was stored, then the second, and so on:

Looking at a two-element array looked quite like binary, representing 000, 100, 010, 110, 001, 101, 011, 111. That’s opposite of how binary counting is normally represented, where it starts with the right-most bit. But still interesting to see the familiar pattern.

Here is the program I used to show the VARPTR of each dimension in the array:

0 ' showdims.bas
10 SZ=1
20 DIM DV,I,J,K,A(SZ,SZ,SZ)
30 ' 0=SCREEN, -2=PRINTER
40 DV=0
50 PRINT #DV,"ENTRIES:";(SZ+1)^3
60 FOR K=0 TO SZ
70 FOR J=0 TO SZ
80 FOR I=0 TO SZ
90 PRINT #DV,"VARPTR(A(";
100 PRINT #DV,USING ("# # #");I;J;K;
110 PRINT #DV,")",;
120 PRINT #DV,VARPTR(A(I,J,K))
130 NEXT:PRINT#DV
140 NEXT
150 NEXT

Since I could only fit all the entries from a small array on the 32 column screen’s 16 lines, and only a few more on a CoCo 3’s 24 column 40/80 screen, I made the program support outputting to the printer. Using XRoar’s “print to a file” feature, I was able to capture larger dimensions:

Here is a 3x3x3 “cube” dimension:

ENTRIES: 27 
VARPTR(A(0 0 0)  10060 
VARPTR(A(1 0 0)  10065 
VARPTR(A(2 0 0)  10070 

VARPTR(A(0 1 0)  10075 
VARPTR(A(1 1 0)  10080 
VARPTR(A(2 1 0)  10085 

VARPTR(A(0 2 0)  10090 
VARPTR(A(1 2 0)  10095 
VARPTR(A(2 2 0)  10100 

VARPTR(A(0 0 1)  10105 
VARPTR(A(1 0 1)  10110 
VARPTR(A(2 0 1)  10115 

VARPTR(A(0 1 1)  10120 
VARPTR(A(1 1 1)  10125 
VARPTR(A(2 1 1)  10130 

VARPTR(A(0 2 1)  10135 
VARPTR(A(1 2 1)  10140 
VARPTR(A(2 2 1)  10145 

VARPTR(A(0 0 2)  10150 
VARPTR(A(1 0 2)  10155 
VARPTR(A(2 0 2)  10160 

VARPTR(A(0 1 2)  10165 
VARPTR(A(1 1 2)  10170 
VARPTR(A(2 1 2)  10175 

VARPTR(A(0 2 2)  10180 
VARPTR(A(1 2 2)  10185 
VARPTR(A(2 2 2)  10190 

And here is a 10x10x10:

ENTRIES: 1000 
VARPTR(A(0 0 0)  10060 
VARPTR(A(1 0 0)  10065 
VARPTR(A(2 0 0)  10070 
VARPTR(A(3 0 0)  10075 
VARPTR(A(4 0 0)  10080 
VARPTR(A(5 0 0)  10085 
VARPTR(A(6 0 0)  10090 
VARPTR(A(7 0 0)  10095 
VARPTR(A(8 0 0)  10100 
VARPTR(A(9 0 0)  10105 

VARPTR(A(0 1 0)  10110 
VARPTR(A(1 1 0)  10115 
VARPTR(A(2 1 0)  10120 
VARPTR(A(3 1 0)  10125 
VARPTR(A(4 1 0)  10130 
VARPTR(A(5 1 0)  10135 
VARPTR(A(6 1 0)  10140 
VARPTR(A(7 1 0)  10145 
VARPTR(A(8 1 0)  10150 
VARPTR(A(9 1 0)  10155 

VARPTR(A(0 2 0)  10160 
VARPTR(A(1 2 0)  10165 
VARPTR(A(2 2 0)  10170 
VARPTR(A(3 2 0)  10175 
VARPTR(A(4 2 0)  10180 
VARPTR(A(5 2 0)  10185 
VARPTR(A(6 2 0)  10190 
VARPTR(A(7 2 0)  10195 
VARPTR(A(8 2 0)  10200 
VARPTR(A(9 2 0)  10205 

...just kidding...

I really did start with 1000 lines of output in this article. I get paid per line ;-)

If you get bored, maybe you can figure out a program that would dump the bytes for each array element.

So how big is it?

Here is a short program which will calculate how much room an array will take. If it is a string array, it will be this size plus the size of all the bytes in the string data portion.

0 ' dimsize.bas
10 INPUT "NUMBER OF DIMENSIONS";ND
20 M=1
30 FOR I=0 TO ND-1
40 PRINT "ENTRIES FOR DIM";I;
50 INPUT J
60 M=M*J
70 NEXT
80 M=5+(2*ND)+(M*5)
90 PRINT "MEMORY USED:";M

In conclusion…

And that is about all I have to say on VARTPR. For now. Until I started writing about it, I knew nothing about how it worked with arrays. I had used it a few times to pass in a string to an assembly language routine, but that was the extent of my knowledge.

Let me know what I got wrong.

Until next time…

Google and Apple and multiple accounts…

I am posting this for the search engines to find, in case others have found themselves in this situation.

Google and Apple created situations where you ended up with multiple accounts that could not be merged together.

Apple

2000 – iTools

Apple introduces iTools, which included a free e-mail address “for life.” This service was renamed to .Mac in 2002, and later MobileMe in 2008. It was replaced by iCloud in 2012.

I signed up for my free @mac.com e-mail address back then, and still have that original e-mail address. I never used that e-mail, since I had been using the same pobox.com service for that since 1995.

As iTools/.Mac/MobileMe/iCloud evolved, they remained linked with my original 2000 account. When I purchases extra iCloud Drive space, that had to be done through my iCloud (.mac) e-mail address, since that was the account all the services were on.

2003 – iTunes Store

Apple introduces the iTunes program in 2001, which was used to sync music to the brand new iPod MP3 player. In 2003, the iTunes Store launched, and I signed up for an account there and purchased my first music digital download.

These were two completely different services and accounts. I signed up to the iTunes Store using the e-mail address I’d been using since 1995.

Over the years, all my iTunes purchases were made under my iTunes account. This includes any iPhone apps purchased from the App Store, introduced in 2008.

Apple ID

At some point, the Apple ID was introduced. One of the features is:

Users can use different Apple IDs for their store purchases and for their iCloud storage and other uses. This includes many MobileMe users who have always had difficulties as they were forced to use more than one Apple ID, because on signing-up to the MobileMe service a new Apple ID was automatically created using the me.com email address being created at the time, meaning users could not change their previous Apple ID email address to be their me.com email address and has always remained so. Apple does not permit different accounts to be merged.[13]

Wikipedia page for Apple ID

Meanwhile…

Google

2004 – Gmail

Google launched a free web e-mail service on April 1, 2004. It claimed to give every user 1 gigabyte of e-mail storage, for free. That was such a huge amount of storage that many suspected this was an April Fool’s Day joke. Like many, I signed up for a gmail.com e-mail address.

I never used it, preferring to stick with my pobox.com alias I had been using since 1995.

2005 – YouTube

YouTube was not created by Google. It was purchased by Google in late 2006. I signed up for a YouTube account in those early days, before Google was involved. Thus, not a Google account. After Google acquired YouTube, they rolled the accounts over to Google accounts, but could not merge them.

Thus, I ended up with one Google account, which was for my gmail, and another Google account, which was my YouTube. As Google introduced more services, they were added to both my accounts.

Today

Today, I have two Apple IDs — one that I had always used for purchases, and the other was just a free e-mail account. As more and more Apple services were added, they were added to both accounts. Anything with an iCloud login, such as an iPhone, iPad, Apple Watch, Mac, etc., I would use my original iTools account. Anything associated with the Apple Stores would use my original iTunes account.

Fortunately, iOS devices have these areas separated — you can log in to your iCloud account, and log in to a separate account for purchases and subscriptions.

Meanwhile, as Google introduced new services, they rolled in to all accounts. I used them with my original YouTube account, and kept my Gmail account just as e-mail.

Which means today I have two Google Sheets, two Google Docs, two Google Voice, etc. I log in to one if using e-mail, and the other if using “everything else.”

Neither company has a way to merge these accounts. With Google, I had created a free Google Voice voice mail number in the mid-2000s to use with the podcasts I was making at the time, Then, several years later I had to give up my cell phone for awhile. I ported my old Sprint-then-AT&T number over to my other account’s Google Voice. If Google ever did allow merging accounts, I’d have to lose one of those numbers.

As to Apple, I initially liked having the thing connected to money (credit cards on file to make purchases or pay for subscriptions) separated from the thing that was just e-mail and free Apple services. But that created a problem, as I somehow have been in different accounts when I’ve activated different things, such as AppleCare on a device, or a subscription to some service. Now I have to log out and re-log to the other account just to see what all I am paying for.

It’s quite a mess.

YouTube Channels makes things even worse

To make things even worse, at some point Google introduced Channels to YouTube. This allowed you to have one main YouTube account (hooked to you Google login) but create sub-channels that could have their own videos and playlists. My channel has always been an odd mix of theme park home movies, Halloween haunted house interviews, Renaissance festival footage, retro computer videos, and dogs skateboarding. Channels should be a solution, but YouTube does not allow you to move a video from a legacy account to a new Channel. They advise you to download the video from YouTube, then re-upload to the new channel. That loses all play history, comments, etc.

Is there a solution?

I am posting this here in case anyone finds it via search, and discovers any type of solution to these issues.

I spent about an hour with various Apple Support folks last night — initially as a Chat, and then via phone as I was forwarded from department to department. Obviously, changing billing credit cards is a different division than the one that handles iCloud Disk subscriptions, so I don’t feel I was ever put in the wrong place. There were just a bunch of different places that are all part of this ecosystem. I expect, if I could reach a Google support person on the phone, I would have a similar experience there.

I offer no tips. No solutions. And no advice. But if you end up here with a similar story, please share it in the comments. I’d like to hear what you have discovered.

Until next time… Remember: Anything simple today may become a huge pain in the arse decades later ;-)

Color BASIC Attract Screen – part 6

See also: part 1, part 2, part 3, part 4, unrelated, and part 5.

War by James Garon

Just as I thought I had reached the conclusion to my epic masterpiece about classic CoCo game startup screens, Robert Gault made this post to the Color Computer mailing list:

Robert Gault robert.gault at att.net
Sat Jul 2 09:56:21 EDT 2022

An author of games for Tandy, James Garon, put some lines in the Basic game WAR which is on colorcomputerarchive.com . The code is in lines 60000 and up which can’t be LISTed with a CoCo3 but can be read with a CoCo2.  The lines in question contain PRINT commands which when listed with a CoCo2, look like the data inside the quotes have been converted into Basic commands. You can also see this if you use imgtool or wimgtool to extract the program from the .dsk image.  These lines generate PMODE graphics for the title screen.

Do anyone have a clue as to how these Basic lines work?

– Robert Gault, via CoColist

The game in question is available in tape or disk format from the Color Computer Archive:

https://colorcomputerarchive.com/search?q=War+%28Tandy%29&ww=1

And, you can even click the “Play Now” button and see it run right in your web browser. Click below and take a look at the title screen:

https://colorcomputerarchive.com/test/xroar-online/?machine=cocous&basic=RUN%22WAR%22%5cr&cart=rsdos&disk0=/unzip%3Ffile%3DDisks/Games/War%20(Tandy).zip/WAR.DSK

War by James Garon (title screen)

This title screen uses the CoCo’s lesser-known screen color, and has those iconic rotating color blocks.

And, it’s in BASIC! Well, almost. It contains an assembly language routine that rotates those colors, and it does it the same way I figured out in this article series. I am just thirty years too late with my solution.

Line 60000

60000 CLS:A$=STRING$(28,32):PRINT"RESTORERESTOREMOTORMOTOR^^SCREENSCREENDRIVEDRIVEDSKI$DSKI$!!!RESTORERESTOREMOTORMOTOR^^SCREENSCREENDRIVEDRIVEDSKI$DSKI$!!!";

As Robert mentioned, there’s some weirdness in the program starting at line 60000. When you LIST it, you get a rather odd output full of BASIC keywords and such:

WAR.BAS line 60000

The first bit looks okay… CLS to clear the screen, then A$ is created to be 28 spaces — CHR$(32). But that PRINT looks a bit … weird.

I’ve seen this trick before, and even mentioned it recently when talking about typing in an un-typable BAISC pogram. The idea is you can create a program that contains something in a string or PRINT statement, like:

PRINT "1234567890"

…and then you alter the bytes between those quotes somehow, such as locating them and using POKE to change them. Let’s figure out an easy way to do this.

First, I’ll start with a print statement that has characters I can look for later, such as the asterisk. I like that because it is ASCII 42. If you don’t know why I like forty-two, you must be new here. This wikipedia page will give you the details…

100 PRINT "**********"

Now we need to alter those bytes and change them to something we couldn’t normally type, such as graphics blocks (characters 128-255). To do this, we can use some code that scans from the start of the BASIC program to the end, and tries to replace the 42s with something else.

This is dangerous, since 42 could easily appear in a program as part of a keyword token or line number or other data. But, I know that a quote is CHR$(34), so I could specifically look for a series of 42s that is between two 34s.

Numbers.

So many numbers.

In Color BASIC, memory locations 25 and 26 contains the start of the BASIC program in memory. Locations 27 and 28 is the end of the program. Code to scan that range of memory looking for a quote byte (34) that has an asterisks byte (42) after it might look like this:

0 ' CODEHACK.bas
10 S=PEEK(25)*256+PEEK(26)
20 E=PEEK(27)*256+PEEK(28)
30 L=S
40 V=PEEK(L)
50 IF V=34 THEN IF PEEK(L+1)=42 THEN 80
60 L=L+1:IF L<E THEN 40
70 END
...

We could then add code to change all the 42s encountered up until the next quote (34).

80 L=L+1:IF PEEK(L)=34 THEN END
90  POKE L,128:GOTO 80
100 PRINT "**********"

Line 80 moves to the next byte and will end if that byte is a quote.

In line 90, it uses POKE to change the character to a 128 — a black block. It continues to do this until it finds the closing quote.

If you load this program and LIST it, it looks like the code shown. But after you RUN, listing it reveals a garbled PRINT statement similar to the WAR line 60000. But, if you run that garbled PRINT statement, you get the output of black blocks:

CoCo code hack!

As you can see, line 100 changes ten asterisks to the keyword FOR ten times. I am guessing that the numeric token for “FOR” is 128.

Color BASIC Unravelled

…and my guess was correct! According to Color BASIC Unravelled, the token for FOR is hex 80 — which is 128. Perfect. So the BASIC “LIST” routine is dump, and tries to detokenize things even if they are surrounded by quotes. Interesting.

At this point, if you were to EDIT that line, it would detokenize it to be…

100 PRINT "FORFORFORFORFORFORFORFORFORFOR"

…and if you saved your edit, you’d now have a line that would print exactly that, no longer ten black blocks for the word FOR ten times as if you’d typed them all in.

This makes these changes uneditable. BUT, once the modification code has been ran, you can delete it, then SAVE/CSAVE the modified program. When it loads back up, it will have those changes.

In the case of WAR line 60000, it’s a PRINT that shows a series of color blocks used for the top of the attract screen.

Here is the garbled output of lines 60000 on in the WAR.BAS program:

60000 CLS:A$=STRING$(28,32):PRINT"RESTORERESTOREMOTORMOTOR^^SCREENSCREENDRIVEDRIVEDSKI$DSKI$!!!RESTORERESTOREMOTORMOTOR^^SCREENSCREENDRIVEDRIVEDSKI$DSKI$!!!";
60005 FORI=1TO8:GOSUB60028:NEXT:FORI=1TO6:GOSUB60028:NEXT
60010                             PRINT"MOTORMOTORRESTORERESTORE!!!DSKI$DSKI$DRIVEDRIVESCREENSCREEN^^MOTORMOTORRESTORERESTORE!!!DSKI$DSKI$DRIVEDRIVESCREENSCREEN^";
60020 POKE1535,175:T$="WAR!":PRINT@99,"A YOUNG PERSON'S CARD GAME";:PRINT@80-LEN(T$)/2,T$;:PRINT@175,"BY";:PRINT@202,"JAMES  GARON";:PRINT@263,"COPYRIGHT (C) 1982";:PRINT@298,"DATASOFT INC.";:PRINT@389,"LICENSED TO TANDY CORP.";:SCREEN0,1
60025 GOSUB60030:I$=INKEY$:FORI=1TO300:FORJ=1TO30:NEXT:IFINKEY$=""THENEXECV:NEXT:RETURNELSERETURN
60028 PRINTSTRING$(2,127+16*(9-I))TAB(30)STRING$(2,127+16*I);:RETURN
60030 A$="RUN!SUBELSE,NEXTENDFORTHENFORDIM/!9"
60060 V=VARPTR(A$):V=PEEK(V+2)*256+PEEK(V+3):RETURN

Line 60005 does two FOR/NEXT loops and both GOSUB 600028, so we’ll look at that next.

Line 60028

60028 PRINTSTRING$(2,127+16*(9-I))TAB(30)STRING$(2,127+16*I);:RETURN

This routine prints solid colored blocks on the left and right side of the screen, based on the value of I.

WAR.BAS line 60028

The two FOR loops are used to fill the entire screen. There are only 8 colors, so you can’t just do one loop from 1 to 14.

Line 60010

60010                             PRINT"MOTORMOTORRESTORERESTORE!!!DSKI$DSKI$DRIVEDRIVESCREENSCREEN^^MOTORMOTORRESTORERESTORE!!!DSKI$DSKI$DRIVEDRIVESCREENSCREEN^";

Look familiar? It’s just like the PRINT in line 60000, though the pattern of the blocks is different, and it is only printing 31. Since the one prints on the bottom of the screen, if it printed all the way to the bottom right position, the screen would scroll up one line.

WAR.BAS line 60010

Line 60020

60020 POKE1535,175:T$="WAR!":PRINT@99,"A YOUNG PERSON'S CARD GAME";:PRINT@80-LEN(T$)/2,T$;:PRINT@175,"BY";:PRINT@202,"JAMES  GARON";:PRINT@263,"COPYRIGHT (C) 1982";:PRINT@298,"DATASOFT INC.";:PRINT@389,"LICENSED TO TANDY CORP.";:SCREEN0,1

More normal code… The POKE 1535,175 is what fills in the bottom right block of the screen, where PRINT did not go to. 175 is a blue block.

After this are just normal PRINT@ statements to put the text on the screen.

At the end, SCREEN 0,1 puts the CoCo in to its alternate screen color of pink/red/orange/whatever color that is.

Line 60025

60025 GOSUB60030:I$=INKEY$:FORI=1TO300:FORJ=1TO30:NEXT:IFINKEY$=""THENEXECV:NEXT:RETURNELSERETURN

This line is normal code, but the use of EXEC tells us some assembly language is being used.

The first thing it does is GOSUB 60030, then it gets any waiting keypress in I$. I don’t see I$ used so I’m unsure of this purpose.

Next it does two FOR/NEXT loops. The first “FOR I” appears to be the number of times to do this routine. The second “FOR J/NEXT” is just a timing delay.

After this is a direct check for any waiting key by using INKEY$ directly. If no key is pressed, “EXEC V” is done… This would execute whatever machine language routine is loaded in to memory at wherever V is set to. But what is V? That must be the GOSUB 60030, which we will discuss after this.

After the NEXT (FOR I) is “RETURN ELSE RETURN”. That way it does a return whether or not the IF INKEY$ is true. Since either way will RETURN, with nothing executed after this line, this might have also worked (extra spaces added for readability):

60025 GOSUB 60030:I$=INKEY$:FOR I=1 TO 300:FOR J=1 TO 30:NEXT:IF INKEY$=""THEN EXEC V:NEXT
6026 RETURN

But James Garon seems to know his stuff. Each line number takes up 5 bytes of space. The three keywords “RETURN ELSE RETURN” (without spaces) only takes up four (I’m guessing RETURN is a one byte token and ELSE is a two byte token.)

So indeed, the odd “RETURN ELSE RETURN” is less memory than putting one RETURN on the next line. (In my Benchmarking BASIC articles, I’ve focused on speed versus space, so perhaps I’ll have to do a series on “Making BASIC Smaller” some time…)

This leads us to the GOSUB 60030.

Lines 60030-60060

60030 A$="RUN!SUBELSE,NEXTENDFORTHENFORDIM/!9"
60060 V=VARPTR(A$):V=PEEK(V+2)*256+PEEK(V+3):RETURN

Line 60030 creates a string, but the string seems reminiscent of those PRINT lines seen earlier. Since we don’t see anything using this string, it’s probably not for displaying block graphics characters.

We do see the use of VARPTR(A$) on the next line. VARPTR returns the “variable pointer” for a specified variable. I’ve discussed VARPTR in earlier articles, but the Getting Started with Color BASIC manual describes it as:

VARPTR (var) Returns addresses of pointer to the specified variable.

– Getting Started with Color BASIC

When using VARPTR on a numeric (floating point) variable, it returns the location of the five bytes that make up the number, somewhere in variable space.

But strings are different. Strings live in separate string memory (reserved by using the CLEAR command, with a default of 200 bytes), or they could be contained in the program code itself. See my String Theory series for more on that. With a string, the VARPTR points to five bytes that point to where the string data is contained.

In part 3 of my my DEFUSR series, I describe it as:

The first byte where the string is stored will be the size of that string:

A$=”THIS IS A STRING IN MEMORY”
X = VARPTR(A$)
PRINT “A$ IS LOCATED AT”;X
PRINT “A$ IS”;PEEK(X);”LONG”

I forget what the second byte is used for, but bytes three and four are the actual address of the string character data:

PRINT “STRING DATA IS AT”;PEEK(X+2)*256+PEEK(X+3)

– Interfacing assembly with BASIC via DEFUSR, part 3

This looks familiar, since the VARPTR(A$) in this code then gets the address of the string by PEEKing bytes three and four:

60060 V=VARPTR(A$):V=PEEK(V+2)*256+PEEK(V+3):RETURN

This tells me A$ contains a machine language routine. The GOSUB to this routine returns the address of the bytes between the quotes of the A$=”…” then that location is EXECuted to run whatever the routine does.

To figure this one out is much simpler, since no PEEKing of BASIC code is needed. It’s in a string, so we can just print the ASC() value of each byte in the string:

60030 A$="RUN!SUBELSE,NEXTENDFORTHENFORDIM/!9"
60031 FOR I=1 TO LEN(A$):PRINT ASC(MID$(A$,I,1));:NEXT:END

By doing a RUN 60030 I then get a list of bytes that are inside of that string:

142  3  255  48  1  166  132  44  4  139  16  138  128  167  128  140  6  1  47  241  57

I recognize 57 as the op code for an RTS instruction, so this does look like it’s machine code. And while I could use some 6809 data sheet and look up each of those bytes to figure out what they are, I’d rather have something else do the work for me.

Online 6809 Simulator to the rescue!

At www.6809.uk is an incredible 6809 simulator that I recently wrote about. It lets you paste in assembly code and run it in a debugger, showing the registers, op codes, etc. To get these bytes in to the emulator, I just turned them in to a stream of DATA using the “fcb” command in the simulator’s assembler:

routine fcb 142,3,255,48,1,166,132,44
        fcb 4,139,16,138,128,167,128,140,6
        fcb 1,47,241,57

By pasting that in to the simulator’s “Assembly language input” box and then clicking “Assemble source code“, the code is built and then displays on the left side in the debugger, complete with op codes:

6809.uk

Now I can see the code is:

4000:	8E 03 FF      	LDX #$03FF
4003:	30 01         	LEAX $01,X
4005:	A6 84         	LDA ,X
4007:	2C 04         	BGE $400D
4009:	8B 10         	ADDA #$10
400B:	8A 80         	ORA #$80
400D:	A7 80         	STA ,X+
400F:	8C 06 01      	CMPX #$0601
4012:	2F F1         	BLE $4005
4014:	39            	RTS

Since I did not give any origination address for where this code should go, the simulator used hex 4000. There are two branch instructions that refer to memory locations, so I’ll change those to labels and convert this just to the source code. I’ll even include some comments:

    LDX #$03FF  * X points to 1023, one byte before screen memory
    LEAX $01,X  * Increment X by 1 so it is now 1024
L4005
    LDA ,X      * Load A with the byte pointed to by X
    BGE L400D   * If that byte is not a graphics char, branch to L400d
    ADDA #$10   * Add hex 10 (16) to the character, next color up
    ORA #$80    * Set the high bit (in case value rolled over)
L400D
    STA ,X+     * Store (possibly changed) value back at X and increment X.
    CMPX #$0601 * Compare X to two bytes past end of screen
    BLE L4005   * If X is less than that, branch to L4005
    RTS         * Return

This code scans each byte of the 23 column screen. If the character there has the high bit set, it is a graphics character (128-255 range). It adds 16 to the value, which moves it to the next color (16 possible combinations of a 2×2 character block for 8 colors). It stores the value back to the screen (either the original, or one that has been shifted) and then increments X to the next screen position. If X is less than two bytes past the end of the screen, it goes back and does it again.

Hmm, it seems this routine would actually write to one byte past the end of the screen memory if it contained a value 128-255. Hopefully nothing important is stored there. (Am I right?)

And, for folks more used to BASIC, here is the same code with BASIC-style comments:

    LDX #$03FF  * X=&H3FF (1023, byte before screen)
    LEAX $01,X  * X=X+1
L4005
    LDA ,X      * A=PEEK(X)
    BGE L400D   * IF A<128 GOTO L400D
    ADDA #$10   * A=A+&H10:IF A>&HFF THEN A=A-&HFF
    ORA #$80    * A=A OR &H80
L400D
    STA ,X+     * POKE X,A:X=X+1
    CMPX #$0601 * Compare X to &H601 (two bytes past end)
    BLE L4005   * IF X<=&H601 GOTO L4005
    RTS         * RETURN

My BASIC-style comments don’t exactly match what happens in assembly since. For example, “BGE” means “Branch If Greater Than”, but there was no compare instruction before it. In this case, it’s branching based on what was just loaded in to the A register, and would be compared to 0. That looks odd, but BGE is comparing the value based on it being signed — instead of the byte representing 0-255, it represents -127 to 128, with the high bit set to indicate the value is negative. So, if the high bit is set, it’s a negative value, and the BGE would NOT branch. Fun.

If you run this BASIC program, it will BASICally do the same thing … just much slower:

100 X=&H3FF
110 X=X+1
120 A=PEEK(X)
130 IF A<128 GOTO 160
140 A=A+&H10:IF A>&HFF THEN A=A-&HFF
150 A=A OR &H80
160 POKE A,X:X=X+1
170 'Compare X to &H601
180 IF X<=&H601 GOTO 120
190 RETURN

So that is the magic to how this attract screen works so fast. It uses POKEd PRINT statements to quickly print the top and bottom of the screen, FOR/NEXT loops to print the sides, then this assembly routine to shift the colors and make them rotate around the screen.

And, this code is very similar to the routine I came up with earlier in this series:

start
    ldx #1024   X points to top left of 32-col screen
loop
    lda ,x+     load A with what X points to and inc X
    bpl skip    if not >128, skip
    adda #16    add 16, changing to next color
    ora #$80    make sure high gfx bit is set
    sta -1,x    save at X-1
skip
    cmpx #1536  compare X with last byte of screen
    bne loop    if not there, repeat
    sync        wait for screen sync
    rts         done

My routine starts with X at 1024, then uses BPL instead of BGE. I also increment X after I load the character, and I only update it if it gets modified by storing it 1 before where X is then.

At first, I thought mine was more clever. But I decided to see what LWASM said.

Counting cycles for fun and profit. Or just more speed.

LWASM can be made to display a listing of the compiled program and, optionally, list how many cycles each line will take. And, optionally optionally, keep a running total of cycles between places in the code you mark. (I have another article about how to use this.)

Here is my version, with (cycles) in parens, and running totals in the column next to it.

        start
(3)         ldx #1024
                loop
(5)     5           lda ,x+
(3)     8           bpl skip
(2)     10          adda #16
(2)     12          ora #$80
(5)     17          sta -1,x
                skip
(3)     20          cmpx #1536
(3)     23          bne loop
(4)         rts

My routine uses 23 cycles from “loop” to “bne loop”.

Here is the routine from WAR.BAS:

(3)         LDX #$03FF
(5)         LEAX $01,X
                L4005
(4)     4           LDA ,X 
(3)     7           BGE L400D
(2)     9           ADDA #$10
(2)     11          ORA #$80
                L400D
(5)     16          STA ,X+
(3)     19          CMPX #$0601
(3)     22          BLE L4005
(4)         RTS

This one appears to use one cycle less — 22 — in it’s loop. Nice! Even though I thought it would be worse, once again, James Garon appears to know his stuff.

I think his routine may be a bit larger, and I wondered why he started X one byte before the screen memory and then incremented it. That seems wasteful.

However, William “Lost Wizard” Astle saw exactly why in a reply on the CoCo mailing list:

He uses that sequence instead of the more obvious one to avoid having a NUL byte in the code. A NUL would cause the interpreter to think it’s the end of the line and break things.

– William Astle via CoCo mailing list

It took me a moment, but I think I understand now. If he had done “LDA #$4000”, the byte sequence would have been whatever byte is LDA, followed by $04 and $00. You can’t put a 0 in a string, or BASIC will think that is the end of the string. Any assembly encoded this way must avoid using a zero. This is also the reason he doesn’t compare to the byte past the end of the screen, which is $6000. Though, I expect comparing to 1535 and using “Branch if less than OR equal to” would have worked and avoided the zero.

But James Garon knows his stuff, so I had to see if my way was larger or slower:

(3)         cmpx #1535
(3)         ble loop
        
(3)         CMPX #$0601
(3)         BLE L4005

Well, they look the same speed, and neither generates a zero byte in the machine code. I don’t know why he does it that way.

Any thoughts?

BONUS!

If one were to patch the Color BASIC “UNCRUNCH” routine to show things between quotes, here is what those lines would look like… (And if one did such a patch, I expect they’d be writing a future article about it…)

Conclusion

For some reason, James Garon chose to embed assembly code and graphics characters like this, rather than using DATA statements and building strings or POKEing assembly bytes in to memory somewhere.

It’s cool to see. But unless someone knows James Garon, I guess we don’t know why this method was done.

Other than “because it’s cool,” which is always a good reason to do something when programming.

Until next time…

More CoCo MC6847 VDG chip “draw black” challenge responses.

See also: challenge, responses, and more responses.

Today Sebastian Tepper submitted a solution to the “draw black” challenge. He wrote:

I think this is much faster and avoids unnecessary SETs. Instruction 100 will do the POKE only once per character block.

– Sebastian Tepper, 7/5/2022

The routine he presented (seen in lines 100 and 101) looked like this:

10 CLS
20 FOR A=0 TO 31
30 X=A:Y=A:GOSUB 100
40 NEXT
50 GOTO 50
100 IF POINT(X,Y)<0 THEN POKE 1024+Y*16+X/2,143
101 RESET(X,Y):RETURN

It did see the criteria of the challenge, correctly drawing a diagonal line from (0,0) down to (31,31) on the screen. And, it was fast.

POINT() will return -1 if the location is not a graphics character. On the standard CLS screen, the screen is filled with character 96 — a space. (That’s the value you use to POKE to the screen, but when printing, it would be CHR$(32) instead.) His code would simply figure out which screen character contained the target pixel, and POKE it to 143 before setting the pixel.

So I immediately tried to break it. I wondered what would happen if it was setting two pixels next to each other in the same block. What would RESET do?

I added a few lines to the original test program so it drew the diagonal line in both directions PLUS draw a box (with no overlapping corners). My intent was to make it draw a horizontal line on an even pixel line, and odd pixel line, and the same for verticals. It looks like this (and the original article has been updated):

10 CLS
20 FOR A=0 TO 15

30 X=A:Y=A:GOSUB 100
31 X=15-A:Y=16+A:GOSUB 100

32 X=40+A:Y=7:GOSUB 100
33 X=40+A:Y=24:GOSUB 100

34 X=39:Y=8+A:GOSUB 100
35 X=56:Y=8+A:GOSUB 100

40 NEXT
50 GOTO 50

And this did break Sebastian’s routine… and he immediately fixed it:

100 IF POINT(X,Y)<0 THEN POKE 1024+INT(Y/2)*32+INT(X/2),143
101 RESET(X,Y):RETURN

I haven’t looked at what changed, but I see it calculates the character memory location by dividing Y by two (and making sure it’s an integer with no floating point decimals — so for 15 becomes 7 rather than 7.5), and then adds half of X. (Screen blocks are half as many as the SET/RESET pixels).

And it works. And it works well — all cases are satisfied.

And if that wasn’t enough, some optimizations came next:

And for maximum speed you could change line 100 from:

100 IF POINT(X,Y)<0 THEN POKE 1024+INT(Y/2)*32+INT(X/2),143

to:

100 IFPOINT(X,Y)<.THEN POKE&H400+INT(Y/2)*&H20+INT(X/2),&H8F

To time the difference, I added these extra lines:

15 TIMER=0

and:

45 PRINT TIMER

This lowers execution time from 188 to 163 timer units, i.e., down to 87% of the original time.

– Sebastian Tepper, 7/5/2022

Any time I see TIMER in the mix, I get giddy.

Spaces had been removed, 0 was changed to . (which BASIC will see a much faster-to-parse zero), and integer values were changed to base-16 hex values.

Also, in doing speed tests about the number format I verified that using hexadecimal numbers was more convenient only when the numbers in question have two or more digits.

– Sebastian Tepper, 7/5/2022

Awesome!

Perhaps final improvement could be to change the screen memory location from 1024/&H400 to a variable set to that value, the multiplication value of 32/&h20, as well as the 143/&H8F. Looking up a variable, if there are not too many of them in the list before the ones you’re looking up, can be even faster.

Using the timer value of 163 for our speed to beat, first I moved that extra space just to see if it mattered. No change.

Then I declared three new variables, and used DIM to put them in the order I wanted them (the A in the FOR/NEXT loop initially being the last):

11 DIM S,C,W,A
12 S=1024:W=32:C=143
...
100 IFPOINT(X,Y)<.THENPOKES+INT(Y/2)*W+INT(X/2),C
101 RESET(X,Y):RETURN

No change. I still got 163. So I moved A to the start. A is used more than any other variable, so maybe that will help:

11 DIM A,S,C,W

No change — still 163.

Are there any other optimizations we could try? Let us know in the comments.

Thank you for this contribution, Sebastian. I admire your attention to speed.

Until next time…