Skip to content

Opening the borders

Drawing outside the frame the VIC-II reserves

The C64's border is not a frame but a switch: the VIC-II turns it on and off at fixed beam positions. Changing the screen height or width at the right moment makes the chip miss the switch, so sprites can be shown where the border should be. The top and bottom borders need a write on the right raster line; the side borders need one on an exact cycle of every line.

In April 1987 ZZAP!64 printed two views of the same trick in one issue. On page 47 Gary Liddon’s Compunet column rolled its eyes at the demos uploaded that month, with “bit map screens, Rob Hubbard music and sprites in the border scrolling messages!” On page 90, in his diary of writing Morpheus, Andrew Braybrook was “unhappy about using sprites in the border as it could be exploiting a video-chip bug. This may not work on some machines or it could cause damage.” Demo coders had made the Commodore 64’s border into screen space; a professional games programmer was not yet sure it was safe.

The worry faded as the trick spread into games. Later descriptions of the VIC-II show why it works: it follows from the way the chip’s border logic is built.

The border is a switch, not a frame

The VIC-II does not draw its border as a fixed frame. Christian Bauer’s description of the chip explains that it compares the beam’s position with a few hardwired values and turns the border on or off when one of them is reached exactly. Which values it uses depends on two bits: RSEL (bit 3 of $D011) chooses 25 or 24 rows, and CSEL (bit 3 of $D016) chooses 40 or 38 columns. Commodore’s Programmer’s Reference Guide explains what the smaller settings are for: they “give you a place for your new data to scroll on from”, hidden under the border.

Border edge 24 rows / 38 columns 25 rows / 40 columns
Top (raster line) 55 51
Bottom (raster line) 247 251
Left (X position) 31 24
Right (X position) 335 344

“The comparisons only match if the values are reached precisely,” Bauer writes. “There is no comparison with an interval.” If a program changes the setting after the beam has passed one value but before it reaches the other, neither ever matches, and the border does not switch on.

Top and bottom

For the lower border the switch is RSEL. With 25 rows the border starts at line 251; with 24 rows, at line 247. Bauer’s recipe is to run with 25 rows, wait until the beam is on line 248, 249 or 250, and switch to 24 rows. Line 247 has gone and line 251 no longer counts, so the chip “forgets” to turn on the border. Switching back to 25 rows any time after line 251 sets things up for the next frame. Because the border never started, it is never switched off at the top either, so one write per frame opens both the top and the bottom.

This program does that from a raster interrupt on line 249, puts a sprite below the normal screen, and returns to BASIC:

Assembly
        * = $0801
        !byte $0b,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00  ; 10 SYS2061

start   sei
        lda #$00
        sta $3fff           ; the byte the VIC-II repeats in the open border
        lda #$ff
        ldx #62
-       sta $0340,x         ; sprite shape: a solid block in block 13
        dex
        bpl -
        lda #13
        sta $07f8           ; sprite 0 uses block 13
        lda #160
        sta $d000           ; X = 160
        lda #255
        sta $d001           ; Y = 255: below the 25-row border line
        lda #7
        sta $d027           ; yellow
        lda #$01
        sta $d015           ; sprite 0 on

        lda #$7f
        sta $dc0d           ; switch off CIA 1's timer interrupt
        lda $dc0d
        lda #<irq1
        sta $0314
        lda #>irq1
        sta $0315
        lda #249
        sta $d012           ; first interrupt on line 249
        lda #$1b
        sta $d011           ; normal screen; bit 8 of the line is 0
        lda #$01
        sta $d01a           ; raster interrupts on
        cli
        rts

irq1    lda #$13            ; line 249: 24 rows. The 24-row border line (247)
        sta $d011           ; has gone, and the 25-row one (251) no longer counts
        lda #<irq2
        sta $0314
        lda #>irq2
        sta $0315
        lda #30
        sta $d012           ; next interrupt on line 30
        lda #$01
        sta $d019           ; acknowledge
        jmp $ea81           ; restore A, X and Y and return

irq2    lda #$1b            ; line 30: 25 rows again, ready for the next frame
        sta $d011
        lda #<irq1
        sta $0314
        lda #>irq1
        sta $0315
        lda #249
        sta $d012
        lda #$01
        sta $d019
        jmp $ea31           ; the KERNAL's own routine, once a frame

It works on PAL and NTSC machines. The handlers write whole values to $D011 instead of reading it and changing one bit, because reading $D011 returns bit 8 of the current raster line in bit 7. A read-modify-write made on any line after 255 would set bit 8 of the interrupt line by accident, and the next interrupt would be asked for on a line that does not exist.

Two things show in the opened area. Sprites appear normally, which is why the trick is really “sprites in the border”. Behind them the chip shows idle-state graphics: Bauer notes that with no character row to display, the VIC-II fetches the byte at $3FFF of the 16K bank it is using and repeats it wherever the opened area shows graphics, so programs clear that byte, or put a pattern in it on purpose. The listing sets it to zero.

