Sunday, 2 August 2026

Timebomb Part 1 - Speeding It Up

"Listen Willard, there's a kind of a little hootymajig thing in there... a wheel of some kind. Why don't you try turning the wheel, Willard?...

Oh, I don't know... turn it to the left and see what happens...

Yeh!...I can hear it, Willard, it's ticking a lot faster, isn't it?...

You'd better turn it back, Willard!"

Bob Newhart - Defusing a Bomb

This is a post I started about two years ago, and never got around to finishing it.

Now I have finally finished it.

It started after I went through a list of someones top 22 VIC20 games. There were a lot of odd choices on there, but one I quite liked was Timebomb.

I like maze games, and this was quite good, scrolling up and down a maze looking for a bomb.

If you don't find the bomb in time (about 80 seconds), it explodes, destroying the maze, so it has to build a new one so you can try again.

If you find the bomb in time, you restart the same maze, but each round you have 5 seconds less.

As the maze is the same, you should get increasingly fast at it, once you discount all the dead ends.

It decrements each round but always lets you have at least 15 seconds.

However, there were a few things I didn't like.

The main one was there was a full minute to wait whilst it was "Making (the) Maze".

It also takes another full minute to create a new maze if you didn't find the bomb in time.

At the top of the maze, it looked like you could see code on the screen

There was no on screen countdown to shown when the bomb was going to go off

There were no instructions to tell you what to do.

Although this was considered to be added to the Penultimate Cartridge, it didn't make the cut, mainly due to the glacial speed of the maze generation.

Later on, some space became free in the cartridge, before the next release, so I wondered if I could have a go at speeding up the maze generation, and maybe some of the other things and getting it into the cartridge.

I did some initial work and made good progress, but somewhere along the line I messed something up, and it was drawing the maze, but the movement was all wrong.

Ironically, the clock was ticking, and I ran out of time, and the bomb exploded.

This was put to one side and I moved onto finishing the cartridge and testing, testing and more testing of all the games within.


I went back once or twice to see if I could find what was wrong, but didn't see anything, so again it got put aside.


Back to today, and it's time for another refresh of the cartridge. I have managed to free up quite a bit of space, so let's finally have a proper look at Timebomb, and get it working.


The Original Version

I had a look around and found the original was actually a type-in game by Doug Smoak from Compute! magazine in July 1983.

It is a very short game, so any deficiencies are understandable.

There are no unnecessary spaces, it is very compressed.

To make it easier to follow, I added all the spaces in.

This was just to make the program more readable. It didn't actually work with all the spaces added to the program, the file size increased from 1843 bytes to 2574 bytes, which did not leave enough space in the unexpanded VIC20 to draw the maze.

Top Tip - PETCAT

PETCAT is a very handy program that is part of the VICE suite. It can be used to list PRG files as text, or turn text into PRG files.

petcat timebomb.prg > timebomb.bas

Will generate a text file with a listing of the program.

petcat -w2 -l 1001 -o timebomb.prg -- timebomb.bas

Will do the reverse and generate a PRG from the listing

-w2 = BASIC V2,
-l 1001 = Load address $1001 (unexpanded VIC)

I did considered rewriting it in C, but looking through the code it is mostly poke and loops, and the data statements contain some assembler routines.

So, I decided to write it all in assembler.

What could possibly go wrong.....

Actually, it wasn't that bad.

I worked through it line by line, starting with the spaced out line in comments and added assembler below to recreate the functionality.

Top Tip - Line Numbers

This program is a good example of speeding things up (a little) with careful use of line numbers.

The code is numbered from 2-42 (line 1 was probably a comment that got removed).

The line numbers themselves are stored as a two-byte value, so the actual number used does not, in itself, make a difference.

1 PRINT "HELLO"
10 PRINT "HELLO"
1000 PRINT "HELLO"

All take up the same space, as all the line numbers are two bytes.

However, when you have a GOTO or a GOSUB, the destination line number is stored as text characters, so the longer the line number, the more bytes it takes up in the file, and the longer it takes to parse.

2 GOTO 1
20 GOTO 10
2000 GOTO 1000

The GOTO 1 part is three bytes (the token for GOTO 1, the space and the character 1).

GOTO 10 is four bytes (GOTO, space, 1 and 0).

GOTO 1000 would be six bytes.

The space after the line number you get for free, that is not stored, it is always displayed in listings. The spaces within the line are stored as part of the program, so add bytes to the file and slow the parsing.

GOTO1 is even better (if less readable) at only two bytes.

This program also moves the least used functions (the initialisation) to line 19 onwards, so the main loop is at line 5, saving a few bytes in various GOTOs.

Development Environment

I had a question about this, so here is some more information on what I use for projects like this.

I use some ancient command line assemblers. These are the ones I have been using for years, they just work and do what I need, and I know how to work around any little issues they may have.

  • For 6502 I use ACME.
  • For Z80 I use TASM.

I run those on linux, ACME.EXE and TASM.EXE are DOS programs, so I use Wine to run those.

I am not a fan of Makefiles, they allways seem to make things overly complicated.

