A white spaceship against black. Nothing moves, nothing makes sound — it just sits there. That’s the goal of this unit, and it’s the first piece of your first C64 game.
In the Primer you met the VIC-II through its border and background colours, wrote bytes straight into screen memory, and launched machine code with a SYS stub. This unit uses all of that — LDA/STA, memory-mapped registers, the 10 SYS 2061 stub — and adds the one new thing a game needs: a hardware sprite, a movable graphic the VIC-II draws for you, on top of the screen, wherever you tell it.
We’ll get there from a blank screen, in two steps.
Where we start
A freshly booted C64, the way the Primer left it: the start-up message, and READY. Type RUN and our bare program does nothing yet — there’s nothing on screen but BASIC.

Milestone 1 — a black canvas
Before the ship, we clear the stage: border and background to black, and the BASIC text wiped out of screen memory. This is the Primer’s screen work — STA $d020/$d021 for the colours, a loop writing spaces across the 1,000 bytes of screen RAM at $0400.
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00
; ------------------------------------------------
Removed: ; Program — nothing yet, just hand control to a do-nothing loop
Added: ; Program
; ------------------------------------------------
*= $080d
Added: ; Black screen
Added: lda #$00
Added: sta $d020 ; Border colour
Added: sta $d021 ; Background colour
Added:
Added: ; Clear the screen (fill with spaces)
Added: ldx #$00
Added: - lda #$20
Added: sta $0400,x
Added: sta $0500,x
Added: sta $0600,x
Added: sta $0700,x
Added: inx
Added: bne -
Added:
loop:
jmp loop
The complete step 1 program
; Starfield - Unit 1: Ship on Screen
; Cumulative steps: step-00 (bare program) -> step-01 (+ black screen) -> step-02 (+ the ship)
; Assemble: acme -f cbm -o <step>.prg <step>.asm
; ------------------------------------------------
; BASIC stub
; ------------------------------------------------
*= $0801
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00
; ------------------------------------------------
; Program
; ------------------------------------------------
*= $080d
; Black screen
lda #$00
sta $d020 ; Border colour
sta $d021 ; Background colour
; Clear the screen (fill with spaces)
ldx #$00
- lda #$20
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
inx
bne -
loop:
jmp loopThe four STA writes set the two colours; the loop counts the X register from 0, writing a space ($20) into each page of screen memory until INX wraps it back to 0 and BNE stops looping. The result is a clean black canvas:

Milestone 2 — the ship
Now the new part. A hardware sprite needs four things from the VIC-II, plus its picture in memory.
sta $0700,x
inx
bne -
Added:
Added: ; Sprite 0 setup (ship)
Added: lda #128
Added: sta $07f8 ; Data pointer (block 128 = $2000)
Added: lda #172
Added: sta $d000 ; X position
Added: lda #220
Added: sta $d001 ; Y position
Added: lda #$01
Added: sta $d027 ; Colour (white)
Added: lda #%00000001
Added: sta $d015 ; Enable sprite 0
loop:
jmp loop
Added:
Added: ; ------------------------------------------------
Added: ; Sprite data at $2000 (block 128)
Added: ; ------------------------------------------------
Added: *= $2000
Added: !byte $00,$18,$00 ; ##
Added: !byte $00,$3c,$00 ; ####
Added: !byte $00,$3c,$00 ; ####
Added: !byte $00,$7e,$00 ; ######
Added: !byte $00,$7e,$00 ; ######
Added: !byte $00,$ff,$00 ; ########
Added: !byte $00,$ff,$00 ; ########
Added: !byte $01,$ff,$80 ; ##########
Added: !byte $03,$ff,$c0 ; ############
Added: !byte $07,$ff,$e0 ; ##############
Added: !byte $07,$ff,$e0 ; ##############
Added: !byte $07,$e7,$e0 ; ###..####..###
Added: !byte $03,$c3,$c0 ; ##....##....##
Added: !byte $01,$ff,$80 ; ##########
Added: !byte $00,$ff,$00 ; ########
Added: !byte $00,$ff,$00 ; ########
Added: !byte $00,$db,$00 ; ##.##.##
Added: !byte $00,$db,$00 ; ##.##.##
Added: !byte $00,$66,$00 ; ##..##
Added: !byte $00,$24,$00 ; #..#
Added: !byte $00,$00,$00 ;
The complete program
; Starfield - Unit 1: Ship on Screen
; Cumulative steps: step-00 (bare program) -> step-01 (+ black screen) -> step-02 (+ the ship)
; Assemble: acme -f cbm -o <step>.prg <step>.asm
; ------------------------------------------------
; BASIC stub
; ------------------------------------------------
*= $0801
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00
; ------------------------------------------------
; Program
; ------------------------------------------------
*= $080d
; Black screen
lda #$00
sta $d020 ; Border colour
sta $d021 ; Background colour
; Clear the screen (fill with spaces)
ldx #$00
- lda #$20
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
inx
bne -
; Sprite 0 setup (ship)
lda #128
sta $07f8 ; Data pointer (block 128 = $2000)
lda #172
sta $d000 ; X position
lda #220
sta $d001 ; Y position
lda #$01
sta $d027 ; Colour (white)
lda #%00000001
sta $d015 ; Enable sprite 0
loop:
jmp loop
; ------------------------------------------------
; Sprite data at $2000 (block 128)
; ------------------------------------------------
*= $2000
!byte $00,$18,$00 ; ##
!byte $00,$3c,$00 ; ####
!byte $00,$3c,$00 ; ####
!byte $00,$7e,$00 ; ######
!byte $00,$7e,$00 ; ######
!byte $00,$ff,$00 ; ########
!byte $00,$ff,$00 ; ########
!byte $01,$ff,$80 ; ##########
!byte $03,$ff,$c0 ; ############
!byte $07,$ff,$e0 ; ##############
!byte $07,$ff,$e0 ; ##############
!byte $07,$e7,$e0 ; ###..####..###
!byte $03,$c3,$c0 ; ##....##....##
!byte $01,$ff,$80 ; ##########
!byte $00,$ff,$00 ; ########
!byte $00,$ff,$00 ; ########
!byte $00,$db,$00 ; ##.##.##
!byte $00,$db,$00 ; ##.##.##
!byte $00,$66,$00 ; ##..##
!byte $00,$24,$00 ; #..#
!byte $00,$00,$00 ;And there it is:

