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

Level Complete

The game detects when all creatures have been saved or lost. A new STATE_LOST handles creatures that fall off the screen, and the panel changes colour to show the result — green for a win, red for a loss.

11% of Exodus

Creatures walk, fall, and reach the exit — but the game never ends. Some creatures fall off the bottom of the screen and vanish into nothing. In this unit, we add a proper end condition: when every creature has been saved or lost, the level is complete.

Exodus Unit 14

The panel shows “0/3” and the game runs until all three creatures are accounted for. When the level ends, the panel turns green (all saved) or red (some lost).

Lost Creatures

Until now, creatures that fell below the screen height simply resumed walking — a placeholder behaviour. Now they get a proper state:

STATE_LOST          equ 3

When a falling creature’s y position exceeds SCREEN_HEIGHT - SPRITE_HEIGHT, it transitions to STATE_LOST. Its sprite is hidden (just like saved creatures) and a lost_count variable increments:

.do_fall:
            move.w  CR_Y(a2),d0
            add.w   #FALL_SPEED,d0
            cmp.w   #SCREEN_HEIGHT-SPRITE_HEIGHT,d0
            blt.s   .fall_ok
            ; Fell below screen — creature is lost
            move.w  #STATE_LOST,CR_STATE(a2)
            move.w  #0,CR_Y(a2)
            addq.w  #1,lost_count
            bra.s   .done

The creature’s y is reset to 0 and its state prevents any further processing. write_sprite_pos already hides sprites for saved creatures — it now also hides lost ones.

Level Complete Detection

Every frame, the main loop checks whether all creatures have been accounted for:

            ; --- Check level complete ---
            move.w  game_state,d0
            cmp.w   #GAME_COMPLETE,d0
            beq.s   .skip_check
            move.w  saved_count,d0
            add.w   lost_count,d0
            cmp.w   #NUM_CREATURES,d0
            blt.s   .skip_check
            ; All creatures accounted for
            move.w  #GAME_COMPLETE,game_state
            bsr     draw_result
.skip_check:

The check is simple arithmetic: saved_count + lost_count >= NUM_CREATURES. Every creature must end up in one category or the other. Once the total matches, the game state changes to GAME_COMPLETE and the result is displayed.

The game_state check at the top prevents draw_result from being called every frame after completion — it only fires once.

Runtime Copper Patching

The result is communicated visually by changing the panel’s background colour. This works by writing directly to a value inside the Copper list:

;──────────────────────────────────────────────────────────────
; draw_result — Change panel colour based on outcome
;──────────────────────────────────────────────────────────────
draw_result:
            move.w  saved_count,d0
            cmp.w   #NUM_CREATURES,d0
            bne.s   .not_win
            ; All saved — green
            move.w  #COLOUR_WIN,panel_bg_val
            rts
.not_win:
            ; Some lost — red
            move.w  #COLOUR_LOSE,panel_bg_val
            rts

The Copper list contains a labelled value for the panel background:

            dc.w    COLOR00
panel_bg_val:
            dc.w    COLOUR_PANEL_BG     ; Patched at runtime

Writing a new colour value to panel_bg_val changes what the Copper outputs next frame. No register writes, no timing tricks — the Copper reads the new value on its next pass. Green ($0362) for a win, red ($0622) for a loss.

This is runtime Copper patching: the CPU modifies the Copper program while it runs. The Copper re-reads its list every frame, so changes take effect immediately.

Game States

The game now has two states:

GAME_PLAYING        equ 0
GAME_COMPLETE       equ 1

During GAME_PLAYING, creatures are processed normally. When GAME_COMPLETE is set, the creature loop still runs (sprites need position updates) but the level-complete check is skipped. The result colours remain on screen until the program is restarted.

The Complete Code

;──────────────────────────────────────────────────────────────
; EXODUS - A terrain puzzle for the Commodore Amiga
; Unit 14: Level Complete
;
; The game detects when all creatures have been saved or lost.
; A result message appears in the panel when the level ends.
;──────────────────────────────────────────────────────────────

;══════════════════════════════════════════════════════════════
; 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_WIN          equ $0362
COLOUR_LOSE         equ $0622

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
STATE_LOST          equ 3

; Game states
GAME_PLAYING        equ 0
GAME_COMPLETE       equ 1