I use simple command line builds, sometimes in shell scrips.

All it usually takes is something like

wine TASM.EXE -t80 -b Lambda8300.asm Lambda8300.bin

or

wine ACME.exe timebomb.asm

or if you want a listing file

wine ACME.exe -r timebomb.lst timebomb.asm

To edit, I use various text editors.

I have been using Visual Studio recently, it is a nice editor which many useful features. But also some absolute howlers that I can't believe a dedicated code editor is so bad at.

I like the split views, being able to see multiple files at the same time, even safely edit the same file in multiple windows. That is very useful to have function in one window and the caller in another etc.

There is a powerful regular expression based search and replace that I used a lot for things like reformatting the Lambda 8300 ROM code.

e.g. sub_([0-9a-f]{4})h  => SUB_\U$1 

Would change all the labels like sub_da42h to they style of SUB_DA42

However, there are a couple of major issues I have.

Firstly, the menu keeps locking up so I can't open it. That is the only place there is a "save all" button. Luckily (and probably because they forgot to disable it) ctrl+S still works, but I have to do that regularly until the menu decides to start working again.

Autocomplete is very overzealous.

"Hey, I see you are typing a number. You have typed a number before, it was a different number, but I assume that must be the one you like. Let me change it to the other one for you."

No.

I want to type several different number, that's not unusual is it?

Also, I can't use things like LDA #255 for decimal numeric constants, which is annoying, I have to convert everything to hex LDA #$FF.

If I use # followed by any 3 digit number, even in an asm file, it thinks

"ah, that must be an HTML colour code. Let me add a little square in that colour to annoy you"

"oh, and if you click on it to try to get rid of it, I will change it to 225555"

What?

You just broke my code.

"Oh, and if you try to find the setting to disable it, I will fill you nice neat folders with more folders and XML files. And still not give you the option to disable it."

I ask you, what kind of person would let features like that through?

Who? Only a yoghurt.

Code Breakdown

The code is broken down as follows:

1 - missing, presumably a comment
2 - initialisation then gosub 29

29 - copy code to cassette buffer then return
30-36 data statements

3 - initialise some constants then goto 19

19 - initialise maze creation
20 - sys 861 then clear screen and print "MAKING MAZE"
21 - add spaces down the right (why?) and a line of spaces at the top
22 - 26 - create maze
27 - randomly place the timebomb
28 - sys 830 then set the start point then sys 923 and finally goto 4

4 - play the starting sounds

5 - main loop, if bomb found goto 40
6 - increment time, if timed out, goto 37
7 - ominous ticking sounds
8-14 - read joystick and move
15 - if you tried to move into a wall, go back where you were
16 - if you found the bomb, go back to 5 (which will goto 40)
17-18 - redraw the maze then go back to 5

37-39 - the bomb explodes, gosub 42 then run (full restart)

40-41 - bomb defused, reset for next round, gosub 42 then got 27

42 - print "ROUND r" and "PRESS F7" then wait for F7 and return

The values in the data statements in lines 30-36 are POKEd into RAM in the cassette buffer region, always a good place to store your assembler routines.

The functions within are called from the code, and are as follows:

SYS 830 - setup VIC and fill the colour memory
SYS 861 - fill maze with walls
SYS 887 - scroll screen up
SYS 905 - scroll screen down
SYS 923 - redraw screen

Oh, so all the data is code? There are no data statements for the graphics?

They are interesting, the red and blue gives a sort of "I should be wearing 3D glasses" type effect.

But it's normal PETSCII graphics and a clever trick I haven't seen used like that before.

The wall characters are all the inverse circle, character $B1, the inverse of character $51, as seen here in the font ROM.

; Character $51
.BYTE %00000000 ; ........
.BYTE %00111100 ; ..OOOO..
.BYTE %01111110 ; .OOOOOO.
.BYTE %01111110 ; .OOOOOO.
.BYTE %01111110 ; .OOOOOO.
.BYTE %01111110 ; .OOOOOO.
.BYTE %00111100 ; ..OOOO..
.BYTE %00000000 ; ........

If I set the colours of the first 8 characters to colours 0-7, you can see the circles.

What is happening here is colour 10 is being used. Any colour greater than 7 triggers the multicolour character mode. Here the 8 pixels of the character are used to generate 4 pixels, each of which can be one of four colours.

00 - uses the background colour
01 - uses the character colour
10 - uses the border colour
11 - uses the auxilliary colour (the top 4 bits of $900E)

So the middle lines of the font are split from

.BYTE %01111110 ; .OOOOOO.

to

.BYTE %01 11 11 10

The first pixel is 01, so uses the character colour, in this case set to red.

The second and third pixels are 11, so use the aux colour (light blue)

The final pixel is 10, so uses the border colour, blue

You can see that better if I add spaces and set the alternate squares to colours 8-15.

The time bomb looks more threatening in this mode, but it is just as asterisk.

The player characters are a little bit of a let down, V, <, > and ^, but adding UDGs would be tricky with the way the memory is arranged.

