Skip to content
Game 1 Unit 12 of 128 1 hr learning time

Creature Count Display

A Copper screen split creates a status panel at the bottom. The Copper switches bitplane pointers mid-frame, giving the panel its own graphics memory. An 8x8 font draws the saved count.

9% of Exodus

Creatures reach the exit and vanish, but there’s no visible feedback. How many have been saved? In this unit, a Copper screen split divides the display into a game area and a status panel. The panel has its own bitplane and shows the saved count as a digit.

Exodus Unit 12

The dark blue panel sits below the game area. A “0” is drawn in the centre — when a creature reaches the exit, the digit updates. The Copper handles the split automatically every frame.

The Copper Screen Split

The Copper can change any display register mid-frame. By switching the bitplane pointer at a specific scanline, we get two independent display regions — each with its own graphics memory and colours:

            ; --- Panel screen split ---
            ; Wait for panel scanline ($2c + 200 = $E4)
            dc.w    $e401,$fffe
            dc.w    COLOR00,COLOUR_PANEL_BG     ; New background colour
            dc.w    COLOR01,COLOUR_PANEL_FG     ; New foreground colour
            dc.w    BPL1PTH                     ; Switch bitplane pointer
panel_bpl1pth_val:
            dc.w    $0000
            dc.w    BPL1PTL
panel_bpl1ptl_val:
            dc.w    $0000

            dc.w    $ffff,$fffe                 ; End of Copper list

At scanline $E4 (line 200 of the display), the Copper:

  1. Changes COLOR00 to the panel background colour
  2. Changes COLOR01 to the panel foreground colour (white)
  3. Switches BPL1PTH/BPL1PTL to point at the panel bitplane

From this point down, the display reads from panel_bitplane instead of bitplane. The game area and panel are completely independent — drawing in one doesn’t affect the other.

This is the Copper’s defining trick. No CPU effort, no interrupts, no timing code. The Copper just executes its program, synchronised to the beam, every single frame.

Two Bitplanes, One Display

The display now has two separate memory regions:

SCREEN_HEIGHT   equ 200            ; Game area only
BITPLANE_SIZE   equ BYTES_PER_ROW*SCREEN_HEIGHT

PANEL_HEIGHT    equ 56
PANEL_SIZE      equ BYTES_PER_ROW*PANEL_HEIGHT

The game bitplane is 200 lines (8,000 bytes). The panel bitplane is 56 lines (2,240 bytes). They’re allocated separately and patched into the Copper list independently.

The terrain and creatures only interact with the game bitplane. The digit only writes to the panel bitplane. No overlap, no conflicts.

Drawing a Digit

The saved count is drawn as an 8x8 pixel character from a simple font:

;──────────────────────────────────────────────────────────────
; draw_saved_count — Draw the saved count digit in the panel
;──────────────────────────────────────────────────────────────
draw_saved_count:
            ; Clear the digit area first (8x8 pixels = 1 byte x 8 rows)
            lea     panel_bitplane,a0
            add.w   #PANEL_DIGIT_Y*BYTES_PER_ROW,a0
            add.w   #PANEL_DIGIT_X/8,a0
            moveq   #7,d3
.clear:
            move.b  #0,(a0)
            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.clear

            ; Get digit font data
            move.w  saved_count,d0
            cmp.w   #9,d0
            ble.s   .ok
            move.w  #9,d0           ; Clamp to single digit
.ok:
            lsl.w   #3,d0           ; * 8 bytes per glyph
            lea     font_digits,a1
            add.w   d0,a1

            ; Draw the digit
            lea     panel_bitplane,a0
            add.w   #PANEL_DIGIT_Y*BYTES_PER_ROW,a0
            add.w   #PANEL_DIGIT_X/8,a0
            moveq   #7,d3
.draw:
            move.b  (a1)+,(a0)
            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.draw
            rts

The font is a table of 10 glyphs (digits 0-9), each 8 bytes — one byte per row, one bit per pixel. To draw a digit: calculate the glyph offset (digit * 8), then copy 8 bytes into the panel bitplane at the digit position.

Before drawing, the old digit is cleared by writing zeros. This prevents leftover pixels when the count changes from, say, 1 to 2.

