Unfortunate Coincidence

Szórádi Balázs
Jan 15 · 7 min read

Unfortunate Coincidence is a c64 one file demo released on the 14th of Jan. 2021 in a special competition allowing sprites only. In this article, I would like to give you an overview of what's going on under the hood.

The effect

The idea of the main effect is to overlay horizontal raster bars with a layer of sprites representing vertical color bars with alternating on/off pixels to show a “transparent” lattice. The result is a crazy looking plasma-like effect with lots of colors.

History

I already did a version of this effect in gfx mode for the demo “25 Years Atlantis”. In that demo the overly was a gfx grid instead of sprites therefore I could have 2 colors per vertical char column ($d800 was not used) with two-pixel wide pixels. I used a trick to avoid “bad lines” (Char DMA fetches) so the vertical colors in the gfx had to be updated only for the first char line (40 bytes) instead of the whole screen (1000 bytes). This allowed the effect to run full frame at 50fps. (I'm pretty proud of this effect as I got applause for it during its presentation at X’2016)

Screenshot from the demo “25 years Atlantis”

Sprite only version

So when I first heard about Raistlin’s “Sprite Only Compo” at CSDB this effect immediately popped into my mind that this has to be done with sprites and open borders.

I knew there will be limitations compared to the original effect, but hoped it would look good as well.

  • In order to cover the full width of the screen, I need to use all 8 sprites expanded on the x-axis, which doubles the x-axis pixel resolution to 2 pixels.
  • I can have only 3 colors for the overlay, as sprites can have only 3 colors two of which are fixed and one freely adjustable. For simplicity, I decided to use 3 fixed colors for all of the sprites. Turning on multicolor doubles the x-axis pixel resolution again so the x resolution will be 4 pixels.

As I was planning to update the raster bar colors on every 4th raster line having 4 pixel wide vertical bars turns out to be an acceptable restriction.

First prototype

For starts, I wanted to see how the timing goes with open borders, four pixels high bars, and overlayed x-axis expanded multiplexed sprites. The sprites in the example are in dark grey with all sprite edge pixels set. At this point, I got really excited that I can do this.

The first prototype of the effect.

The problem

The problem came up after I added the color animation and sprite update calculations. As you can see below, some lines are displayed twice, and that is because c64 sprites have an odd number of rows. Placing the same sprite underneath one another will cause this as the first and last lines of the sprite are the same.

Unfortunately, there is no way of stopping the drawing of a sprite once it has been started so VIC will display all lines, and multiplexing a new set of sprites is also not possible before all previous sprite rows have been displayed.

What can be done is to change the sprite pointer per line so VIC reads different data for even and odd lines. To do this I had to do twice the calculation that I planned originally. Now because of this bug, I had to update 16 sprites instead of 8. One set containing pixel bytes like %10101010 and another one containing %01010101 all the way down from sprite row 1 to 21.

As I had no raster time left to update 16 sprites per frame I had to put the sprite calculation in the main execution thread and use double buffering (using VIC bank switch) to hide update glitches.

Then I had to rewrite the timing code to add a $d018 switch per line to alternate between the two sets of spites. (Finally, I had 3 cycles left per line phew…)

The problem c64 has an odd number of sprite rows…

Update: Oswald/Resource pointed out I could have used sprite stretching to solve the problem caused by having odd number of sprite lines. (As I have never used sprite stretching the idea never occured to me.)

So after looking into the sprite stretching trick I updated my prototype in 15 minutes to use sprite stretching and yes it allows me to remove the multiplexer + only the first line has to be updated in the sprite which is stretched (repeated) for the full height of the effect. This saves 3840 CPU cycles, so now in my new version I have time to update the sprite animation per frame as well.

A little consolation is that in case I used stretching I could not have placed the “Coincidence” logo right below the effect, because VIC renders the remaining 20 lines of the sprite once the stretching is stopped.

The bounce

Adding the bounce seemed easy at first as I imagined all I needed to do was updating the raster interrupt position and I'm good. Obviously, I forgot about the sprite y coordinates. So I had to tweak the timing precise code once again to read the y coordinates for the multiplexer from zero-page addresses that I update before the end of the previous frame.

//------------------------------------------------------------------
irq: {
irqStart
ldy $10 // set y position of first sprite row
ldx sine,y
stx $d012
inx // add an offset of two as we need
inx // two rasters to get a stable raster
stx $d001
stx $d003
stx $d005
stx $d007
stx $d009
stx $d00b
stx $d00d
stx $d00f
lda #$00 // acknowledge main thread to
sta mloop.ack // continue updating sprites
lda mloop.flp // set the correct video bank
bne !+ // (double buffering for sprites)
lda #%00000010 // VIC Bank to $4000-$7fff
sta $dd00
jmp cont
!: lda #%00000001 // VIC Bank to $8000-$bfff
sta $dd00
cont: mw #mainEffect : $fffe // set next irq address
irqEnd
.align $100
mainEffect: {
irqStart_stableRaster
pause_looped #36
ldy #$00 // y holds the raster line counter
jmp cont // align to $100 memory address
.align $100
cont:
.var rastercnt = 0;
.var spritecnt = 0;
.var step = -1;
// we have 11 sets of 16 pixel high segments
.for(var j=0;j<11;j++) {
.var num = 4
.if (j == 10) {
.eval num = 2
}
// each segment but the last has 4 subsegments
// each sub segment is 4 rasters high
.for(var i=0;i<num;i++) {
// the first line is the most exciting
// update $d021, $d018, sprite pointers...
ldx d018tab,y
lda #0
dec $d016
sta $d021
stx $d018
// zeropage addresses from $f0 hold the
// new y coordinates for the multiplexer
ldx $f0 + spritecnt
bit $00
inc $d016
.if (step < 3) {
.eval step += 1;
}
.if (j<9) {
stx $d001 + step*4
stx $d003 + step*4
}
else {
lda $1000 // dummy do nothing
lda $1000
}
iny
// for the remaing 3 lines I use a cycle
ldx #3
lp: nop
nop
nop
nop
dec $d016
inc $d016
bit $00
lda d018tab,y
sta $d018
nop
nop
nop
nop
iny
dex
bne lp
.if (i<3) {
bit $00
}
else {
jmp next
.align $100
}
.eval rastercnt += 4;
.if (floor(rastercnt/21) == 1) {
.eval rastercnt -= 21;
.eval spritecnt += 1;
.eval step = -1;
}
}
next:
}
nop
nop
lda #$00
dec $d016 // open sideborder for the last line
inc $d016
sta $d021
irqEnd #irqBorder : #[*+25]
irqStart
lda #$00 // screen off 24 rows
sta $d011
irqEnd #irqBorderOff : #[*+25]
irqStart
lda #%00001000 // screen off 25 rows
sta $d011 // (top border gets opened)
inc $10 // calculate sprite y positions for
ldy $10 // the multiplexer of the next frame
ldx sine,y
inx
inx
txa
clc
adc #21
sta $f0
adc #21
sta $f1
adc #21
sta $f2
adc #21
sta $f3
adc #21
sta $f4
adc #21
sta $f5
adc #21
sta $f6
jsr anim_horiz
jsr music.play
dec $d021
irqEnd #irqMusic : #irq
}
}

The logos

Once the bounce was in place I had the idea to place logos above and below as space opens. Here I had to be careful that the logo does not hit the main effect as otherwise, the timing of the effect would break. To do this I chose a pretty simple solution using a table that tells me for each frame whether or not the logo is safe to display or not. By trial and error, I tweaked the content of this table as long as it was not working as I wanted.

The side border scroller

Is a relatively simple hires x-axis expanded set of sprites, using the ROL instruction to scroll the content from sprite to sprite. To gain speed I interleaved the horizontal (used for the rasters) plasma calculation code in the border open code. I was lucky this time as the update of one row of plasma is 17 cycles two of which is exactly the amount of free cycles I had in between the opening.

// open 11 rasterlines (interleave plazma code inbetween)
.for(var i=0; i<11; i++) {
dec $d016
inc $d016
plazma_17c(0+i*2)
plazma_17c(1+i*2)
}
The final production with logo(s) and the scroller.

As I was afraid that adding this scroller would slow the main effect down way too much I decided to use a charset that is only 5 pixels high, and skipping every second line would still give a reasonable char height. In the end, in my opinion, it looks great with the final prod.

The music

I didn’t want to put restrictions on the music so I kept the place and raster time for the music in mind during the whole project. Uses sidwizard demo player 20/25 raster lines.

I love it Vincenzo did an excellent job!

Full credits

Code: Strepto
Music: Vincenzo
Graphics: Grass

Thanks for reading! I had a lot of fun working on this demo, I hope you like it!

Lethargy Dev

Articles on Commodore 64 development

Lethargy Dev

In this publication, you will find articles on c64 (mostly demo) development. Articles cover coding, pixeling, music-making, cross-platform development, algorithms, etc…

Szórádi Balázs

Written by

Programming in assembly, c, c++, java, javascript, typescript. Interested in 3d graphics, visual effects, demoscene, dsp, c64, cycling, climbing…

Lethargy Dev

In this publication, you will find articles on c64 (mostly demo) development. Articles cover coding, pixeling, music-making, cross-platform development, algorithms, etc…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store