Top Tip - ON -(x) GOTO n

One little trick that is used a few times in the code is ON -(x) GOTO n, the function of which is not immediately obvious if you are not familiar with it.

ON x GOTO 10,20,30

Will GOTO 10 if x=1, 20 if x=2, 30 if x=3 or continue for any other values.

It will even continue along the line, so you can have code for the "else" case.

ON x GOTO 10,20,30 : PRINT "none of the above"

In the Timebomb code there are things like line 17

17 on -(me>7921) goto 18 : sys 887 : me = me+22 : goto 5

This is saying if "-(me>7921)" = 1 then goto 18 else do the rest of the line.

The conditional (me>7921) returns -1 if true, 0 if false, so the - sign outside the brackets converts that to +1.

This makes more sense to me if I rewrite it as an IF THEN ELSE statement

17 IF (me>7921) THEN goto 18 ELSE sys 887 : me = me+22 : goto 5

Note the -1 is no longer required as IF is only testing for non-zero.

Quite handy if you need to save space, or want to obfuscate your code, or both.

Numbers

There were a few interesting challenges with numbers.

The standard numbers in Commodore BASIC are floating point, so can happily go over 255.

Floating point numbers are a difficult to work with in assembler. Several of the variables will never go over 255, so I can replace those with single byte variables.

One thing I missed is that BASIC variable as initialised to zero, so I needed to add that to the start.

Top Tip - Always Leave Y=0

I have found it useful over the years to get into the habit of leaving the Y register in the 6502 set as 0.

That means I can initialise variables at the start

L1:

    ldy #0                  ; (leave Y as 0)
    sty OM_LSB              ; om = 0
    sty OM_MSB
    sty F                   ; f = 0
    sty C                   ; c = 0
    sty R_UNITS             ; r = 0
    sty R_TENS
    sty K_UNITS             ; k = 0
    sty K_TENS

And later on, anything that needs to be set to 0, I just use STY

; 41 poke v-1, 0 : f = 0 : k = x : ...
L41:
    sty VIC_NOISE_FREQ      ; v-1 = 0
    sty F                   ; f = 0
    lda X_TENS              ; k = x, start K counter at X
    sta K_TENS
    sty K_UNITS             ; X_UNITS is always 0

A handy 6502 feature is being able to use two sequential bytes in zero page as a pointer, however the only addressing mode which supports it needs X or Y.

lda (ZP_PTR),y

That loads the accumulator with the value read from address make up from the values stored at those two bytes and Y, (ZP_PTR) + 256*(ZP_PTR+1) + Y.

Quite often I just want the value at that address, so I need Y to be 0 for those accesses, another reason to leave Y set to 0.

Some of the number are addresses of things like the VIC chip and the VIA, which make the BASIC program smaller, but in assembler, it is easier to just hard code the values (since they do not change).

3 d = 37154 : p1 = d-3 : p2 = d-2
8 poke d, 127 : p = peek(p2)

Can be replaced with the hard coded values

lda #$7F
sta $9122
lda $9120

Or, it is better to used named constants for things like that.

VIA1_PORTA = $911F ; p1
VIA2_PORTB = $9120 ; p2
VIA2_DDRB = $9122 ; d

lda #$7F ; set joystick 3 as input
sta VIA2_DDRB
lda VIA2_PORTB ; read the port

That makes it easier to follow.

There are some other numeric variables, which are used as counters which count to numbers in the hundreds, so cannot be stored in one byte.

Normally you would store these as a 16 bit value in two bytes, but any arithmetic involving 16-bit numbers is complicated as there are no 16-bit arithmetic instructions in the 6502. Any time you need to do any, it requires separate chunks of code to work on the LSB, and if required, the MSB.

Even comparison between two 16-bit numbers is complicated, and I think this is where I messed up on the first version of this code.

Top Tip - Comparing 16 bit numbers

I will start with comparing two 8 bit values.

    LDA val1
    CMP val2

The zero flag is set if val1 = val2

The carry flag is set if val1 >= val2

So you can test with

    BEQ numbers_are_equal
    BCS val1_larger_or_equal
    BCC val2_larger

With a 16 bit number, you compare the MSB first. If they are the same, then compare the LSB instead.

    LDA val1_msb
    CMD val2_msb
    BNE go_compare    ; MSBs are different, so compare those
    ; MSBs are the same, so compare LSB
    LDA val1_lsb
    CMD val2_lsb
go_compare:
    BEQ numbers_are_equal
    BCS val1_larger_or_equal
    BCC val2_larger