The font data is defined directly in the assembly:

font_digits:
            ; 0
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01101110
            ; ...8 bytes per glyph...

Each byte is one row of 8 pixels. %00111100 draws the top of a “0” — bits 5-2 are set, creating the rounded top. This is the simplest possible bitmap font.

Redrawing on Change

The digit only needs redrawing when the count changes. The main loop tracks the previous count:

            move.w  saved_count,d6      ; Remember count before update
            ; ...process creatures...
            move.w  saved_count,d0
            cmp.w   d6,d0
            beq.s   .no_redraw
            bsr     draw_saved_count
.no_redraw:

Comparing the count before and after the creature loop avoids unnecessary redraws. Drawing is cheap, but this pattern scales — when there are more display elements to update, only redrawing what changed matters.

Experiment: Panel Colours

COLOUR_PANEL_BG     equ $0000       ; Black panel
COLOUR_PANEL_FG     equ $0F00       ; Red digits

The panel colours are independent of the game colours. Try a bright panel to make it stand out, or a subtle one that blends with the scene.

The Complete Code

;──────────────────────────────────────────────────────────────
; EXODUS - A terrain puzzle for the Commodore Amiga
; Unit 12: Creature Count Display
;
; A Copper screen split creates a status panel at the bottom.
; The saved count is drawn as a digit in the panel bitplane.
;──────────────────────────────────────────────────────────────

;══════════════════════════════════════════════════════════════
; TWEAKABLE VALUES
;══════════════════════════════════════════════════════════════

COLOUR_SKY_DEEP     equ $0016
COLOUR_SKY_UPPER    equ $0038
COLOUR_SKY_MID      equ $005B
COLOUR_SKY_LOWER    equ $007D
COLOUR_SKY_HORIZON  equ $009E

COLOUR_TERRAIN      equ $0741
COLOUR_PANEL_BG     equ $0223
COLOUR_PANEL_FG     equ $0FFF

COLOUR_SPR0_1       equ $0FFF
COLOUR_SPR0_2       equ $0F80
COLOUR_SPR0_3       equ $0000

CREATURE_SPEED      equ 1
FALL_SPEED          equ 2

FOOT_OFFSET_X       equ 8
FOOT_OFFSET_Y       equ 12

STEP_PERIOD         equ 400
STEP_VOLUME         equ 48

NUM_CREATURES       equ 3

; Creature data table offsets
CR_X                equ 0
CR_Y                equ 2
CR_DX               equ 4
CR_STATE            equ 6
CR_STEP             equ 8
CR_SIZE             equ 10

STATE_WALKING       equ 0
STATE_FALLING       equ 1
STATE_SAVED         equ 2

; Exit zone
EXIT_X              equ 280
EXIT_Y              equ 108
EXIT_W              equ 24
EXIT_H              equ 12

; Panel
PANEL_Y             equ 200         ; Scanline where panel starts
PANEL_DIGIT_X       equ 152        ; X position for digit (byte-aligned: 152/8=19)
PANEL_DIGIT_Y       equ 4          ; Y offset within panel bitplane

; Terrain
GROUND_L_X          equ 0
GROUND_L_Y          equ 152
GROUND_L_W          equ 128
GROUND_L_H          equ 48

GROUND_R_X          equ 128
GROUND_R_Y          equ 120
GROUND_R_W          equ 192
GROUND_R_H          equ 80

PLATFORM_X          equ 24
PLATFORM_Y          equ 104
PLATFORM_W          equ 72
PLATFORM_H          equ 8

;══════════════════════════════════════════════════════════════
; DISPLAY CONSTANTS
;══════════════════════════════════════════════════════════════

SCREEN_WIDTH    equ 320
SCREEN_HEIGHT   equ 200            ; Game area only (panel below)
BYTES_PER_ROW   equ SCREEN_WIDTH/8
BITPLANE_SIZE   equ BYTES_PER_ROW*SCREEN_HEIGHT

PANEL_HEIGHT    equ 56
PANEL_SIZE      equ BYTES_PER_ROW*PANEL_HEIGHT

