Skip to content
Game 1 Unit 4 of 16 1 hr learning time

Fire Button Shoots

Read the fire button and launch a bullet from the ship — then make the shot spawn correctly wherever the ship is, using the ninth X bit from the last unit.

25% of Starfield

The ship flies anywhere on screen now and stops cleanly at every edge — but it's unarmed. A shooter needs a shot. This unit reads the fire button and launches a bullet that streaks up the screen, then fixes the one thing that breaks the moment you fire from the right: the ninth X bit you added last unit.

Where we start

We have last unit's ship: full-width movement, clamped to the screen, sprite 0 in white. Nothing happens when you press fire — the button isn't read yet, and there's no second sprite to launch. We'll give the ship a bullet on sprite 1.

Milestone 1 — fire a bullet

The fire button is one more bit on the same joystick byte you read for movement. Port 2 sits at $dc00; bits 0–3 are the four directions, and bit 4 is fire — active-low like the rest, so a pressed button reads as a 0.

$DC00 — CIA 1 Port B — joystick 2 (active low) 7 0 6 0 5 0 4 Fire 1 3 Right 0 2 Left 0 1 Down 0 0 Up 0
Bit 4 is the fire button. Like the direction bits it reads 0 when pressed, so we isolate it and branch when it's clear.

The bullet rides on sprite 1. We point it at a tall, thin shape, colour it yellow, and leave it disabled until the moment of firing. A single zero-page flag, bullet_active, tracks whether a shot is in flight — and that flag is also the guard: we only spawn a new bullet when no bullet is already flying, so holding fire down gives one shot at a time, not a solid beam.

$00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $18 $00 $00 $18 $00 $00 $18 $00 $00 $18 $00 $00 $18 $00 $00 $18 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00
The bullet sprite: a short vertical bar in the middle columns, stored in block 129 at $2040.

Here's the whole step — reading fire, the spawn guard, and moving the bullet up the screen each frame:

Step 1: read fire, launch a bullet, fly it up
+90
11 ; Starfield - Unit 4: Fire Button Shoots
22 ; Cumulative steps: step-00 (ship + clamping) -> step-01 (+ a bullet) -> step-02 (+ 9th-bit-aware bullet)
33 ; Assemble: acme -f cbm -o <step>.prg <step>.asm
4+
5+; ------------------------------------------------
6+; Zero-page variables
7+; ------------------------------------------------
8+bullet_active = $02 ; 0 = no bullet, 1 = active
9+bullet_y = $03 ; Bullet Y position
410
511 ; ------------------------------------------------
612 ; BASIC stub
...
4046 sta $d015 ; Enable sprite 0
4147 lda #$00
4248 sta $d010 ; sprite high-X bits clear (ship starts under X=256)
49+
50+ ; Sprite 1 setup (bullet)
51+ lda #129
52+ sta $07f9 ; Data pointer (block 129 = $2040)
53+ lda #$07
54+ sta $d028 ; Colour (yellow)
55+
56+ ; Bullet starts inactive
57+ lda #$00
58+ sta bullet_active
4359
4460 ; ------------------------------------------------
4561 ; Game loop — runs once per frame
...
125141 sta $d010
126142 +
127143 not_right:
144+
145+ ; --- Fire button (bit 4) ---
146+ lda $dc00
147+ and #%00010000
148+ bne no_fire ; Bit is 1 = NOT pressed
149+
150+ ; Only spawn if no bullet is already flying
151+ lda bullet_active
152+ bne no_fire
153+
154+ ; Spawn the bullet at the ship's position
155+ lda $d000 ; Ship X (low byte) -> bullet X
156+ sta $d002
157+ lda $d001 ; Ship Y -> bullet Y
158+ sta bullet_y
159+
160+ ; Enable sprite 1 (keep sprite 0 enabled)
161+ lda $d015
162+ ora #%00000010
163+ sta $d015
164+
165+ lda #$01
166+ sta bullet_active
167+
168+no_fire:
169+
170+ ; --- Update the bullet ---
171+ lda bullet_active
172+ beq no_bullet
173+
174+ ; Move it up 4 pixels a frame
175+ lda bullet_y
176+ sec
177+ sbc #$04
178+ sta bullet_y
179+ sta $d003 ; sprite 1 Y
180+
181+ ; Gone off the top? (Y < 30) -> remove it
182+ cmp #$1e
183+ bcs no_bullet
184+
185+ lda #$00
186+ sta bullet_active
187+ lda $d015
188+ and #%11111101 ; disable sprite 1, keep sprite 0
189+ sta $d015
190+
191+no_bullet:
128192
129193 jmp game_loop
130194
...
153217 !byte $00,$66,$00 ; ##..##
154218 !byte $00,$24,$00 ; #..#
155219 !byte $00,$00,$00 ;
220+
221+; ------------------------------------------------
222+; Sprite data at $2040 (block 129) — bullet
223+; ------------------------------------------------
224+*= $2040
225+ !byte $00,$00,$00
226+ !byte $00,$00,$00
227+ !byte $00,$00,$00
228+ !byte $00,$00,$00
229+ !byte $00,$00,$00
230+ !byte $00,$00,$00
231+ !byte $00,$00,$00
232+ !byte $00,$18,$00 ; ##
233+ !byte $00,$18,$00 ; ##
234+ !byte $00,$18,$00 ; ##
235+ !byte $00,$18,$00 ; ##
236+ !byte $00,$18,$00 ; ##
237+ !byte $00,$18,$00 ; ##
238+ !byte $00,$00,$00
239+ !byte $00,$00,$00
240+ !byte $00,$00,$00
241+ !byte $00,$00,$00
242+ !byte $00,$00,$00
243+ !byte $00,$00,$00
244+ !byte $00,$00,$00
245+ !byte $00,$00,$00
156246
The complete step 1 program
; Starfield - Unit 4: Fire Button Shoots
; Cumulative steps: step-00 (ship + clamping) -> step-01 (+ a bullet) -> step-02 (+ 9th-bit-aware bullet)
; Assemble: acme -f cbm -o <step>.prg <step>.asm