To avoid that, I like to use a shortcut which saves some unnecessary multiplication and division (which is tricky enough with 8-bit numbers if it's not a power of 2 that you can relace with a shift operation).

Top Tip - Tens and Units

With several of these variables, e.g. K, the clocK, it counts from 0-600 at which point the bomb goes off.

If this was stored as a 16-bit value, to check if it has reached 600, I would need to load and check both bytes to see if they equaled $02 and $58.

Instead, I store the values as two bytes, X_TENS and X_UNITS. I increment X_UNITS and check to see if it has gone past 9. If it has, I set it to 0 (Y = 0) and increment X_TENS.

inc K_UNITS
lda K_UNITS
cmp #10
bcc +
sty K_UNITS
inc K_TENS
+

When it comes to test for > 600, I only need to test the X_TENS value in a single byte comparison, is it over 60.

lda K_TENS
cmp #60
bcc L7

Another issue I hit a few times was adding an 8 bit signed number to a 16 bit number. There were a lot of "add 22 to the position to move down 1 row" or "subtract 22 to the position to move up 1 row".

Top Tip - Adding a signed 8 bit value to a 16 bit number

Something like me = me+c looks simple, but when me is a 16-bit location and c could be positive or negative, it took a bit of thought.

Positive numbers are relatively easy, you add the number to the LSB, and check if there is a carry. If there is, you increment the MSB.

lda C
clc
adc ME_LSB
sta ME_LSB
bcc +
inc ME_MSB
+

Note you need to clear the carry before you start as the 6502's only addition instruction is adc (add with carry).

(and unlike other 8-bit processors I could name, the 6502 has two nice simple instructions to set and clear the carry flag. None of this "one instruction sets the carry flag, the other one inverts the carry flag")

Negative numbers are stored in two's compliment, so -1 is actually $100-$01 = $FF.

The range goes from -128 to + 127. In this case, C could be -22, which is stored as $EA.

Rather than subtracting $16 (which is 22 in hex), you add $EA (-22).

So $40 - $16 is implemented as $40 + $EA. The result is $012A, but if this is an 8-bit number, the carry is ignored, so you get the result $2A.

When looking at 16-bit values, you end up with an answer which is $0100 too high.

If $2040 - $16 is implemented as $2040 + $EA, the result is $212A, which is wrong.

You need to do $2040 + $EA - $0100, and you get $202A.

In practice, you decrememnt the MSB before you add the two's compliment of the number you want to subtract.

Yes, my head hurts too.

lda C
bpl +
dec ME_MSB
+
clc
adc ME_LSB
sta ME_LSB
bcc +
inc ME_MSB
+

Here the bpl + skips over the decrement of the MSB if the value is positive. The remainder is an addition, which is the same for positive or negative numbers.

There was also an array of directions in the BASIC program.

If the array is very small, there is a trick you can use to simplify things.

Top Tip - Simple Arrays

A small array was setup as part of line 19

; 19 dim a(3) : a(0) = 2 : a(1) = -44 : a(2) = -2 : a(3) = 44

A simple way to store that is to define some space in zero page for the 4 elements

; a = 2, -44, -2, 44
lda #2
sta ARRAY
lda #-44
sta ARRAY+1
lda #-2
sta ARRAY+2
lda #44
sta ARRAY+3

Then when it is used, you can use zero paged X-indexed addressing to read the value from the array.

; 23 b = a9 + a(j)
ldx J
lda ARRAY,x

However, another place it was used then divided by 2.

; poke a9 + a(j)/2, hl

My brain was already melted, and I think I could have handled the division by a rotate right, but it would need to decide to set carry and then doing a rotate right for a negative number, or a logical shift right if it was positive. It seemed like that would be a lot of extra steps for each access.

I decided to take a different approach.

Top Tip - Avoid Doing the Maths

Instead of doing the divide by 2 each time, I created a second array.

This contained pre-calculated values that were half of those in the normal array.

lda #1
sta ARRAY_HALF
lda #-22
sta ARRAY_HALF+1
lda #-1
sta ARRAY_HALF+2
lda #22
sta ARRAY_HALF+3

I could then access those immediately using

lda ARRAY_HALF,x

The same thing happened in a few FOR loops, where the itterator was always divided or multiplied.

For example, part of line 4

for tt = 0 to 30 : poke v, tt/2 : next

I instead implemented that as

for tt= 0 to 15 : poke v, tt : next

It does the same thing, but faster, so it needs a longer delay.

Top Tip - Simple Delays

"you've got to speed it up, and then you've got to slow it down"

As assembler is a lot faster than BASIC, with FOR loops like this used to create sounds, I needed to add quite a lot of delay loops to match the speed of the BASIC.

My main delay loop of choice was a simple 256 cycle delay using the Y register.

-   dey
    bne -

Y is always left as 0 (see above), so the first DEY takes it to $FF, that is not zero, so it loop. $FE is not zero, loop again, down to $00. That is zero, so it exits, and leaves Y as zero as it should be.

Where I needed more of those, I involved the X register as well.

    ldx #$04
-   dey
    bne -
    dex
    bne -

This loops around the 256 cycle loop 4 times.

What more can I tell you?

Well, in a few places a needed a random number.

Top Tip - Random Numbers

When I first looked at converting this program, George Beckett had recently been discussing Random Number Generators in out Patreon discord (hint, join the Patreon if you want to join the discord, a great place to find out about project updates and behind the scenes stuff, and discuss your own projects).

He found a routine on a page that has since disappeared, but was thanks to the wonderful archive.org "wayback machine", it is still available.

(Incidentally, that is an incredibly useful resource to have when you follow a link to a webpage that isn't there anymore. They need donations to keep going, so if you find it useful, and you are able to do so, please give them something.)

From that page:

"Xorshift is a fast pseudorandom generator algorithm originally developed by George Marsaglia. John Metcalf found a 16-bit version of the algorithm that is fast on 8-bit platforms with only single bit shifts available. It has a period of 65535 and passes reasonable tests for randomness. His pseudocode is reprinted here:

/* 16-bit xorshift PRNG */
unsigned x = 1;
unsigned xorshift( )
{
    x ^= x << 7;
    x ^= x >> 9;
    x ^= x << 8;
    return x;
}

The page then presented a version for the Commodore 64.

George used it on an assembler version of a 10 PRINT maze for the Commodore PET.

I have used that same code here.

RAND16:
    lda SEED_MSB
    lsr
    lda SEED_LSB
    ror
    eor SEED_MSB
    sta SEED_MSB
    ror
    eor SEED_LSB
    sta SEED_LSB
    eor SEED_MSB
    sta SEED_MSB
    rts

It is seeded at the start of the program from the VIA1 timer, which is set counting at boot.

start:
    lda VIA1_TIMER2_LSB
    sta SEED_LSB
    lda VIA1_TIMER2_MSB
    sta SEED_MSB

When I started this process, I followed the path of the code, starting with the maze generation, and then movement and redraw (which is where I got the 16-bit comparison wrong).

Once I fixed the movement, I could progress to the "you won" and "you lost" stages.

There isn't much displayed on the screen, both cases display the same:

The number of the current round needs to be displayed, as part of that.

Top Tip - Displaying Simple Numbers

If you have a number from 0-9 to display, simply add $30 to it and store it in screen RAM.

    lda R
    clc
    adc #$30     ; A = R+$30  (0->"0", 1->"1" etc.)
    sta ROUND_DIGIT_1

That works fine until you hit 10.

Here I again split into R_TENS and R_UNITS, to make it easier to display. If R_TENS was 0, then just display R_UNITS, if not, display R_TENS and then R_UNITS.

    lda R_TENS
    beq L42_1
    ; double digit
    clc
    adc #$30
    sta ROUND_DIGIT_1
    lda R_UNITS
    clc
    adc #$30
    sta ROUND_DIGIT_2
    bne L42_2              ; branch always
L42_1:
    ; single digit
    lda R_UNITS
    clc
    adc #$30
    sta ROUND_DIGIT_1

That could probably be optimised a bit, but it only runs one at the end of each round, so it's not that critical.

The next step is to wait for F7 to be pressed. I was considering changing this to "press fire", as it is easier to check the joystick port, but when the keyboard is only being used for this, you can take a shortcut and use a very simple technique.

Top Tip - Simple "Press a Key"

There is a simple way to just check for a single key press, or a specific key.

$C5 in zero page holds the last key pressed. This is updated by the interrupt routine as it scans the keyboard.

If no key is pressed, it will return $64.

-
    lda LAST_KEY        ; check last key pressed
    cmp #NO_KEY         ; is a key pressed?
    beq -               ; no, keep checking

Here I want to check for F7, which is $63.

-
    lda LAST_KEY        ; check last key pressed
    cmp #KEY_F7         ; was it F7?
    bne -               ; no, keep checking

You might want to write your own keyboard routines if you are using the keyboard for directional movement, but it's fine for "press the any key".

With all that done, there was a lot of tweaking of delays to make the sounds right, and the loop timing.

The game has a clock, K, which counts up to 600. I am not sure if that is meant to represent 60 seconds, but in practice on a PAL VIC20, the original game gave you about 80 seconds.

I think I got the sounds and explosion effect reasonably close, and the ominous ticking sound.

The only real difference to the timing is the maze generation.

On the BASIC version, that takes about 55 seconds.

On the assembler version, it is pretty much instantaneous, so much so that I didn't even bother putting up the "making maze" screen as you would barely see it.

I found the same thing when I converted 3D Monster Maze, rather than a minute watching "the mists of time", I had to add a dissolve effect to make it look like something had happened.

Once all that was sorted out, I had a final pass through and was able to optimise out X_UNITS, as this was always zero (X was incremented by 50 each time).

I also rewrote the original assembler routines, with I think shorter and faster ones (he said with the arrogance of a programmer with a long grey beard who has been doing this for a long time).

e.g. part of SYS 830

This fills the colour RAM from $9600 to $97FF with $0A (the colour light red)

I don't think it is beneficial to use zero page here when the destination is always going to be fixed.

    lda #$00      ; destination 9600
    sta $FB
    lda #$96
    sta $FC

    ldy #$00
L034D:
    lda #$0A      ; fill with 0A
L034F:
    sta ($FB),y
    iny
    bne L034F

    inc $FC       ; keep going until 9800
    lda $FC

    cmp #$98

    bne L034D

I went with a simpler approach

    lda #COLOUR_LIGHTRED     ; all text colour to light red

-   sta COLOUR_RAM_PAGE0,y
    sta COLOUR_RAM_PAGE1,y
    iny

    bne -

I also inline that function into line 28 to save a JSR and RTS.

Since line numbers no longer affect speed, I also shuffled things around so that the maze generation was at the start, where it logically comes in the sequence.

Other Things to Consider

The bottom of the maze is neatly hidden, the maze if stored in RAM right below the screen RAM, so when it hits the bottom of the maze, it continues copying the top of the screen.

You can see the top half is repeated below the solid line.

The top of the maze is not handled this way, instead you get a view of the BASIC heap I think.

I did consider tidying that up, but I haven't.*

I also considered a visible timer, but I think the ominous ticking of the clock is fine.

I did think about adding a title screen, even instructions, but I don't think it needs it.

I will call it done for now that.

I have also uploaded the source to github, with the original BASIC, spaced out BASIC and assembler versions.

There is also a PRG file if you want to play it on a real VIC or an emulator - remember to set remove or disable any memory expansions, or set the memory on the emulator to none (not 0, that is very different).

xvic -memory none timebomb.prg

The original was BASIC version was 1843 bytes.

The final tally of the assembler rewrite is 1067 bytes. It seems like I should try to get it down to below 1024 bytes, but I won't.*

* Part 2

OK, I did tidy things up a bit and fixed a few bugs (which were present in the original) and tried to get it under 1K.

I also wrote a version for the PET. That is all to follow in Part 2 next week.


All items are still available, and I can ship worldwide.

International shipping is a bit complicated at the moment, so if you want to order from outside the UK, please see this page for more information:

Sorry to have to do this, and I know this is going to affect sales, but please understand it is the only option I have right now.

Patreon

If you enjoy posts like this, you can support me via Patreon, and get access to advance previews of blog posts, and exclusive posts.

You also get progress updates on new projects and other behind the scenes updates, as well as access to my Patreon only Discord server for even more regular updates, and to discuss your own projects.

I currently have 50% off your fist month if you want to try it out.

Sunday, 26 July 2026

Game over?

TLDR;

International shipping from the UK is now even more complicated. If you want to order from outside of the UK, please use the contact button above and tell me what you want to order (including any options) and your address, and I will get a price for you. This price will now include duties that used to be collected by customs, so should work out overall faster and cheaper.

If you want to know why, read on, otherwise give this one a miss, it's too depressing.

Come back next week when there is a nice post about writing a VIC 20 game.

Or there is 50% of my Patreon if you want to read it now.

Good News Everyone

Thank you Royal Mail for sending me this great news.

Yes, more of my parcels can now be sent duty paid.

Great news...?

Hang on, how does that work again?

How it used to Work

The customer selects a product and selects some options and hits checkout.

The postage costs are pre-set based on size and weight of the parcel and destination.

The customer pays for the item, say £100, and the postage, say £15, so £115 gets paid to Tindie or whatever marketplace is being used.

I pack the order, pay the £15 for the shipping and off the order goes.

In many countries, the order will arrive, customs will inspect the paperwork and decided how much duty, if any, is due and ask the buyer to pay the duty, say 20%, plus a handling fee, say a total of £25.

In the past, the US has a high point of entry for duties, so most orders went through without needing any duties paid. That is no longer the case.

The EU also had different rules for items under €150, that has also now changed, so everything is going to need duties paid.

Once I mark the order as shipped, the total the buyer paid, minus the platform fees and payment processing fees will be payable to me. So I get about £100 of that £115.

As the seller, I need to pay for the parts to make the order, and pay for the postage in advance, and I then get reimbursed some time later.

That used to be almost immediately, but now there may be delays of several weeks, so I need to have enough float in my account to pay for all the parts and postage until I get reimbursed.

That is manageable, with a bit of planning, even with the extra delays.


New Rules

Good news says the Royal Mail in a series of messages.

Now what needs to happen is I need to pay the duty as part of the postage costs to many destinations including the US and parts of the EU.

Previously there used to be a drop down box that let me select "sender pays the duty" or "recipient pays the duty".

That is now greyed out, and is fixed as "I will pay any taxes, duties & fees (PDDP)" - Postal Delivery Duties Paid.

The amount varies, but 20% is common in Europe, the US now looks to be 25%. So I now need to pay the £15 shipping, plus 20-25% of the item value, in this case, £25. Plus handling fees, only 50p this time.

So now I need to pay £40 to ship the item.

In theory now there should be nothing extra to pay at customs, so the buyer should receive their item faster (although I understand there may be state taxes on top of this in some states).

That was the US, lets try Germany.

That is also on a £100 item, looks like 20% of item and postage costs? so £35 to ship a £100 item to Germany.

(there is also a new "temporary" EU rule that adds €3 for every type of item in an order, I have yet to see that being charged as part of the postage. It is far more of an issue for low value, multi-item orders, most of mine are high value, small number of items. I have heard that they may still be collecting this by sending cards from customs, which makes a mockery of enforcing collecting the duty from the sender if they are still going to card the recipient.)

That's great. So now I can't just pay the shipping, I need to deal with the duty as well.

However, there is a problem.

How do I get that 20-25% of the item cost from the buyer?

Why is that now my job anyway?

Sticks on Revolver, track 1, "Yeah, I'm the tax man"

Option 1 - Increase the item price

One suggestion is to increase the price of the item to cover this, so reprice the £100 item at £120. That would cover the cost, but now the order paperwork would say the item cost is £120, so the amount due would now be 20% of that, £24. So it would need to be even higher, £125 so that the duty would be £25 and I would get the £100 for the item, but the paperwork would be all wrong, and the buyer would have paid a bit more than they need. It also makes things look less appealing if they are 25% more expensive.

Option 2 - Add it to the postage

This is what I was initially doing for the 10% US tariff. I setup a number of postage rates, each inflated by enough to cover about 10% of the item cost. That sort of worked, but just made the postage look very expensive and if the customer buys several things, I lose money on the shipping.

Now I need to look at 20-25%, that's not really an option.

I can't even do it per item, as something like the Minstrel 2 can very from £130 -£230 depending on the options, built kit, with sockets, with membranes and overlays etc.

So that would mean between £26 and £46 duty at 20%.

Option 3 - Get the Marketplace to collect it

That would be nice, but currently it looks like only Lectronz has that option, but I have not seen how that works in practice yet.

I would also need to go through and work out the amount of duty for each country or state and setup separate shipping rates for each one. An awful lot of admin, and these things keep changing, so I would need to keep on top of that or risk over- or under-charging.

Option 4 - "Gift, value $1"

This is how all the far east marketplaces used to get around this, and the sort of thing they are trying to fix with these new rules. I only ever ship with everything fully declared. I am not interested in trying to cheat the system, no matter how broken, unfair or unworkable it is.


None of this shows on the postal labels, or the CN22 customs form, other than a small PDDP logo, so it doesn't even look like anything has been paid. Great work guys, thank you again. Makes my products look more expensive and then make it look like I am ripping people off.

Even if I get all that working, the other problem is one of finance. I now need to buy the parts to make the product, and then pay the postage say £15, plus the 20-25% duty and the fees, all in advance out of my own pocket.

I will need a lot more of a float if I am paying out a quarter of the order value on top of everything else, and then only getting that back a few weeks later.

I have no idea how that will work in the case of a refund, do I simply lose the 25% I paid out as well as the postage?


The New Plan

All I can suggest is that I will have to calculate international shipping on a case by case basis.

After I lost quite a bit of money paying the duty on the outstanding orders I had to post, I have set the stores to UK only for now. I hate to have to do that, but I don't think it is going to be possible to have this automatically calculated, and I certainly cannot afford to give away 25% of any more orders.

Please look at the blog posts and the store listings, work out what you want, and what options you want (that is important), and tell me that and your shipping address.

I will work out what the cost of shipping will be and give you an accurate overall price (which should work out cheaper than having to pay part of that later at customs).

I can then send a fully itemised PayPal invoice with all the details on.

I am sorry that sounds really clunky, but that is the only way I can see to make this work.

I also realise that makes everything look more expensive and more trouble, but I hope you will be prepared to work with me to get things too you without either of us losing too much money.

Sorry of that was all a bit ranty, I was quite positive in last weeks post, looking at listing more things and more widely, but now all the changes have kicked in, I am going to have to rein it all in again.

Thank you all for your past, and hopefully future, custom.

Ordering

All items are still available.

I can ship worldwide.

The only problem is I can't get the shipping and duty to be calculated automatically.

If you want to order something, please use the contact link at the top of the page and tell me:

  1. What you want to order, including options - see blog posts and listings for reference
  2. The delivery address

I will then send you an invoice which you can pay via PayPal, or if in the UK, by bank transfer.

Sorry to have to do this, and I know this is going to affect sales, but please understand it is the only option I have right now.

Patreon

If you enjoy posts like the ones I normally write, you can support me via Patreon, and get access to advance previews of blog posts, and exclusive posts.

You also get progress updates on new projects and other behind the scenes updates, as well as access to my Patreon only Discord server for even more regular updates, and to discuss your own projects.

I am very grateful to my existing patrons, I need this even more if I am going to be losing out on orders.

It would be wonderful to get enough support on Patreon to allow me to concentrate on writing more and better posts rather than spending all my time dealing with shipping admin.

I have set this to 50% off your fist month if you want to try it out.

Sunday, 19 July 2026

PET ROM/RAM Board Updates

Update

International shipping from the UK is now even more complicated. If you want to order from outside of the UK, please use the contact button above and tell me what you want to order (including any options) and your address, and I will get a price for you. This price will now include duties that used to be collected by customs, so should work out overall faster and cheaper.

Some updates to the PET ROM/RAM board, and more importantly, how and where I sell them.

Overview

Just a quick overview for anyone not familiar. This is a board which plugs into the 6502 socket of a PET 2001/2001N/30xx/40xx or CBM 40xx/8032. The 6502 then plugs into this board.

It bypasses onboard the ROM and/or RAM and replaces them with 32K of RAM and selectable ROM with BASIC 1, 2 or the most preferable and latest BASIC 4.0.

Once fitted, you can remove the onboard main ROM and RAM chips, it saves power and preserves any that are still working. Only the video RAM and character ROM need to remain (although I have replacements for both of those).

For most applications, the 5 switch version is the best option. The 9 switch version has additional control of which ROMs and RAMs are replaced, and an LED that flashes for writes. Those can be useful for troubleshooting problems.

In most cases, go for the 5 switch version.

Board Changes

There isn't much to spot between the old and new versions.

The changes to the boards are mostly aesthetics.

The square corners are now rounded.

I have also moved the resistor arrays as they used to stick out to the side of the DIP switches.

I have moved them to be level with the DIP switches.

Makes no difference in practical terms, but makes me happier.

That is the 9 switch version, which has been similarly updated. The 9 switch has also been modified to support the 39SF020A EEPROM chips (the 5 switch already did). This was about a week before they announced they were discontinuing the 39SF020A. Oh well.

Purchasing Options

My sales are down significantly in recent months, I am sure a lot of that is due to the state of the world right now, but just in case it is, in some way, down to Tindie changes, I have been looking at other options.

Pricing is slightly different between these sites, due to currency conversions, some have the base price set in USD, others in GBP. The price has gone up slightly as one of the parts has been discontinued, one now costs five times what it used to, and one seems to be permanently out of stock everywhere.

Tindie

I sell on Tindie

Tindie is now under new ownership. It has been, shall we say, a rather turbulent transition.

Things are starting to get back together, but I can understand some people now can't (or don't want to) use Tindie.

The new owners have not been able to come to an agreement with PayPal, and so that is no longer available for buyers or sellers. They have moved to Stripe payments. I hadn't used that before, and had to set that up (and ended up having to buy a new tablet as I didn't have anything new enough to run the app required)