SPRITE_HEIGHT   equ 12
STEP_INTERVAL   equ 8

;══════════════════════════════════════════════════════════════
; HARDWARE REGISTERS
;══════════════════════════════════════════════════════════════

CUSTOM      equ $dff000
DMACON      equ $096
INTENA      equ $09a
INTREQ      equ $09c
COP1LC      equ $080
COPJMP1     equ $088
BPLCON0     equ $100
BPLCON1     equ $102
BPLCON2     equ $104
BPL1MOD     equ $108
DIWSTRT     equ $08e
DIWSTOP     equ $090
DDFSTRT     equ $092
DDFSTOP     equ $094
BPL1PTH     equ $0e0
BPL1PTL     equ $0e2
SPR0PTH     equ $120
SPR0PTL     equ $122
SPR1PTH     equ $124
SPR1PTL     equ $126
SPR2PTH     equ $128
SPR2PTL     equ $12a
COLOR00     equ $180
COLOR01     equ $182
COLOR17     equ $1a2
COLOR18     equ $1a4
COLOR19     equ $1a6
VPOSR       equ $004

AUD0LC      equ $0a0
AUD0LEN     equ $0a4
AUD0PER     equ $0a6
AUD0VOL     equ $0a8

;══════════════════════════════════════════════════════════════
; CODE (Chip RAM)
;══════════════════════════════════════════════════════════════

            section code,code_c

start:
            lea     CUSTOM,a5

            move.w  #$7fff,INTENA(a5)
            move.w  #$7fff,INTREQ(a5)
            move.w  #$7fff,DMACON(a5)

            ; --- Initialise creatures ---
            lea     creatures,a0

            move.w  #80,CR_X(a0)
            move.w  #92,CR_Y(a0)
            move.w  #CREATURE_SPEED,CR_DX(a0)
            move.w  #STATE_WALKING,CR_STATE(a0)
            move.w  #0,CR_STEP(a0)

            move.w  #16,CR_X+CR_SIZE(a0)
            move.w  #140,CR_Y+CR_SIZE(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE(a0)
            move.w  #4,CR_STEP+CR_SIZE(a0)

            move.w  #160,CR_X+CR_SIZE*2(a0)
            move.w  #108,CR_Y+CR_SIZE*2(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE*2(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE*2(a0)
            move.w  #2,CR_STEP+CR_SIZE*2(a0)

            move.w  #0,saved_count

            ; --- Draw terrain ---
            move.w  #GROUND_L_X,d0
            move.w  #GROUND_L_Y,d1
            move.w  #GROUND_L_W,d2
            move.w  #GROUND_L_H,d3
            bsr     draw_rect

            move.w  #GROUND_R_X,d0
            move.w  #GROUND_R_Y,d1
            move.w  #GROUND_R_W,d2
            move.w  #GROUND_R_H,d3
            bsr     draw_rect

            move.w  #PLATFORM_X,d0
            move.w  #PLATFORM_Y,d1
            move.w  #PLATFORM_W,d2
            move.w  #PLATFORM_H,d3
            bsr     draw_rect

            ; Exit marker
            move.w  #EXIT_X,d0
            move.w  #EXIT_Y,d1
            move.w  #EXIT_W,d2
            move.w  #1,d3
            bsr     draw_rect
            move.w  #EXIT_X,d0
            move.w  #EXIT_Y+EXIT_H-1,d1
            move.w  #EXIT_W,d2
            move.w  #1,d3
            bsr     draw_rect

            ; --- Draw initial digit in panel ---
            bsr     draw_saved_count

            ; --- Patch bitplane pointer ---
            lea     bitplane,a0
            move.l  a0,d0
            swap    d0
            lea     bpl1pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     bpl1ptl_val,a1
            move.w  d0,(a1)

            ; --- Patch panel bitplane pointer ---
            lea     panel_bitplane,a0
            move.l  a0,d0
            swap    d0
            lea     panel_bpl1pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     panel_bpl1ptl_val,a1
            move.w  d0,(a1)

            ; --- Patch sprite pointers ---
            lea     sprite0_data,a0
            move.l  a0,d0
            swap    d0
            lea     spr0pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr0ptl_val,a1
            move.w  d0,(a1)

            lea     sprite1_data,a0
            move.l  a0,d0
            swap    d0
            lea     spr1pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr1ptl_val,a1
            move.w  d0,(a1)

            lea     sprite2_data,a0
            move.l  a0,d0
            swap    d0
            lea     spr2pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr2ptl_val,a1
            move.w  d0,(a1)

            ; --- Install Copper list ---
            lea     copperlist,a0
            move.l  a0,COP1LC(a5)
            move.w  d0,COPJMP1(a5)

            ; --- Enable DMA ---
            move.w  #$83a1,DMACON(a5)

            ; === Main Loop ===
