heckmeck!

Nerd content and
cringe since 1999

Alexander Grupe
Losso/ATW

July 2025

Blog

2025-06

Cool stuff to stumble upon, new and old.
What’s Cool? · What’s Cool? II · What’s Cool? III · What’s Cool? IV · What’s Cool? V

  • Fonts before Mac (1983)
    Font archeology on the Mac – learn why the original Macintosh system font was called “Elefont” and how it evolved, together with other early bitmap fonts for the Mac. Susan Kare’s pixel work is always worth contemplating in awe! Bonus: Through this article, I’ve learned what a Twiggy Mac is.
  • Those Secret Fonts from the ISA-16 PS/2 Models (Again)
    More font archeology, but in the IBM PC world and five years later: VileR traces back the origins of some extra ROM fonts in certain PS/2 models to an internal IBM software package. Fascinating detective work, leaving no stone pixel unturned!
  • Fredrik Liljegren on the creation of Pinball Dreams
    Fast forward four more years, and we’ve arrived in 1992 when The Silents set out to publish their first game, Pinball Dreams, and founded “Digital Illusions”, nowadays known as “DICE”. This interview sheds some light on how it all came to be: One guy drew a multi-screen pinball table and the others thought: Heh, interesting… Ah, the simpler times! :)
  • More retrocomputing, less nostalgia
    Speaking of simpler times: Is it really nostalgia when we still hand-pixel pretty pictures on the Amiga to this day, or compose SID tunes for the C64? Or is it more about the fun in exercising crafts you’ve learnt in your youth and simply still enjoy? I would agree that “real nostalgia” hits different; the other day, I was struck out of the blue with the feeling how incredibly cool it was in 1995 that my feeble Amiga could play along on the internet: browsing the web, sending e-mail around the planet, learning HTML from other websites, creating your own content for the whole world to see. It’s bittersweet how that level of excitement can never be brought back, but it’s also blissful and rewarding to keep using that ol’ box!
  • Yes, you can store data on a bird
    I’m sure this story is currently making the rounds everywhere in the geekosphere, but the headline is just golden. As is the tech, in hardware and biological form: a bird drawing a bird in the spectogram!

Nordlicht 2025 was a blast, and provided me with some much needed wellness time. There was a ton of great releases, lots of fun, and the new party location was awesome. Kudos to the organizers!

Train office on my way to Bremen

Luckily my latest 512 byte intro – Temba, seine Arme weit! – also made it to the podium, even with the tough competition in the 512 byte category. Phew! :)

A nice prize!

Rotozoom craze

The 4×4 copperchunky rotozoom Amiga competition is coming along nicely!

Back in 2016, Photon/Scoopex released Grade My Waterbear, a true-color rotozoomer intro, along with a bold claim:

This is the first and only maximum size, minimum bandwidth truecolor 4x4 OCS chunky mode. No-one will beat those specs, so.

If you’re unfamiliar with Amiga tech-tech, this is about doing image effects with the help of the Copper, the display coprocessor that can do things like “wait for screen position x,y and change the background color”. This way you can get around palette limitations (usually 32 colors at a time) and use all 4,096 colors of the Amiga – albeit in fat 4×4 pixels.

Of course, there are hundreds of variations of this technique, all with different technical aspects and limitations. That’s not important here, the main thing is: Photon’s oldschool bragging worked nicely, triggering countless efforts to one-up the effect under very specific constraints – even years after the fact. “Only 50 columns? Pathetic! I can do 52!” :)

The intros even come with helpful little scales nowadays!

Up until today, we’ve got all these gems related to the original 2016 production. And I’m pretty sure we haven’t seen the end of it. Isn’t that delightful?

Update: It goes on already! Days after this post, Batman Group came out with another rotozoomer (added below). Now the cropped image size I chose for the screenshots isn’t enough to display all the columns anymore, hehe… :)

ProductionWho and when

Grade My Waterbear

Scoopex
Feb 2016

YouTube

HSDV2

Damones
Mar 2016

This seems to merely have coincided with Photon’s release, but what a gorgeous HAM rotozoomer nevertheless!

YouTube

Grizzlybear

Damones
May 2016

YouTube

Water My Grey Beard

Loonies & Struts
Jan 2023

YouTube

Joker Snacks On Blueberries

Batman Group
Dec 2024

YouTube

Twelve Jokers In A Deck

Loonies & TTE
Jun 2025

YouTube

…And One Up The Sleeve

Lemon.
Jul 2025

YouTube

Still Snacking

Batman Group & Capsule
Jul 2025

YouTube

Anyhoo… The point of this post is not to announce that I will be taking part in this as well. Cycle-exact CPU writes? Emulator-breaking hardware abuse? Hell, no! :) With regards to rotozooming, I’m happy with my own little bootblock from 2016.

But: Speaking of emulator breaking (i. e. using tricks so advanced that they’re not properly emulated yet) – I was a little worried when I read that “…And One Up The Sleeve” requires the latest WinUAE release to render correctly. “Sigh, time to finally update Coppenheimer as well,” I thought.