It is less than ideal for me as there is now several weeks delay between shipping orders and getting paid, which does not help with the cash flow situation (which is pretty dire at the moment).

Lectronz

I did look at this a while ago, but I wasn't able to complete the setup as I couldn't setup Stripe payments at the time.

Since I now have had to setup Stripe anyway, I have also setup a Lectronz store.

I have only listed the PET ROM/RAM to start with, I will see how that goes and will add more products over time.

Lectronz has a much more sophisticated and configurable postage system. That seems very good.

There is a handy method to import Tindie listings directly. They clearly know that is where most of their sellers are migrating from.

With new or updated listings, the photo upload is a little clunky with one at a time file selection, rather than selecting multiple files. They also have the same limit of 12 pictures and recommended ratio of 3:2 like Tindie.

Their BluSky post notifications seem better.

The Tindie one is pretty pointless. No picture, the link is hidden, no link to seller.

Did you know I released a special reissue of the Penultimate Cartridge? I haven't sold any, so maybe you didn't

SmallRun

Find Tynemouth on SmallRun

This is a relatively new site, so I am interested to see how it works out. It has a some nice features like being able to tie photos to options, so that when you select the 9 switch version, the main picture changes to the 9 switch board.

There is also a very different way of doing postage, the buyer get shown a range of shipping options from various couriers, calculated based on weight and size of the order from a site called Shippo. It seems to be a US equivalent of sites like ParcelMonkey and Parcel2Go etc.