mainloop:
            move.l  #$1ff00,d1
.vbwait:
            move.l  VPOSR(a5),d0
            and.l   d1,d0
            bne.s   .vbwait

            ; --- Process all creatures ---
            lea     creatures,a2
            moveq   #NUM_CREATURES-1,d7

.creature_loop:
            move.w  saved_count,d6      ; Remember count before update
            bsr     update_creature
            add.w   #CR_SIZE,a2
            dbra    d7,.creature_loop

            ; --- Redraw count if changed ---
            move.w  saved_count,d0
            cmp.w   d6,d0
            beq.s   .no_redraw
            bsr     draw_saved_count
.no_redraw:

            ; --- Update sprites ---
            lea     creatures,a2
            lea     sprite0_data,a0
            bsr     write_sprite_pos

            lea     creatures+CR_SIZE,a2
            lea     sprite1_data,a0
            bsr     write_sprite_pos

            lea     creatures+CR_SIZE*2,a2
            lea     sprite2_data,a0
            bsr     write_sprite_pos

            btst    #6,$bfe001
            bne     mainloop

.halt:
            bra.s   .halt

;──────────────────────────────────────────────────────────────
; update_creature — Update one creature (A2 = creature data)
;──────────────────────────────────────────────────────────────
update_creature:
            move.w  CR_STATE(a2),d0
            cmp.w   #STATE_SAVED,d0
            beq     .done
            cmp.w   #STATE_FALLING,d0
            beq     .do_fall

            ; --- Walking ---
            move.w  CR_X(a2),d0
            add.w   CR_DX(a2),d0
            add.w   #FOOT_OFFSET_X,d0
            move.w  CR_Y(a2),d1
            add.w   #FOOT_OFFSET_Y,d1
            bsr     check_pixel

            tst.b   d0
            beq.s   .walk_no_floor

            move.w  CR_X(a2),d0
            add.w   CR_DX(a2),d0
            move.w  d0,CR_X(a2)

            bsr     check_exit

            move.w  CR_STEP(a2),d0
            subq.w  #1,d0
            bgt.s   .no_step
            bsr     play_step
            move.w  #STEP_INTERVAL,d0
.no_step:
            move.w  d0,CR_STEP(a2)

            move.w  CR_X(a2),d0
            add.w   #FOOT_OFFSET_X,d0
            move.w  CR_Y(a2),d1
            add.w   #FOOT_OFFSET_Y,d1
            bsr     check_pixel

            tst.b   d0
            bne.s   .done
            move.w  #STATE_FALLING,CR_STATE(a2)
            bra.s   .done

.walk_no_floor:
            neg.w   CR_DX(a2)
            bra.s   .done

            ; --- Falling ---
.do_fall:
            move.w  CR_Y(a2),d0
            add.w   #FALL_SPEED,d0

            cmp.w   #SCREEN_HEIGHT-SPRITE_HEIGHT,d0
            blt.s   .fall_ok
            move.w  #SCREEN_HEIGHT-SPRITE_HEIGHT,d0
            move.w  d0,CR_Y(a2)
            move.w  #STATE_WALKING,CR_STATE(a2)
            bra.s   .done

.fall_ok:
            move.w  d0,CR_Y(a2)

            move.w  CR_X(a2),d0
            add.w   #FOOT_OFFSET_X,d0
            move.w  CR_Y(a2),d1
            add.w   #FOOT_OFFSET_Y,d1
            bsr     check_pixel

            tst.b   d0
            beq.s   .done

            move.w  #STATE_WALKING,CR_STATE(a2)