; ------------------------------------------------
; Zero-page variables
; ------------------------------------------------
bullet_active = $02     ; 0 = no bullet, 1 = active
bullet_y      = $03     ; Bullet Y position

; ------------------------------------------------
; BASIC stub
; ------------------------------------------------
*= $0801
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00

; ------------------------------------------------
; Initialisation
; ------------------------------------------------
*= $080d
        ; Black screen
        lda #$00
        sta $d020           ; Border colour
        sta $d021           ; Background colour

        ; Clear the screen
        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
        lda #$00
        sta $d010           ; sprite high-X bits clear (ship starts under X=256)

        ; Sprite 1 setup (bullet)
        lda #129
        sta $07f9           ; Data pointer (block 129 = $2040)
        lda #$07
        sta $d028           ; Colour (yellow)

        ; Bullet starts inactive
        lda #$00
        sta bullet_active

; ------------------------------------------------
; Game loop — runs once per frame
; ------------------------------------------------
game_loop:
        ; Wait for the raster beam to reach line 255
        ; This syncs our code to the display (~50Hz PAL)
-       lda $d012
        cmp #$ff
        bne -

        ; --- Read joystick and move ship ---

        ; UP (bit 0) — clamp to Y >= 50
        lda $dc00           ; Read joystick port 2
        and #%00000001      ; Isolate bit 0
        bne not_up          ; Bit is 1 = NOT pressed (active low)
        lda $d001
        cmp #52             ; 50 + room for a 2-pixel move
        bcc not_up          ; already at the top — don't move
        dec $d001           ; Move ship up (decrease Y)
        dec $d001           ; 2 pixels per frame
not_up:

        ; DOWN (bit 1) — clamp to Y <= 234
        lda $dc00
        and #%00000010
        bne not_down
        lda $d001
        cmp #233            ; 234 - room for a 2-pixel move
        bcs not_down        ; already at the bottom — don't move
        inc $d001           ; Move ship down (increase Y)
        inc $d001
not_down:

        ; LEFT (bit 2) — 9-bit X, clamp to X >= 24
        lda $dc00
        and #%00000100
        bne not_left
        lda $d010
        and #$01
        bne left_ok         ; high bit set: X >= 256, always safe to go left
        lda $d000
        cmp #26             ; 24 + room for a 2-pixel move
        bcc not_left        ; already at the left edge — don't move