; 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_SAVED_X       equ 144         ; X position for saved digit (byte-aligned)
PANEL_SLASH_X       equ 152         ; X position for slash
PANEL_TOTAL_X       equ 160         ; X position for total digit
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
            move.w  #0,lost_count
            move.w  #GAME_PLAYING,game_state

            ; --- 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:

            ; --- Check level complete ---
            move.w  game_state,d0
            cmp.w   #GAME_COMPLETE,d0
            beq.s   .skip_check
            move.w  saved_count,d0
            add.w   lost_count,d0
            cmp.w   #NUM_CREATURES,d0
            blt.s   .skip_check
            ; All creatures accounted for
            move.w  #GAME_COMPLETE,game_state
            bsr     draw_result
.skip_check:

            ; --- 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_LOST,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
            ; Fell below screen — creature is lost
            move.w  #STATE_LOST,CR_STATE(a2)
            move.w  #0,CR_Y(a2)
            addq.w  #1,lost_count
            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_score — Draw "saved / total" in the panel
;──────────────────────────────────────────────────────────────
draw_saved_count:
            ; Draw saved digit
            move.w  saved_count,d0
            move.w  #PANEL_SAVED_X,d1
            bsr     draw_panel_digit

            ; Draw slash
            lea     font_slash,a1
            move.w  #PANEL_SLASH_X,d1
            bsr     draw_panel_glyph

            ; Draw total digit
            move.w  #NUM_CREATURES,d0
            move.w  #PANEL_TOTAL_X,d1
            bsr     draw_panel_digit
            rts

;──────────────────────────────────────────────────────────────
; draw_panel_digit — Draw digit D0 at x position D1
;──────────────────────────────────────────────────────────────
draw_panel_digit:
            cmp.w   #9,d0
            ble.s   .ok
            move.w  #9,d0
.ok:
            lsl.w   #3,d0           ; * 8 bytes per glyph
            lea     font_digits,a1
            add.w   d0,a1
            ; Fall through to draw_panel_glyph

;──────────────────────────────────────────────────────────────
; draw_panel_glyph — Draw 8x8 glyph at A1, x position D1
;──────────────────────────────────────────────────────────────
draw_panel_glyph:
            lea     panel_bitplane,a0
            add.w   #PANEL_DIGIT_Y*BYTES_PER_ROW,a0
            move.w  d1,d2
            lsr.w   #3,d2
            add.w   d2,a0
            moveq   #7,d3
.draw:
            move.b  (a1)+,(a0)
            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.draw
            rts

;──────────────────────────────────────────────────────────────
; draw_result — Change panel colour based on outcome
;──────────────────────────────────────────────────────────────
draw_result:
            move.w  saved_count,d0
            cmp.w   #NUM_CREATURES,d0
            bne.s   .not_win
            ; All saved — green
            move.w  #COLOUR_WIN,panel_bg_val
            rts
.not_win:
            ; Some lost — red
            move.w  #COLOUR_LOSE,panel_bg_val
            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
            cmp.w   #STATE_LOST,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

font_slash:
            dc.b    %00000010
            dc.b    %00000100
            dc.b    %00001000
            dc.b    %00010000
            dc.b    %00100000
            dc.b    %01000000
            dc.b    %10000000
            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
panel_bg_val:
            dc.w    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
lost_count:
            dc.w    0
game_state:
            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

  • Creatures vanish but level never completes? Check that lost_count increments in the falling code. The level-complete check needs saved_count + lost_count >= NUM_CREATURES.
  • Panel doesn’t change colour? Make sure panel_bg_val is a label inside the Copper list, positioned after the panel WAIT but before the end marker. The CPU writes to this address directly.
  • Always shows red/green immediately? Check that game_state starts at GAME_PLAYING (0) and the level-complete check tests it before acting.

What You’ve Learnt

  • Lost state — creatures that fall off screen transition to STATE_LOST, are hidden, and counted separately from saved creatures.
  • Level completion — a simple arithmetic check (saved + lost >= total) determines when all creatures are accounted for.
  • Runtime Copper patching — writing values directly into the Copper list changes display properties next frame. The Copper re-reads its program every frame.
  • Game statesGAME_PLAYING and GAME_COMPLETE control which parts of the main loop execute. State machines scale naturally as game logic grows.

What’s Next

The game plays and ends, but there’s no way to start it. In Unit 15, we’ll add a title screen — the game begins with “EXODUS” displayed and waits for the fire button before starting.