.done:
            rts

;──────────────────────────────────────────────────────────────
; check_exit — Test if creature is inside the exit zone
;──────────────────────────────────────────────────────────────
check_exit:
            move.w  CR_X(a2),d0
            add.w   #FOOT_OFFSET_X,d0
            cmp.w   #EXIT_X,d0
            blt.s   .not_in
            cmp.w   #EXIT_X+EXIT_W,d0
            bge.s   .not_in

            move.w  CR_Y(a2),d1
            add.w   #FOOT_OFFSET_Y,d1
            cmp.w   #EXIT_Y,d1
            blt.s   .not_in
            cmp.w   #EXIT_Y+EXIT_H,d1
            bge.s   .not_in

            move.w  #STATE_SAVED,CR_STATE(a2)
            addq.w  #1,saved_count
            move.w  #0,CR_Y(a2)

.not_in:
            rts

;──────────────────────────────────────────────────────────────
; draw_saved_count — Draw the saved count digit in the panel
;──────────────────────────────────────────────────────────────
draw_saved_count:
            ; Clear the digit area first (8x8 pixels = 1 byte x 8 rows)
            lea     panel_bitplane,a0
            add.w   #PANEL_DIGIT_Y*BYTES_PER_ROW,a0
            add.w   #PANEL_DIGIT_X/8,a0
            moveq   #7,d3
.clear:
            move.b  #0,(a0)
            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.clear

            ; Get digit font data
            move.w  saved_count,d0
            cmp.w   #9,d0
            ble.s   .ok
            move.w  #9,d0           ; Clamp to single digit
.ok:
            lsl.w   #3,d0           ; * 8 bytes per glyph
            lea     font_digits,a1
            add.w   d0,a1

            ; Draw the digit
            lea     panel_bitplane,a0
            add.w   #PANEL_DIGIT_Y*BYTES_PER_ROW,a0
            add.w   #PANEL_DIGIT_X/8,a0
            moveq   #7,d3
.draw:
            move.b  (a1)+,(a0)
            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.draw
            rts

;──────────────────────────────────────────────────────────────
; write_sprite_pos — Write position from creature data to sprite
;──────────────────────────────────────────────────────────────
write_sprite_pos:
            move.w  CR_STATE(a2),d0
            cmp.w   #STATE_SAVED,d0
            beq.s   .hide

            move.w  CR_Y(a2),d0
            add.w   #$2c,d0
            move.w  d0,d1
            add.w   #SPRITE_HEIGHT,d1

            move.w  CR_X(a2),d2
            add.w   #$80,d2

            move.b  d0,d3
            lsl.w   #8,d3
            move.w  d2,d4
            lsr.w   #1,d4
            or.b    d4,d3
            move.w  d3,(a0)+

            move.b  d1,d3
            lsl.w   #8,d3
            moveq   #0,d4
            btst    #8,d0
            beq.s   .no_vs8
            bset    #2,d4
.no_vs8:
            btst    #8,d1
            beq.s   .no_ve8
            bset    #1,d4
.no_ve8:
            btst    #0,d2
            beq.s   .no_h0
            bset    #0,d4
.no_h0:
            or.b    d4,d3
            move.w  d3,(a0)
            rts

.hide:
            move.w  #0,(a0)+
            move.w  #0,(a0)
            rts

;──────────────────────────────────────────────────────────────
; play_step — Trigger the footstep sample on Paula channel 0
;──────────────────────────────────────────────────────────────
play_step:
            lea     CUSTOM,a6
            lea     step_sample,a0
            move.l  a0,AUD0LC(a6)
            move.w  #STEP_SAMPLE_LEN/2,AUD0LEN(a6)
            move.w  #STEP_PERIOD,AUD0PER(a6)
            move.w  #STEP_VOLUME,AUD0VOL(a6)
            move.w  #$8201,DMACON(a6)
            rts