left_ok:
        ; before each step, flip the 9th bit when X is about to wrap $00 -> $ff
        lda $d000
        bne +
        lda $d010
        eor #$01            ; the eor bit-flip from the Primer, on sprite 0's high X bit
        sta $d010
+       dec $d000
        lda $d000
        bne +
        lda $d010
        eor #$01
        sta $d010
+       dec $d000
not_left:

        ; RIGHT (bit 3) — 9-bit X, clamp to X <= 320
        lda $dc00
        and #%00001000
        bne not_right
        lda $d010
        and #$01
        beq right_ok        ; high bit clear: X < 256, always safe to go right
        lda $d000
        cmp #63             ; (320 - 256) - room for a 2-pixel move
        bcs not_right       ; already at the right edge — don't move
right_ok:
        ; after each step, flip the 9th bit when X wraps $ff -> $00
        inc $d000
        bne +
        lda $d010
        eor #$01
        sta $d010
+       inc $d000
        bne +
        lda $d010
        eor #$01
        sta $d010
+
not_right:

        ; --- Fire button (bit 4) ---
        lda $dc00
        and #%00010000
        bne no_fire         ; Bit is 1 = NOT pressed

        ; Only spawn if no bullet is already flying
        lda bullet_active
        bne no_fire

        ; Spawn the bullet at the ship's position
        lda $d000           ; Ship X (low byte) -> bullet X
        sta $d002
        lda $d001           ; Ship Y -> bullet Y
        sta bullet_y

        ; Enable sprite 1 (keep sprite 0 enabled)
        lda $d015
        ora #%00000010
        sta $d015

        lda #$01
        sta bullet_active

no_fire:

        ; --- Update the bullet ---
        lda bullet_active
        beq no_bullet

        ; Move it up 4 pixels a frame
        lda bullet_y
        sec
        sbc #$04
        sta bullet_y
        sta $d003           ; sprite 1 Y

        ; Gone off the top? (Y < 30) -> remove it
        cmp #$1e
        bcs no_bullet

        lda #$00
        sta bullet_active
        lda $d015
        and #%11111101      ; disable sprite 1, keep sprite 0
        sta $d015

no_bullet:

        jmp game_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   ;

; ------------------------------------------------
; Sprite data at $2040 (block 129) — bullet
; ------------------------------------------------
*= $2040
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00

Three pieces fit together here:

  • Read fire. Load $dc00, mask bit 4 with and #%00010000, and bne past the spawn when it's set (not pressed) — the same isolate-and-branch you used for each direction in Unit 2.
  • Spawn, once. If bullet_active is already non-zero a bullet is flying, so we skip. Otherwise we copy the ship's X and Y to the bullet, enable sprite 1 alongside sprite 0 (ora #%00000010 into $d015), and set the flag.
  • Fly it up. Each frame, while the flag is set, we pull the bullet's Y down by four with sec / sbc #$04 — the subtraction from the Primer — and write it to sprite 1's Y register. When it passes the top of the screen we clear the flag and switch sprite 1 off again.

Press fire and the shot launches:

Press fire and a yellow bullet launches from the ship and streaks up the screen, vanishing off the top.

Milestone 2 — fire from anywhere

There's a bug hiding in that spawn, and last unit is exactly what exposes it. Fly the ship over to the right half of the screen — past X=255, where the ninth bit is set — and fire. The bullet appears way over on the left:

Fire with the ship on the right half of the screen and the bullet appears way over on the left — only the low byte of the ship's X was copied, not its ninth bit.

We copied $d000, the ship's low X byte, into the bullet's — but not the ninth bit. When the ship is past 255 its real position is "low byte + 256", and dropping the +256 lands the bullet a screen-width to the left. Sprite 1's ninth bit lives in the same $d010 register as the ship's, just one bit over: bit 0 is sprite 0 (the ship), bit 1 is sprite 1 (the bullet). So at spawn we copy the ship's bit across to the bullet's:

