How does one make a demo with someone that has almost the opposite tastes as yourself?
It was around February 2018 that yet another edition of Sommarhack demo party was officially announced. As it has been the tradition for a few years now, apart from the main demo competition a different category was announced: “The twist compo!”. The rules for that compo were:
- The demo must feature a twist
- Must work on 512k RAM
- Must work on Atari ST
- Several entries per person or crew is allowed
Definitions
Before we go further, let’s establish what a “twister” is. The best thing to do is to show the effect, so here’s a single frame from Titan’s “Overdrive” demo:
It’s a quite old effect as far as demos go but it’s easy to do and it looks nice. So it’s been used in pretty much all platforms in one form or the other.
All right, here we go then
So let’s go back to the first rule of the competition. “The demo must feature a twist”. Doesn’t that sound a bit ambiguous? “Twister” would have been more precise, but “twist”? That can lead to misinterpretations. The first thought that occurred to GGN when he read that rule is the old Chubby Checker hit “Let’s Twist Again”. So really a very early idea was to simply convert that tune as a sample and play it on the ST - voila, compliant with all the rules!
Or how about a video of people dancing the twist?
Details are a bit sketchy but these is the gist of some conversations GGN had with XiA that led him to start rotoscoping animations of people dancing the twist.
“Where do ideas come from?”
Probably the earliest asset available was also the gnarliest: the sampled remix of the Chubby Checker song. Initially the idea was to just play the sample, display the aforementioned animations with a quick cover of Chuck Berry’s “You never can tell” and that would have been it.
(To sidetrack a bit here, XiA immediately remembered of the movie Pulp Fiction and the fact that there was a twist compo in it. GGN didn’t, and probably would never remember that. He only watched Pulp Fiction once back in the 90s and wasn’t that impressed by it (sure, there are some very intense scenes but on the whole it left him a bit cold). And that was a theme through the making of this demo: one would suggest some sort of idea or refer to something that the other would dislike or feel cold about it, or even never seen/heard it before. This was solved quite easy: XiA has assumed the role of project lead from the start so his opinion was always the tie breaker. No “but”s or “why”s from GGN. And it worked just fine.)
After some days of hard hand rotoscoping, XiA was ready to present the fruits of his hard labour:
Those animations looked like a demo already! But then GGN thought “Gee, that dress sure feels empty”. So he suggested putting a twister in there to spice things up. Both GGN and Xia thought that this was actually a good idea so XiA also started generating some twister textures to experiment.
Development
Entering this project GGN had a single idea for an effect. Just one, and the idea would be that this would be the only thing to contribute. It’s a simple pixel effect based around an ancient Greek style of art called μαίανδρος (or meander in English). Some examples are provided below.
The idea came when GGN scribbled some meander-like patterns on paper while discussing with people at work. A pixel plotter that would plot patterns like these gradually seemed like a nice idea. How he got suckered into writing pretty much all the effects is anyone’s guess.
Technical debt
Before starting to do any other work, there was an issue to be solved that was put off for nearly a decade. Namely the problem of packed animations and double buffering.
There are many methods to pack successive frames. One of the easiest and fastest is to store the differences of the next frame compared to the current. This is of course known as delta packing. Let’s say that we have the four following frames to delta pack:
The first frame we display will be simply the “1” picture since there’s nothing before that. Then we compare the second frame with the first and store only the differences. Because of ST bitplane mode and to keep complexity low, we operate on 16 pixel wide blocks, so even if one pixel in that block changes we need to update all 16. So we generate a mask that tells us what to change in order to get to frame 2 from frame 1:
We apply the same idea to get to frame 3 and 4
Then we come to the part when we want to display frame 1 again. But if you remember above, we didn’t have anything to delta it against! So we’re forced to pack it again but this time compared to frame 1:
And now we have all the data to play this animation indefinitely.
But this approach makes an assumption that can be problematic. Namely, it assumes we are always unpacking to the same screen buffer. Normally the implementation of this algorithm is fast enough and if there isn’t much movement between frames then there is no flickering or screen tearing. However if we want to display other effects on screen then it becomes a major problem. Unfortunately if we try to depack the frames in a dual buffer configuration, something like this happens:
Buffer 1 will unpack frame 1 properly of course: Buffer 2 will unpack the deltas of frame 1 to 2, so some blanks will be there, still nothing too serious: Buffer 1 will now attempt to transition from frame 2 to 3 but unfortunately it contains frame 1!: Predictably, buffer 2 will want to transition from frame 3 to 4 but has frame 2:
And this was the sticky part: ever since both GGN and XiA coded their own versions of a delta packer this issue never arose. Of course it seemed horribly complex to deal with so it was deferred for some other time where it would be needed. All good things have to come to and end unfortunately so the time has come to deal with this problem once and for all.
It turned out that the solution was very simple.
- Instead of storing the differences of current frame versus the previous, we store the differences between current frame and 2 frames back
- At the beginning store frames 1 and 2 instead of just frame 1 - these would be copied to both screen buffers during playback
- At the end of the stream pack last frame to 1 and 1 to 2 so we can loop properly
This of course has the side effect of slightly larger packed size because of storing an extra frame at the beginning and an extra frame at the end, and possibly because of larger differences between 2 frames instead of 1. But all in all it was quite painless.
(of course there was the other tiny problem of the animations not updating at 50 frames per second but let’s not go there!)
Mixing styles
Another issue that had to be resolved early on was collaboration. While most of the framework and data was provided by XiA there would be friction due to 2 different people sharing code and memory. Thus both parties agreed to a few simple rules:
- Each effect’s code would be prefixed with a short distinctive name to avoid label clashing (remember, we are assembling from a single source file that includes all the rest), thus create a sort of manual namespacing.
- Small binary includes are fine, but anything substantially large has to be loaded by the framework at the appropriate time. This made the code more cumbersome but it paid dividends as the memory footprint was kept down to a minimum without headaches.
- Use the same two screen buffers between all effects. Obvious but easily forgotten!
Another stumbling block was that GGN was mostly using Turbo Assembler to write code while XiA uses rmac. This immediately meant that sharing code would be problematic as the two assemblers don’t have 100% the same syntax. After some thought GGN came up with a few macros that when run in rmac will bridge the gap of assembler directives, which is the main problem syntactically. For reference they are as follows:
.macro PART
.endm
.macro ENDPART
.endm
.macro RSRESET
.abs
.endm
.macro RS value
.ds\! \{value}
.endm
.macro END
.text
.endm
.macro IBYTES
.incbin \1
.endm
There are one or two cases that can’t be fixed using a macro but these covered the majority of the cases. So GGN was able to happily code away in his favorite environment and then be able to export the code for XiA to use without much friction and tedious find/replace commands.
An added bonus to this was that each effect could be written in a vacuum with very minimal framework, thus could be tested and debugged much faster. That boileplate code lives inside conditional assembly which gets skipped if the code is assembled with the demo framework.
512k
As stated above, memory footprint was a constant issue. While the amount of free system RAM is fairly easy to calculate, there was the extra complexity of the hard disk driver. The amount of total available RAM is a bit of a black box to users and has to do with number of partitions, caches and number of folders (that old GEMDOS issue). After much consideration the size 380Kb was selected. This would be the absolute maximum amount of RAM the demo was allowed to use, and on the flipside the demo would fail to run if there was less free RAM than 380k.
In order to keep track of memory usage XiA created and maintained a spreadsheet that recorded the RAM costs for each part of the demo.
A small memory manager was written for the purposes of this demo, that can load assets from disk on the fly and manage pointers. As usual, it could have been better but it did its job well and that’s all that matters!
“What else can we put inside that dress?”
After the breakthrough of “twister inside a twist dancer” Pandora’s box was open. Pretty much everything could go inside that dress! A lot of ideas were tossed around and coded quite a few of them were coded. Some of them turned out nice, some of them looked awful. In the end the following were used:
- Twister
- Starfield
- Zooming checkerboard
- Big rotating stars
(that’s right, the GIFs don’t loop perfectly and they stutter. Deal with it!)
Sample
One thing that was sadly not mentioned in the credits was the use of a third party sample player. Eventually the stock player by the excellent Tony Racine from 2-bit systems was selected. The criteria for choosing this player was that it was fairly easy to integrate with the demo framework and didn’t require any magic conversion steps. (and maybe nobody was bothered to test between so many candidates!)
Being a reference player that was mainly supposed to be called from high level languages and having to cope with replay rates from 4.9KHz to 49.152KHz meant that the replay routine was meant to be run on its own, leaving only but a handful of registers left for general use. Therefore all code that ran alongside the player had to be carefully written to avoid any clashes with the player. So, most of that code has an 8-bit feel to it, hitting the stack and memory very often as not enough registers were available to keep state.
Feature creep (AKA project management 101)
Development of the demo for the most part was pretty uneventful, with only a few issues here and there. Mostly it was tweaking the effects and coming up with interesting patterns and finding interesting ways to present them. Still it was quite surprising when the project hit final a little over 30 days into development, one week before the party.
This surprised both parties involved. It meant it was time to relax and wait for the party.
And we can’t have that, right?
“Is there enough content in there?”
“Maybe a few more effects to replace some twisters?”
GGN’s mind went hyperactive and started thinking of even more effects to use as a dress texture. Initially the effects were noted down on Sunday night. By Tuesday evening 3 of the 4 effects were running and were integrated to the main code. The fourth was written during GGN’s plane flight to Sweden. Eventually none was used as the risk of introducing bugs was deemed greater than adding variety to the demo.
At least everyone was at peace!
Takeaways
- It’s always good to create with another person, bouncing ideas between each other is an invaluable process.
- Atari ST coding is still fun!
- Putting tasks off for long periods, especially for reusable code, is sure to make them look daunting or impossible as time passes.
End result
Both GGN and XiA were surprised that the demo got first place at the compo. Sometimes miracles do happen!
Get the binary from Pouet or watch a video capture of the demo below.