The four registers
- Where’s the picture?
$07f8holds a block number — the sprite’s data divided by 64. Block 128 means address$2000(128 × 64 = 8192 =$2000). - Where on screen?
$d000(X) and$d001(Y) place it. X runs left-to-right, Y top-to-bottom. - What colour?
$d027sets sprite 0’s colour. White is 1. - Is it on?
$d015is the sprite-enable register — one bit per sprite. Set bit 0 to switch sprite 0 on.
The sprite’s picture
A C64 sprite is 24 pixels wide and 21 tall. Each row is 3 bytes (24 bits), 63 bytes in all, padded to 64 — which is why sprite data sits on a 64-byte boundary. Each bit is one pixel: a 1 draws in the sprite’s colour, a 0 is transparent. Bit 7 of the first byte is the top-left pixel.
Those bytes are the !byte rows at $2000 in the diff above. Here is everything together in memory:
When it’s wrong, see why
A sprite that won’t appear has a short list of causes, and they’re all values you can check — re-read them in your code, and if your emulator has a monitor, watch them live. Run through them in order:
$d015— is bit 0 set?$00means every sprite is off, and you’ll stare at a blank screen.$07f8— does the pointer match where the data is?128for data at$2000. A wrong pointer draws garbage or nothing.$d000/$d001— is the sprite on the visible screen? A Y of 0 or an X past the right edge hides it in the border.$d027— is the colour different from the background? A black sprite on black is there, just invisible.
Checking the actual values turns “nothing’s showing” from a guessing game into a four-line checklist.
Before and after
We started at the machine’s boot screen with nothing of ours on it, and finished with a sprite we placed and coloured ourselves — the first thing on screen that’s truly yours.
Try this
- Change the colour. Swap the
$01written to$d027for any value 0–15:$02red,$03cyan,$05green,$07yellow,$0elight blue. Reassemble and the ship changes instantly. - Move the ship. Change the values written to
$d000(X) and$d001(Y). X only reaches 255, but the screen is 320 wide — reaching the right edge needs an extra register ($d010), which is exactly what Unit 3 is about. - Draw your own. Click pixels below; the bytes update as you draw. Paste them over the
!byterows.
What you’ve learnt
- Hardware sprites — the VIC-II draws a movable graphic for you, over the screen, wherever its registers say.
- The four sprite registers — data pointer (
$07f8), position ($d000/$d001), colour ($d027), and enable ($d015). - Sprite data format — 24 × 21 pixels, 3 bytes a row, on a 64-byte boundary; one bit per pixel.
- Debugging by register — a missing sprite is one of four registers being wrong, and you can read each one.
What’s next
The ship sits still. In Unit 2 you’ll wrap it in a game loop and read the joystick, so pushing the stick moves it around the screen.
Run it here
This unit's program, built for you.
- Program
- step-02.prg
- Machine
- Commodore 64 (PAL Breadbin)