Step 2: carry the ninth bit to the bullet too
+14
156156 sta $d002
157157 lda $d001 ; Ship Y -> bullet Y
158158 sta bullet_y
159+
160+ ; Copy the ship's 9th X bit (bit 0) to the bullet's (bit 1),
161+ ; so a shot fired from the right half spawns under the ship
162+ lda $d010
163+ and #%11111101 ; clear the bullet's 9th bit first
164+ sta $d010
165+ lda $d010
166+ and #$01 ; the ship's 9th bit
167+ asl ; shift it into the bullet's position (bit 1)
168+ ora $d010
169+ sta $d010
159170
160171 ; Enable sprite 1 (keep sprite 0 enabled)
161172 lda $d015
...
187198 lda $d015
188199 and #%11111101 ; disable sprite 1, keep sprite 0
189200 sta $d015
201+ lda $d010
202+ and #%11111101 ; clear the bullet's 9th bit
203+ sta $d010
190204
191205 no_bullet:
192206
The complete program
; Starfield - Unit 4: Fire Button Shoots
; Cumulative steps: step-00 (ship + clamping) -> step-01 (+ a bullet) -> step-02 (+ 9th-bit-aware bullet)
; Assemble: acme -f cbm -o <step>.prg <step>.asm

; ------------------------------------------------
; Zero-page variables
; ------------------------------------------------
bullet_active = $02     ; 0 = no bullet, 1 = active
bullet_y      = $03     ; Bullet Y position

; ------------------------------------------------
; BASIC stub
; ------------------------------------------------
*= $0801
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00

; ------------------------------------------------
; Initialisation
; ------------------------------------------------
*= $080d
        ; Black screen
        lda #$00
        sta $d020           ; Border colour
        sta $d021           ; Background colour

        ; Clear the screen
        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
        lda #$00
        sta $d010           ; sprite high-X bits clear (ship starts under X=256)

        ; Sprite 1 setup (bullet)
        lda #129
        sta $07f9           ; Data pointer (block 129 = $2040)
        lda #$07
        sta $d028           ; Colour (yellow)

        ; Bullet starts inactive
        lda #$00
        sta bullet_active

; ------------------------------------------------
; Game loop — runs once per frame
; ------------------------------------------------
game_loop:
        ; Wait for the raster beam to reach line 255
        ; This syncs our code to the display (~50Hz PAL)
-       lda $d012
        cmp #$ff
        bne -

        ; --- Read joystick and move ship ---

        ; UP (bit 0) — clamp to Y >= 50
        lda $dc00           ; Read joystick port 2
        and #%00000001      ; Isolate bit 0
        bne not_up          ; Bit is 1 = NOT pressed (active low)
        lda $d001
        cmp #52             ; 50 + room for a 2-pixel move
        bcc not_up          ; already at the top — don't move
        dec $d001           ; Move ship up (decrease Y)
        dec $d001           ; 2 pixels per frame
not_up:

        ; DOWN (bit 1) — clamp to Y <= 234
        lda $dc00
        and #%00000010
        bne not_down
        lda $d001
        cmp #233            ; 234 - room for a 2-pixel move
        bcs not_down        ; already at the bottom — don't move
        inc $d001           ; Move ship down (increase Y)
        inc $d001
not_down:

        ; LEFT (bit 2) — 9-bit X, clamp to X >= 24
        lda $dc00
        and #%00000100
        bne not_left
        lda $d010
        and #$01
        bne left_ok         ; high bit set: X >= 256, always safe to go left
        lda $d000
        cmp #26             ; 24 + room for a 2-pixel move
        bcc not_left        ; already at the left edge — don't move
left_ok:
        ; before each step, flip the 9th bit when X is about to wrap $00 -> $ff
        lda $d000
        bne +
        lda $d010
        eor #$01            ; the eor bit-flip from the Primer, on sprite 0's high X bit
        sta $d010
+       dec $d000
        lda $d000
        bne +
        lda $d010
        eor #$01
        sta $d010
+       dec $d000
not_left:

        ; RIGHT (bit 3) — 9-bit X, clamp to X <= 320
        lda $dc00
        and #%00001000
        bne not_right
        lda $d010
        and #$01
        beq right_ok        ; high bit clear: X < 256, always safe to go right
        lda $d000
        cmp #63             ; (320 - 256) - room for a 2-pixel move
        bcs not_right       ; already at the right edge — don't move
right_ok:
        ; after each step, flip the 9th bit when X wraps $ff -> $00
        inc $d000
        bne +
        lda $d010
        eor #$01
        sta $d010
+       inc $d000
        bne +
        lda $d010
        eor #$01
        sta $d010