The buyer picks the one they want and pays for that and the seller gets a shipping label ready to attach to the parcel. That's quite a neat idea. Not sure it works for me, as they don't include Royal Mail, which is my preferred option, but I understand they are working to add that.

For the moment, I have gone for the plain flat price shipping as I have on the other sites.

This is actively being developed, and I have been feeding back a few issues and suggestions and they seem very responsive. It will be interesting to follow their progress.

SellMyRetro

I have reactivated the PET ROM/RAM board listings on SellMyRetro.

This is limited to UK only as I can't get on with the way they deal with international shipping.

Order Direct

Finally, you can always order directly from me.

Contact Me and answer these two questions:

  1. What items do you want? including the options, see the listings and blog posts for details
  2. Where do you want the items shipped to? I need the country to work out the postage

Almost everyone who does this misses both of these questions, and I just get "I want to buy a Minstrel" or "How much is a Mini PET" etc.

Buy Tynemouth Products

To avoid having to put all of that at the end of each post, I have setup a sort of store page.

There will be a page linked to that for each product group, I have only done the first one so far

The rest will follow as I add more things, if you want to buy a specific item from a specific place, I am sure I can make that happen.

Too Much?

Is this too much, too complicated, too much choice, I don't know, maybe, I am just trying to give as many options as possible to try to make sales happen.


Adverts

See pretty much the entirety of the above post.


Patreon

If you enjoy posts like these, you can support me via Patreon, and get access to advance previews of blog posts (except when I am running behind, like last week, sorry), and exclusive posts.

You also get progress updates on new projects and other behind the scenes updates, as well as access to my Patreon only Discord server for even more regular updates, and to discuss your own projects.