But no! Turns out the rather dated vAmiga core used in Coppenheimer already emulated Hannibal’s tricks perfectly. So, in addition to compiling some pretty screenshots and links for any Amiga-inclined blog readers, I would like to praise Dirk W. Hoffmann for the stellar emulation effort with vAmiga, and also mithrendal for the vAmigaWeb project!

vAmiga-based Coppenheimer: Also one up the sleeve

One day a Coppenheimer update will come… I’m sure of it. :) I would really like to provide links to those intros with a running emulator instead of blurry YouTube recordings (but that’s more of a question of ROM copyright and web safety concerns).

As mentioned over at Bluesky, I’m currently on sick leave and taking the time to make myself more familiar with ZX0 compression – also in preparation of another 512 byte entry for Nordlicht, maybe. :)

Thankfully, ZX0 author Einar Saukas has provided reference implementations in several programming languages (C, Kotlin, Java), and with that, I was able to quickly peek what’s happening behind the scenes, gather diagnostic logs, etc.

Summary: I saved two bits for my use case, and two bytes for everyone!

Edit: The 68000 decompression routine referenced below is a port by Emmanuel Marty, not a reference implementation by Einar Saukas. Thanks to both of you for your work!

That’s a long end marker!

One thing that has bothered me was that my compressed data always ended with the bytes 0x5556 or 0xAAAB. Can’t this be shorter?

This has to do with how ZX0 marks the end of the data stream.

  • ZX0 has three modes:
    • Output the next n bytes literally (uncompressed)
    • Copy n bytes from last position
    • Copy n bytes from a specific position
  • ZX0 knows two flavors of bytes, so to speak:
    • Whole data bytes (uncompressed, direct data)
    • Bit-packed bytes (control bits and specially-encoded values)
    • Both are interspersed in the compressed data
    • ZX0 will try to cram as many bits as possible into each non-data byte; you can think of all non-data bytes together as a single stream of bits
  • Byte lenghts and offsets use a Elias gamma coding scheme. This way, common short values use fewer bits in the control data stream. For example:
    • 1 = 1
    • 2 = 011
    • 3 = 001
    • 4 = 01011
    • 5 = 01001
    • 6 = 00011
    • 7 = 00001
    • 8 = 0101011
    • etc.
  • The “copy n specific bytes” mode uses two values to calculate the start offset:
    • An Elias-coded value (variable bit length)
    • An unchanged, literal byte taken from the data (8 bits)
    • Combined into eliasValue << 8 | byteValue
  • When the “copy n specific bytes” mode encounters an Elias value of 256, this marks the end of the compressed stream. (The compressor outputs that value when it’s done.)
  • The value 256 is where the 0x5556 and 0xAAAB values come from.
    • 256 encodes to 01010101010101011
    • That’s 0xAAAB in hexadecimal
    • Or 0x15556 when shifted one bit to the left

Curious, I used the decompression code to trace what other Elias values are used in my data for the “copy from” position, in addition to the end marker. I only saw 5 distinct values being used:

Elias valueDecodedMeaning
11Copy n bytes from position 0x01..
0112Copy n bytes from position 0x02..
0013Copy n bytes from position 0x03..
010114Copy n bytes from position 0x04..
01010101010101011256End marker

So if my data only uses start positions up to 0x04.. – could I just use a smaller value for the end marker? Maybe 7, as it uses the same number of Elias bits as 4 and would leave some leeway.

  • 01010101010101011 = old end marker, 256
  • 00001 = new end marker, 7
  • 12 bits saved!

But I couldn’t just replace the new end-marker value in the 68000 decompression code. It doesn’t even use the value 256 directly, but rather checks if the byte portion ends up as 0x00 (the beq branch is taken, then):

.get_offset:   moveq #-2,d0         ; initialize value to $fe
               bsr.s .elias_loop    ; read high byte of match offset
               addq.b #1,d0         ; obtain negative offset high byte
               beq.s .done          ; exit if EOD marker

Bummer! I did, however, change the beq to a bge check (greater or equal than 0, signed). For this check to succeed, we only need an Elias value of 129 instead of 256:

End markerEffect of addq.b #1End detection
2560xFFFFFEFF becomes 0xFFFFFE00beq is true
1290xFFFFFF7E becomes 0xFFFFFF7Fbge is true

This limits the copy-from offsets to the 0x80.. range (acceptable for my data), but sadly, 129 is only 2 bits shorter when Elias-encoded.

  • 01010101010101011 = 256, using 17 bits
  • 010101010101001 = 129, using 15 bits

When the stars align right, we might chop off the last byte of our compressed data – in case it was only used for the lower Elias bits of 256 before. Not a huge win for all that hassle…

Needs more research: Maybe there’s another way of encoding the “end of stream” condition in 68000-assembly-friendly form. Especially when it can be be tailored and hard-coded to my specific data. For now, I’m happy with the 2 bits saved! :)

