Start

heckmeck!

Nerd content and
cringe since 1999
Alexander Grupe
Losso/AttentionWhore

hypotrain

A 512 byte Amiga intro for Nordlicht 2023, where it won 3rd place.

This year’s party theme was all about trains, and so I got the idea to pay hommage to a well-known Amiga intro with trains from 1989 by “covering” it with a mini version: “Subway” by Rebels (YouTube)

Original: 117,224 bytes
Demake: 512 bytes

To put the intro size of 512 bytes into perspective: a text-based picture with a train and buildings like in the intro already takes up 600 bytes.

  #                              ##      _                    
  #	  ####		       (o##	(_)	  ####        
 ###	  ####	####	####	####		  #### ### ###
 ###	  ####	####	####	####		  #### ### ###
##### ########	####	#######	####	#####	  #### ### ###
##### ########	####	#######	####	#####	  #### ### ###
##############################################################
____________________      __________________________      ____
)(__)(__)(__)(__) (_\    /_) (__)(__)(__)(__)(__) (_\    /_) (
_____________________)..(____________________________)..(_____
               ~°°~        ~°°~                ~°°~        ~°°

Now, just add colors, animations, music, some buildup, and a clean exit to AmigaDOS and you’re done… :)

Boring stats

Nordlicht’s 512 byte competition allows all platforms from C64 to Atari VCS and fantasy consoles. On the Amiga, the rule “Your entry must consist of a single file with a hard limit of 512 bytes” means you lose 36 bytes for file headers, so you are left with 476 usable bytes. This also rules out “half-bootblocks” where only the first 512 of 1,024 bytes are used (and you can use 500 bytes, or 502 if you re-use the rootblock field for data).

You can then either fill those bytes with hand-crafted code and data (see: circling by Rebels&Neural, Tide by k2 with a clean exit (!), Half a KB of crap by Compofillers), or use a packer like I did. Of course, then you need to make space for the decompression routine as well!

Numbers for this intro:

WhatSize
Code624 bytes
Data1030 bytes
Code and data compressed384 bytes
Decompress code92 bytes
OS overhead36 bytes

Yielding 36 + 92 + 384 = 512 bytes.

Compression

All this was achieved using ZX0 compression and salvador. I started out with Shrinkler and got good results with the parity contexts disabled, but I couldn’t get the decompression code smaller than 152 bytes. Shrinkler did compress better, but not enough to make up for the larger unpack code required.

Getting this far was quite a journey, trying out shortcuts and different data formats and regularily getting surprised by the compression behavior. For example, adding certain superfluous commands or using more space for the music encoding suddenly making the compressed data smaller, etc.

The usual fight against the packer included:

  • Reordering
    • Escape sequences: set output color before cursor position, or the other way around, etc.
    • ASCII and music data
    • Interchangeable code snippets
  • Variations
    • Music format: shifted, reversed, reduced number of bits per note. As usual with compressed data (by my experience), the compact approach is the worst, i.e. storing a timestamp and the period for each note, having one entry per note. Having one entry per row instead, with zeroes for unused rows, compresses better.
    • Fixed music length vs using an end marker (e.g. a negative value)
    • Activating un-needed audio channels (hoping that $8003 compresses better than $8001 when writing to DMACON, which it did, but only for a while…)
    • Layout of the city/buildings image: spaces vs tabs vs tabs-with-backspace
    • <CSI>7;51f instead of <CSI>7;51H to set the cursor position
    • <CSI>2m (faint color) which behaves like <CSI>32m (set color 2 explicitly)
    • <CSI>0m to reset colors and text style can be written as <CSI>m
    • Using move.w #x everyhwere even if some values allow for moveq #x (answer here: moveq performs better where applicable)
  • Repetition
    • Repeat whole code blocks instead of using subroutines
    • Repeated code blocks within repeated code blocks
    • Write out $dff0xx register accesses verbatim – address register relative offsets have worse compression. This means using move.w #x,$dff0xx everywhere instead of move.w #x,$xx(a6), with a6 loaded with $dff000 (or some shifted offset).
    • bchg #2,x to change gorilla visibility and to swap between left and right building
    • eor.b #$77,x to change the gorilla hand from ( to _ and to change the instruction itself from eor.b #$77 to eor.b #$00 (so it only has an effect every other frame, otherwise the lil’ guy is waving too frantically)
    • Music data is also used as sample data (luckily, the notes start with 71,0,0,71,0,0 which makes for a clean sample)

Tricks

Apart from the obvious “let’s use the console and escape sequences to draw everything” approach, the intro uses two tricks to save space and be compatible:

  • Reserve memory in Hunk header
    • The executable file’s hunk header reserves more memory than is required for the code section. This way, we get a reserved region of chip memory for free, where we can unpack our intro into!
    • Thanks to bifat for the hint!
  • BCPL calls
    • BCPL, the weird, ancient programming language of the old AmigaDOS kernel (or rather, TRIPOS)
    • But a compatible layer still exists in newer Kickstarts, allowing the old calling conventions
    • When your program starts, it already has a BCPL environment in registers a1 (frame pointer), a2 (global vector), a5 (call/bridge routine), a6 (return routine)
    • You have to jump through some hoops to actually call BCPL subroutines, but then you get a writeoutput call to write to the console without the need for an stdout/output handle or a valid dos.library base!
    • Thanks to platon42 for digging up a BCPL global vector table!

Result

In the end, I am quite happy that this intro managed to…

  • avoid using un-allocated memory (confession: some all earlier size-restrained productions of mine were using fixed memory addresses – tsk, tsk…),
  • cleanly return to the system on exit and even re-enable the cursor,
  • include some buildup progression (albeit primitive, but still: train pausing, music starting, gorilla appearing), and
  • be compatible with Kickstarts 1.2 through 3.1, via the magic of BCPL calls.

Sadly, text mode intros still look kind-of shit. :) But I learned a lot of new stuff for future size-coding endeavors!

Links

A custom version for newer Kickstarts and faster Amigas is included on the disk; it uses a different color scheme and includes delay calls, so it shouldn’t run in hyperspeed.

Choo-choo!

previous next close
eie