The sides

The side borders open the same way with CSEL, but the timing is far stricter. Bauer: “Whereas for the vertical border, you have 4 raster lines time for the switch, for the horizontal border the change from CSEL=1 to CSEL=0 has to be exactly in cycle 56.” One processor cycle is eight pixels, and the right border starts every line, so the write has to land on that one cycle on every line that should be open. The program has to know exactly where the beam is, which means a stable raster routine first, and then code whose every line takes exactly 63 cycles on a PAL machine:

Assembly
        lda #$c0            ; $D016 value for 38 columns
        ldy #$c8            ; $D016 value for 40 columns
        ldx #40             ; forty raster lines
line    sta $d016           ; 38 columns, written on cycle 56
        sty $d016           ; 40 columns again for the next line
        ; 50 cycles of padding here (25 NOPs)
        dex
        bne line            ; 4 + 4 + 50 + 2 + 3 = 63 cycles a line

This loop only works if its first STA begins on cycle 53 of the line, so that its write, the instruction’s fourth cycle, falls on cycle 56. One cycle early or late and the border stays shut. It also works only on lines where nothing else takes the bus. A badline stops the processor for 40 to 43 cycles, and each sprite shown on a line takes two more, so a routine that opens the side borders down the main screen, with sprites in them, has a different cycle count for each kind of line. On an NTSC machine a line is 64 or 65 cycles, and the padding changes again.

In games

Within eighteen months of Braybrook’s worry, another games programmer could take it for granted. In his ZZAP!64 diary for September 1988, Martin Walker wrote that the “infobars” in Hunter’s Moon “employed the much used sprites in the border trick”, and “the overall screen display became a lot bigger than normal”. In January 1989 Commodore Disk User printed Jason Finch’s border-sprite routine for its readers, with the claim that “until about two or three years ago sprites in the border were absolutely unheard of and for a long while after, only the top games programmers, such as Jeff Minter, knew the secret, keeping it closely guarded for fear that their rivals would steal the new game sensation.”

Who was first

Public lists of C64 “world firsts” credit the Flash Cracking Group with the first opened upper and lower borders, on 11 October 1985, and TSI of 1001 Crew with the first opened side borders, in April 1986. They are community lists compiled long afterwards, and they invite corrections from anyone with firmer evidence. By April 1987, when ZZAP!64 was teasing Compunet’s demo crews about it, the effect was no longer new.

On the Atari ST

ST programmers removed their borders too, by a different route: they switched the display’s refresh rate for a moment. ST Format’s STOS column explained in February 1993 that “the basic principle is to change to and from different screen frequencies at the correct points in the screen”. The bottom border was “the easiest and most common border to remove”: wait for the last scanline, “change hertz, wait a bit, then change back again”. The left and right borders “are much more complicated and take a lot of processing time since you have to ‘open’ each line of the screen individually”. ST Amiga Format had already reported “full overscan and sprites in the border” in the Union Demo in 1989. See Atari ST.

Sources

  • Christian Bauer, The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64 (1996): section 3.9 (the border unit, comparison values, “reached precisely”), section 3.14.1 (hyperscreen: the RSEL recipe for lines 248–250, cycle 56 for CSEL), section 3.7.3.9 (idle state reads $3FFF), section 3.5 (badlines), section 3.8 (sprite fetches), section 3.2 (bit 7 of $D011 is bit 8 of the raster register).
  • ZZAP!64 issue 20, December 1986, p. 131: a full-screen picture from the 1001 Crew, made with ESCOS.
  • Gary Liddon, Compunet column, ZZAP!64 issue 24, April 1987, p. 47: “sprites in the border scrolling messages” in Compunet demos.
  • Commodore 64 Programmer’s Reference Guide (Commodore, 1982), p. 128: 38-column and 24-row modes for smooth scrolling.
  • Andrew Braybrook, Morpheus diary, ZZAP!64 issue 24, April 1987, p. 90: “unhappy about using sprites in the border”.
  • Martin Walker, Hunter’s Moon diary, ZZAP!64 issue 41, September 1988, p. 101.
  • Jason Finch, “Border Sprite”, Commodore Disk User, vol. 2 no. 2, January 1989, p. 18.
  • Billy Allan, “STOS Corner”, ST Format issue 43, February 1993: removing ST borders by switching screen frequency.
  • “Demos Corner”, ST Amiga Format issue 13, July 1989, p. 38: the Union Demo.
  • “World firsts and records”, Recollection (web, secondary; based on the Codebase64 world-firsts list): Flash Cracking Group, 11 October 1985; TSI/1001 Crew, April 1986. https://www.atlantis-prophecy.org/recollection/?load=world_of_demos

See also

Checked. Verified against primary sources and signed off by a person.