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)
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:
What | Size |
---|---|
Code | 624 bytes |
Data | 1030 bytes |
Code and data compressed | 384 bytes |
Decompress code | 92 bytes |
OS overhead | 36 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 formoveq #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 usingmove.w #x,$dff0xx
everywhere instead ofmove.w #x,$xx(a6)
, witha6
loaded with$dff000
(or some shifted offset). bchg #2,x
to change gorilla visibility and to swap between left and right buildingeor.b #$77,x
to change the gorilla hand from(
to_
and to change the instruction itself fromeor.b #$77
toeor.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:
someall 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!
Demo Scene
- Halle-MOO-jah
- sinéad
- Back to the Future!
- Brutalist Love
- hypotrain
- Conspiracy’s Weird Disk
- Teletext on Amiga
- Blood Sugar Rises
- Arrr512
- More…
Blog
- Unit Arrr
- I just favican’t
- I consider myself more of a Renaissance man
- Zerosphere on Analogue Pocket
- Amiga Topaz 1.4, part 2
- Masto-DDoS
- Elementary, dear Teletext
- Amiga Topaz 1.4
- “UI” as in “utterly idiotic”
- new art: final and write-up
- More…