+
not_right:

        ; --- Fire button (bit 4) ---
        lda $dc00
        and #%00010000
        bne no_fire         ; Bit is 1 = NOT pressed

        ; Only spawn if no bullet is already flying
        lda bullet_active
        bne no_fire

        ; Spawn the bullet at the ship's position
        lda $d000           ; Ship X (low byte) -> bullet X
        sta $d002
        lda $d001           ; Ship Y -> bullet Y
        sta bullet_y

        ; Copy the ship's 9th X bit (bit 0) to the bullet's (bit 1),
        ; so a shot fired from the right half spawns under the ship
        lda $d010
        and #%11111101      ; clear the bullet's 9th bit first
        sta $d010
        lda $d010
        and #$01            ; the ship's 9th bit
        asl                 ; shift it into the bullet's position (bit 1)
        ora $d010
        sta $d010

        ; Enable sprite 1 (keep sprite 0 enabled)
        lda $d015
        ora #%00000010
        sta $d015

        lda #$01
        sta bullet_active

no_fire:

        ; --- Update the bullet ---
        lda bullet_active
        beq no_bullet

        ; Move it up 4 pixels a frame
        lda bullet_y
        sec
        sbc #$04
        sta bullet_y
        sta $d003           ; sprite 1 Y

        ; Gone off the top? (Y < 30) -> remove it
        cmp #$1e
        bcs no_bullet

        lda #$00
        sta bullet_active
        lda $d015
        and #%11111101      ; disable sprite 1, keep sprite 0
        sta $d015
        lda $d010
        and #%11111101      ; clear the bullet's 9th bit
        sta $d010

no_bullet:

        jmp game_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   ;

; ------------------------------------------------
; Sprite data at $2040 (block 129) — bullet
; ------------------------------------------------
*= $2040
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$18,$00   ;        ##
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00
        !byte $00,$00,$00

We clear the bullet's ninth bit first (and #%11111101), then read the ship's bit (and #$01), shift it one place left with asl so it lines up over the bullet's position, and ora it back in. When the bullet despawns we clear its ninth bit again, so the next shot starts clean. Now the same shot from the right launches from under the ship:

With the ninth bit copied across, the same shot from the right launches correctly from under the ship.

When it's wrong, see why

The bullet's behaviour points straight at which piece is off — check these values:

  • Nothing fires. The button read or the guard is the suspect. Watch bullet_active: if it never reaches 1 when you press fire, the bit-4 test is wrong — an inverted branch, or the wrong mask. If it sticks at 1 forever, the despawn never runs and the guard blocks every later shot, so confirm the bullet's Y is being compared against the top-of-screen limit.
  • A solid beam instead of single shots. The guard isn't guarding. bullet_active must be checked before spawning and only cleared when the bullet leaves — spawn regardless and every frame fire is held launches a fresh bullet.
  • Bullet spawns in the wrong place from the right. The ninth bit. Read $d010: bit 0 is the ship, bit 1 is the bullet. After firing from the right half they should match; if bit 1 stays clear while bit 0 is set, the copy isn't happening and the bullet is a screen-width off.

Before and after

We started with an unarmed ship and finished with one that fires a single bullet on demand — and fires it from the right place wherever the ship is sitting, because the shot now carries the same ninth X bit the ship does.

Try this

  • A faster shot. The bullet climbs four pixels a frame (sbc #$04). Make it six or eight and feel how a quicker bullet changes the timing of a shot — how far ahead of a target you have to aim.
  • Rapid fire. Right now the guard allows one bullet at a time. Leave it as is and tap fire repeatedly; then picture what a second bullet sprite would buy you. (We'll come back to multiple shots later — for now, notice how much the single-shot limit shapes the feel.)

What you've learnt

  • Reading the fire button — bit 4 of $dc00, isolated and branched the same way as the direction bits.
  • A spawn guard — one zero-page flag that both tracks the bullet and stops a held button firing a continuous stream.
  • A second sprite in motion — enabling sprite 1, moving it each frame with sbc, and switching it off when it leaves the screen.
  • Carrying the ninth bit between sprites — copying sprite 0's $d010 bit to sprite 1's so a shot fired past X=255 spawns in the right place.

What's next

The shot works, but it's silent. Next you'll give it a voice — program the SID chip to fire a laser sound the moment a bullet launches.