;──────────────────────────────────────────────────────────────
; check_pixel — Test a single pixel in the bitplane
;──────────────────────────────────────────────────────────────
check_pixel:
            lea     bitplane,a0
            mulu    #BYTES_PER_ROW,d1
            add.l   d1,a0
            move.w  d0,d2
            lsr.w   #3,d2
            add.w   d2,a0
            not.w   d0
            and.w   #7,d0
            btst    d0,(a0)
            sne     d0
            and.b   #1,d0
            rts

;──────────────────────────────────────────────────────────────
; draw_rect — Fill a byte-aligned rectangle in the bitplane
;──────────────────────────────────────────────────────────────
draw_rect:
            lea     bitplane,a0
            mulu    #BYTES_PER_ROW,d1
            add.l   d1,a0
            lsr.w   #3,d0
            add.w   d0,a0
            lsr.w   #3,d2

            subq.w  #1,d3
.row:
            move.w  d2,d5
            subq.w  #1,d5
            move.l  a0,a1
.col:
            move.b  #$ff,(a1)+
            dbra    d5,.col

            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.row
            rts

;══════════════════════════════════════════════════════════════
; FONT — 8x8 digit glyphs (0-9)
;══════════════════════════════════════════════════════════════

font_digits:
            ; 0
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01101110
            dc.b    %01110110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000
            ; 1
            dc.b    %00011000
            dc.b    %00111000
            dc.b    %00011000
            dc.b    %00011000
            dc.b    %00011000
            dc.b    %00011000
            dc.b    %01111110
            dc.b    %00000000
            ; 2
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %00000110
            dc.b    %00001100
            dc.b    %00110000
            dc.b    %01100000
            dc.b    %01111110
            dc.b    %00000000
            ; 3
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %00000110
            dc.b    %00011100
            dc.b    %00000110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000
            ; 4
            dc.b    %00001100
            dc.b    %00011100
            dc.b    %00101100
            dc.b    %01001100
            dc.b    %01111110
            dc.b    %00001100
            dc.b    %00001100
            dc.b    %00000000
            ; 5
            dc.b    %01111110
            dc.b    %01100000
            dc.b    %01111100
            dc.b    %00000110
            dc.b    %00000110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000
            ; 6
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01100000
            dc.b    %01111100
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000
            ; 7
            dc.b    %01111110
            dc.b    %01100110
            dc.b    %00001100
            dc.b    %00011000
            dc.b    %00011000
            dc.b    %00011000
            dc.b    %00011000
            dc.b    %00000000
            ; 8
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000
            ; 9
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111110
            dc.b    %00000110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000

;══════════════════════════════════════════════════════════════
; COPPER LIST
;══════════════════════════════════════════════════════════════

copperlist:
            dc.w    DIWSTRT,$2c81
            dc.w    DIWSTOP,$2cc1
            dc.w    DDFSTRT,$0038
            dc.w    DDFSTOP,$00d0

            dc.w    BPLCON0,$1200
            dc.w    BPLCON1,$0000
            dc.w    BPLCON2,$0000
            dc.w    BPL1MOD,$0000

            dc.w    BPL1PTH
bpl1pth_val:
            dc.w    $0000
            dc.w    BPL1PTL
bpl1ptl_val:
            dc.w    $0000

            dc.w    SPR0PTH
spr0pth_val:
            dc.w    $0000
            dc.w    SPR0PTL
spr0ptl_val:
            dc.w    $0000

            dc.w    SPR1PTH
spr1pth_val:
            dc.w    $0000
            dc.w    SPR1PTL
spr1ptl_val:
            dc.w    $0000

            dc.w    SPR2PTH
spr2pth_val:
            dc.w    $0000
            dc.w    SPR2PTL
spr2ptl_val:
            dc.w    $0000

            dc.w    COLOR00,COLOUR_SKY_DEEP
            dc.w    COLOR01,COLOUR_TERRAIN
            dc.w    COLOR17,COLOUR_SPR0_1
            dc.w    COLOR18,COLOUR_SPR0_2
            dc.w    COLOR19,COLOUR_SPR0_3

            dc.w    $3401,$fffe
            dc.w    COLOR00,COLOUR_SKY_UPPER
            dc.w    $4401,$fffe
            dc.w    COLOR00,COLOUR_SKY_MID
            dc.w    $5401,$fffe
            dc.w    COLOR00,COLOUR_SKY_LOWER
            dc.w    $6001,$fffe
            dc.w    COLOR00,COLOUR_SKY_HORIZON
            dc.w    $6801,$fffe
            dc.w    COLOR00,$0000

            ; --- Panel screen split ---
            ; Wait for panel scanline ($2c + 200 = $E4)
            dc.w    $e401,$fffe
            dc.w    COLOR00,COLOUR_PANEL_BG
            dc.w    COLOR01,COLOUR_PANEL_FG
            dc.w    BPL1PTH
