Skip to content

Sprite Multiplexing

Reuse the C64's eight hardware sprites several times down the screen to show sixteen, twenty-four or more — the raster trick behind every busy C64 game.

spritesmultiplexingrastervic-ii

Overview

The VIC-II gives you eight sprites — but only eight at once on a given scanline. The instant the raster beam passes the bottom of a sprite, that hardware sprite is idle and can be sent somewhere lower down the screen to appear as a completely different one. Trigger that with a raster interrupt, do it repeatedly, and eight sprites become sixteen, twenty-four, thirty — a multiplexer. It’s how nearly every crowded C64 game put more than eight things on screen.

It rests on three pieces: a list of “virtual” sprites, kept sorted by Y; a raster-interrupt chain that fires as each one finishes; and the reprogramming of a freed hardware sprite to the next virtual one in the list.

Code

; =============================================================================
; SPRITE MULTIPLEXING - C64
; Each raster interrupt hands a freed hardware sprite to the next virtual one
; Needs: a Y-sorted virtual sprite list, and ideally a stable raster
; =============================================================================

VIC_IRQ     = $D019
VIC_RASTER  = $D012
SCREEN      = $0400
SPR_PTR     = SCREEN + $3F8

; Virtual sprites, sorted top-to-bottom each frame (more than 8 of them):
;   vy,x  vx,x  vptr,x  vcol,x     count in NUM
; msp  = index of the next virtual sprite waiting for a hardware slot
; slot = which hardware sprite (0..7) to reuse next

mux_irq:
    lda #$01
    sta VIC_IRQ                ; acknowledge the raster interrupt

    ldx msp
    cpx #NUM
    bcs frame_end              ; all virtual sprites placed — wait for next frame

    ldy slot                   ; the hardware sprite that just came free (0..7)

    ; --- hand virtual sprite X to hardware sprite Y ---
    ; (register index is Y*2 for X/Y position; Y for colour, pointer, MSB bit)
    lda vy,x
    sta hw_y,y                 ; -> $D001 + 2*slot   (see note on indexing)
    lda vx,x
    sta hw_x,y                 ; -> $D000 + 2*slot
    lda vptr,x
    sta SPR_PTR,y              ; -> pointer for sprite `slot`
    lda vcol,x
    sta hw_col,y               ; -> $D027 + slot
    ; ... set/clear this sprite's X-MSB bit in $D010 too ...

    inc msp                    ; advance the virtual list
    iny                        ; rotate the hardware slot 0..7
    tya
    and #$07
    sta slot

    ; --- aim the next interrupt just below the NEXT virtual sprite ---
    ldx msp
    lda vy,x
    sec
    sbc #1                     ; a line or two of margin so we're not too late
    sta VIC_RASTER
    jmp $EA81

frame_end:
    ; reset for the next frame: msp=0, slot=0, IRQ back to the topmost sprite
    ; (the per-frame Y-sort of the virtual list happens here or in the main loop)
    lda #0
    sta msp
    sta slot
    lda vy                     ; topmost sprite's line
    sta VIC_RASTER
    jmp $EA81

(The hw_y,y / hw_x,y writes stand in for the ×2 register indexing — most multiplexers pre-build a small table of the eight $D000+2n addresses, or unroll the eight cases, rather than compute it each time.)

Trade-offs

Aspect Cost
CPU One interrupt per reused sprite — several per frame
Memory Virtual sprite tables, plus the sort
Limitation Still only 8 on any one scanline; total depends on vertical spread

When to use: Any game that needs more than eight sprites — shmups, crowds of enemies, big bosses built from several sprites.

When to avoid: If eight is genuinely enough. The multiplexer’s timing is unforgiving; don’t pay for it unless you need it.

The sort is the heart of it

Hardware sprites are reused in Y order, so the virtual list has to be sorted top-to-bottom every frame. A full sort each frame would be dear — but sprites move only a little between frames, so the list is almost sorted already. An insertion sort exploits that: it’s near-linear on nearly-sorted data, so the per-frame cost stays small. Getting the sort wrong is the classic multiplexer bug — a sprite reused before the beam has passed its old position flickers or tears.

Timing and limits

  • Never reprogram a sprite the beam is still drawing. Reposition it only after its line has passed, with a line or two of margin — hence aiming the next interrupt just below the current sprite.
  • Eight per scanline is the hard ceiling. Multiplexing buys you more down the screen, never more across one line. Bunch too many at the same height and the ninth vanishes.
  • Jitter shows. Because the reprogramming races the beam, an unstable raster makes sprites shimmer. Serious multiplexers run on a stable raster.

Patterns: Hardware Sprites, Raster Splits

Vault: VIC-II | Commodore 64