An unexpected save

The bigger surprise came when I was looking at the 68000 decompression code some more.

In the loop to copy literal bytes, it does this:

.literals:  bsr.s .get_elias   ; read number of literals to copy
            subq.l #1,d0       ; dbf will loop until d0 is -1, not 0
.copy_lits: move.b (a0)+,(a1)+ ; copy literal byte
            dbf d0,.copy_lits  ; loop for all literal byte

This gets assembled to:

.literals:  bsr.s .get_elias   ;  6136 
            subq.l #1,d0       ;  5380 
.copy_lits: move.b (a0)+,(a1)+ ;  12d8 
            dbf d0,.copy_lits  ;  51c8   fffc 

That dbf instruction is huge, requiring an extra word to encode the branching target (here: 0xFFFC = -4 signed).

Now, for short loops a dbf instruction can be replaced with a subq and a conditional branch, like this:

dbf codesubq version
      ;
      ; d0 = # of iterations
      ;
      subq.l #1,d0
.loop ;
      ; do stuff
      ;
      dbf d0,.loop ;  51c8   fffc 

      ;
      ; d0 is -1 now
      ;
      ;
      ; d0 = # of iterations
      ;
      subq.l #1,d0
.loop ;
      ; do stuff
      ;
      subq.w #1,d0 ;  5340 
      bge.b  .loop ;  6cfa 
      ;
      ; d0 is -1 now
      ;

Of course, that’s still the same size – both versions take up 4 bytes.

As noted in the decompressor code comment, a dbf will always loop until the counter register becomes -1. If we want 23 loop executions, we need to put 23-1 = 22 into register d0. (Hence the subq #1 before the loop.)

With subq and a branch, however, we can change the branch condition to end at zero. We’ll be getting 23 executions when d0 is 23, and don’t need to subtract 1 before the loop! Only difference: After the loop, d0 will contain 0, not -1.

Is it important if d0 ends up as 0 or -1? Let’s check what happens after the loop!

.literals:  bsr.s .get_elias   ; read number of literals to copy
            subq.l #1,d0       ; dbf will loop until d0 is -1, not 0
.copy_lits: move.b (a0)+,(a1)+ ; copy literal byte
            dbf d0,.copy_lits  ; loop for all literal bytes

            add.b d1,d1        ; read 'match or rep-match' bit      
            bcs.s .get_offset  ; if 1: read offset, if 0: rep-match 

The code either continues or jumps elsewhere. This is what happens in each case:

“Match or rep-match” = 0“Match or rep-match” = 1
             add.b d1,d1
             bcs.s .get_offset
             ;
             ; branch not taken
             ;
.rep_match:  bsr.s .get_elias
             ...
.get_elias:  moveq #1,d0 
             ...
             add.b d1,d1
             bcs.s .get_offset
             ;
             ; branch taken
             ;
.get_offset: moveq #-2,d0 
             ...


Turns out d0 will always be overwritten! Thus, we can rewrite the loop like so:

            subq.l #1,d0       ; 5380
.copy_lits: move.b (a0)+,(a1)+ ; 12d8
            dbf d0,.copy_lits  ; 51c8 fffc
            subq.w #1,d0       ; 5340
            bgt.s  .copy_lits  ; 6efa

Cool! We just removed unnecessary code from the decompression routine that has been sitting there the whole time! Everyone using unzx0_68000.S can save two whopping bytes now.

That can make quite the difference in a 512 byte intro! :)

I need to stop here for now, as I’m getting more excited than my doctor would recommend. Time for a nap, and let’s hope I didn’t overlook anything here!

Cool stuff to stumble upon, new and old.
What’s Cool? · What’s Cool? II · What’s Cool? III · What’s Cool? IV

  • Viewing images
    You cannot write an image viewer for the terminal without understanding everything about file formats and displaying images on a computer screen first. Right? Like, everything! A post full of side quests about pixels, compression, aliasing, image libraries, color spaces, and math. There’s a section about cursed mouse pointers, too! :)
  • My color cycling efforts at Datastorm
    Dalton/Tulou shows off and explains some of his color cycling Amiga pictures (i. e. animation by palette changes). These were all made for the Datastorm demo parties which had dedicated color cycling competitions, apparently – I must have missed that when I was there in 2014…
  • The REM-arkable Misadventures of LIST
    Another post about everything – here, it’s everything about Commodore BASIC’s un-tokenizer implementation on the Commodore PET: the “LIST” command. An earlier post dealt with the opposite direction, i. e. the tokenizer for parsing BASIC code. These articles examine quirks and bugs across different BASIC versions, with detailled explanations exactly why and how something goes wrong, down to the 6502 machine code.
  • Alternative Layout System
    What if all words in a paragraph have the same width? Why not cram in long words near the margin by bending them? Or reduce the text size of the last syllable? How about horizontally stretching the last word or letter? Turns out it has all been done, often in historic Hebrew and Arabic manuscripts, and it’s all demonstrated here (and in the book).
previous next close