panel_bpl1pth_val:
            dc.w    $0000
            dc.w    BPL1PTL
panel_bpl1ptl_val:
            dc.w    $0000

            dc.w    $ffff,$fffe

;══════════════════════════════════════════════════════════════
; SPRITE DATA
;══════════════════════════════════════════════════════════════

            even
sprite0_data:
            dc.w    $0000,$0000
            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0000111111110000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001101110111000,%0001111111111000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000111001110000,%0000011111100000
            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0001111111111000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000110000110000,%0000000000000000
            dc.w    %0000110000110000,%0000000000000000
            dc.w    $0000,$0000

            even
sprite1_data:
            dc.w    $0000,$0000
            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0000111111110000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001101110111000,%0001111111111000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000111001110000,%0000011111100000
            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0001111111111000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000110000110000,%0000000000000000
            dc.w    %0000110000110000,%0000000000000000
            dc.w    $0000,$0000

            even
sprite2_data:
            dc.w    $0000,$0000
            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0000111111110000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001101110111000,%0001111111111000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000111001110000,%0000011111100000
            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0001111111111000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000110000110000,%0000000000000000
            dc.w    %0000110000110000,%0000000000000000
            dc.w    $0000,$0000

;══════════════════════════════════════════════════════════════
; STEP SAMPLE
;══════════════════════════════════════════════════════════════

            even
step_sample:
            dc.b    $60,$40,$20,$10,$08,$04,$02,$01
            dc.b    $fe,$fc,$f8,$f0,$e0,$d0,$c0,$b0
            dc.b    $50,$30,$18,$0c,$06,$03,$01,$00
            dc.b    $ff,$fd,$fa,$f4,$e8,$d8,$c8,$b8
STEP_SAMPLE_LEN equ *-step_sample

;══════════════════════════════════════════════════════════════
; VARIABLES
;══════════════════════════════════════════════════════════════

            even
creatures:
            dcb.b   CR_SIZE*NUM_CREATURES,0

saved_count:
            dc.w    0

;══════════════════════════════════════════════════════════════
; BITPLANE DATA
;══════════════════════════════════════════════════════════════

            even
bitplane:
            dcb.b   BITPLANE_SIZE,0

            even
panel_bitplane:
            dcb.b   PANEL_SIZE,0

If It Doesn’t Work

  • No panel visible? Check the Copper WAIT value. For the panel at line 200, the wait position is $E4 (display offset $2C + 200). The wait word is $E401.
  • Panel shows game graphics? The panel bitplane pointer isn’t being patched. Make sure panel_bpl1pth_val and panel_bpl1ptl_val labels are inside the Copper list, after the WAIT.
  • Digit doesn’t appear? Check that PANEL_DIGIT_X is byte-aligned (divisible by 8). The font draw routine writes whole bytes, so non-aligned positions shift the digit.
  • Digit doesn’t update? Make sure draw_saved_count reads saved_count and is called when the count changes.

What You’ve Learnt

  • Copper screen splits — the Copper can change display registers mid-frame. By switching bitplane pointers at a specific scanline, different screen regions show different graphics.
  • Dual bitplanes — separate memory regions for separate display areas. Each can be drawn to independently with no conflicts.
  • Bitmap fonts — character data stored as byte arrays (one byte per row). Drawing is a simple byte-copy loop.
  • Conditional redraw — comparing state before and after processing to decide whether display updates are needed. Avoids unnecessary work.

What’s Next

The digit shows how many creatures have been saved, but there’s no goal number. In Unit 13, we’ll add a score display showing “saved / total” — and the count updates live as creatures reach the exit.