Atari 800 Winter Solstice Celebration Demo 2022

Every December, I try to make a little wintery demo to exercise one of 8bitworkshop’s platforms. This year, I chose the Atari 800 to test out the new native TypeScript emulator in 8bitworkshop. You can see the demo emulated here.

I revisted my Bally Astrocade “Happy 2020” Demo, which simulates falling snow. It uses pixel-level collision to simulate each falling particle, which creates neat little piles of snow and particles that ripple off the sides of obstacles.


Atari ANTIC Mode $F uses the GTIA chip to add additional modes beyond what the original CTIA chip provides. I used GTIA Mode 10, which is 80 x 192 pixels with nine different colors.

I changed the snow-falling algorithm so that the snow piles up only when it collides with existing snow. This scheme reserves one of the palette entries for snow, so we can detect snow vs. background.

First I used DiffusionBee, a stable diffusion AI art app, to make some nice little villages. This one turned out the best:

I painted a white streak on the roofs of buildings and the ground, ensuring that snow would collide there. I then ran it through Dithertron to convert it to Atari Mode 10.

Particle Simulation

In the original demo, the falling particles follow these rules every frame:

  • Move the particle down 1 pixel. If there is an empty pixel there, we’re done for now.

  • Otherwise, if there is an empty pixel to the left or right, move it left or right.

  • Otherwise, set the background pixel to snow color, and recycle the particle to the top of the screen where it will fall somewhere else.

In the new demo, we modify them thusly:

  • Instead of “empty pixel” we only collide when we find a “snow pixel”. This means particles will pass over the background image until they hit snow. Using XOR ensures we won’t damage the background.

  • When a particle should move left or right, there will be a small probability that they will instead stop. Over time this will create piles on top of steep roofs, whereas the old algorithm would never pile up any snow on an incline steeper than 45 degrees.


I adapted the Atari vector game music routine from Making 8-Bit Arcade Games in C. This sets up the POKEY in 16-bit mode to gain higher frequency accuracy, so you can hit notes without the weird tuning problems like on the Atari 2600.

The arcade games have two POKEYs, and a stock Atari 800 only has one, so we only get two voices with this scheme.

To play at a steady tempo, we need to call the music_update routine at a steady rate. The CC65 library sets up a VBI (vertical blank interrupt) that runs at 60 Hz, so we can use that.

The problem with CC65 and interrupts is that CC65 code uses registers and stack in RAM, which can get clobbered if the interrupt fires at the wrong time. CC65 provides a special set_irq() function which hooks C routines into the interrupt chain, but preserves global registers and uses a second stack for the interrupt.

// set interrupt routine with stack at $7000-$70FF
set_irq(music_update, (void*)0x7000, 0x100);

CC65 Config File

For this demo we’re going to output a 32 KB cartridge ROM, which takes up addresses $8000-$BFFF. This requires us to hook in a custom .cfg file, like this (in the main .c file):

//#resource "atari-tgi.cfg"
#define CFGFILE atari-tgi.cfg

Making the cartridge 32 KB is easy, there’s just one variable to change:

__CARTSIZE__: type = weak, value = $4000;

TGI Graphics

We are using the TGI library for setting up the Atari graphics mode. TGI requires us to reserve enough memory for the frame buffer(s), which is done with this config variable:

__RESERVED_MEMORY__: type = export, value = $1e00;

Here’s how to initialize TGI for Mode 10 graphics:

const byte PALETTE[9] = {
  0xf0, 0x42, 0x84, 0x46,
  0xa6, 0x3a, 0xaa, 0x1e, 0x0e,

    tgi_install (atr10_tgi); 
    tgi_init ();

We don’t use TGI for the particle-drawing routines, because they’re too slow and don’t support XOR. Instead, we write our own:

byte* fb; // frame buffer address

void drawflake(word pos) {
  if (pos & 1)
    fb[pos>>1] ^= 0x08;
    fb[pos>>1] ^= 0x80;

The fb variable holds the address of the graphics frame buffer. The SAVMSC BIOS register (0x58/0x59) contains the start of screen memory, so after we initialize TGI we read SAVMSC and set the fb variable:

  fb = (char*) PEEKW(0x58);

For the background image, I downloaded the BIN file from Dithertron and converted it to a C array with a binary-to-text converter – the same one linked to on 8bitworkshop’s Tools menu. Then you can just memcpy() it to the frame buffer:

  memcpy(fb, BITMAP, 40*192);

Try it in 8bitworkshop

The source code to the demo is available on GitHub, so if you’re interested in Atari programming, check it out!