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

Integration and Polish

The Phase 1 finale: five creatures use hardware sprites 0-4, staggered across the terrain. The Copper list grows to patch five sprite pointers. The complete game loop runs from title to result and back.

13% of Exodus

Three creatures proved the mechanics. Five creatures prove the design. In this unit — the Phase 1 finale — we increase to five hardware sprites, stagger the spawn positions, and patch all five sprite pointers through the Copper list.

Exodus Unit 16

The title screen appears first. Press fire, and five creatures begin their journey across the terrain. The score now reads “0/5” — there are more to save, and the terrain hasn’t changed.

Five Hardware Sprites

The Amiga’s OCS chipset provides eight hardware sprites. We’ve been using three (SPR0-SPR2). Now we add SPR3 and SPR4:

SPR3PTH     equ $12c
SPR3PTL     equ $12e
SPR4PTH     equ $130
SPR4PTL     equ $132

Each sprite needs its own data block and Copper list entries. The Copper patches all five pointers during initialisation:

            lea     sprite3_data,a0
            move.l  a0,d0
            swap    d0
            lea     spr3pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr3ptl_val,a1
            move.w  d0,(a1)

The pattern is identical for every sprite — load address, split into high/low words, write to the Copper list labels. Five sprites means five copies of this sequence.

Staggered Spawning

Five creatures need different starting positions and step timers to avoid clumping:

NUM_CREATURES       equ 5

            ; Creature 0: starts on platform
            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)

            ; Creature 1: starts on left ground
            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)

            ; Creature 2: starts on right ground
            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)

            ; Creature 3: starts on left ground (staggered)
            move.w  #48,CR_X+CR_SIZE*3(a0)
            move.w  #140,CR_Y+CR_SIZE*3(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE*3(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE*3(a0)
            move.w  #6,CR_STEP+CR_SIZE*3(a0)

            ; Creature 4: starts on right ground (staggered)
            move.w  #200,CR_X+CR_SIZE*4(a0)
            move.w  #108,CR_Y+CR_SIZE*4(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE*4(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE*4(a0)
            move.w  #3,CR_STEP+CR_SIZE*4(a0)

Creatures 0 and 2 start on higher terrain (the platform and right ground). Creatures 1, 3, and 4 are spread across different x positions. The CR_STEP values are staggered so footstep sounds don’t all trigger on the same frame.

The same initialisation appears in both the startup code and start_game — when the game restarts, all five creatures return to their starting positions.

Five Sprite Updates

The main loop updates sprite positions for all five creatures after the processing loop:

            ; Update sprites for all 5 creatures
            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

            lea     creatures+CR_SIZE*3,a2
            lea     sprite3_data,a0
            bsr     write_sprite_pos

            lea     creatures+CR_SIZE*4,a2
            lea     sprite4_data,a0
            bsr     write_sprite_pos

Each creature’s data is paired with its sprite data block. write_sprite_pos handles the position encoding — or hides the sprite if the creature is saved or lost.

The Copper List

The Copper list now includes entries for all five sprites:

            dc.w    SPR0PTH
spr0pth_val: dc.w   $0000
            dc.w    SPR0PTL
spr0ptl_val: dc.w   $0000
            ; ...same for SPR1, SPR2, SPR3, SPR4...

All five pointer pairs sit in the Copper list before the colour registers. The Copper loads them every frame, so the DMA hardware always knows where to fetch sprite data from.

What Phase 1 Built

Sixteen units have constructed a complete game from nothing:

  • Copper list — defines the entire display: bitplane pointers, colours, screen split, sprite pointers, gradient sky
  • Bitplane graphics — terrain drawn directly in memory, pixel collision for gameplay
  • Hardware sprites — five creatures with position encoding and visibility control
  • Creature AI — walking, falling, direction reversal, exit detection, lost detection
  • Audio — Paula footstep samples with timing control
  • Score display — Copper screen split with bitmap font rendering
  • Game loop — title, play, and result states with fire button transitions

The program boots from floppy, takes over the hardware, and runs a complete game loop. No operating system, no libraries, no abstractions — just 68000 instructions talking directly to custom chips.

The Complete Code

;──────────────────────────────────────────────────────────────
; EXODUS - A terrain puzzle for the Commodore Amiga
; Unit 16: Integration and Polish
;
; The complete Phase 1 game: title → play → result → title.
; Five creatures, refined terrain, staggered spawning.
;──────────────────────────────────────────────────────────────

;══════════════════════════════════════════════════════════════
; 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 5

; 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_TITLE          equ 0
GAME_PLAYING        equ 1
GAME_COMPLETE       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_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
SPR3PTH     equ $12c
SPR3PTL     equ $12e
SPR4PTH     equ $130
SPR4PTL     equ $132
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  #48,CR_X+CR_SIZE*3(a0)
            move.w  #140,CR_Y+CR_SIZE*3(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE*3(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE*3(a0)
            move.w  #6,CR_STEP+CR_SIZE*3(a0)

            move.w  #200,CR_X+CR_SIZE*4(a0)
            move.w  #108,CR_Y+CR_SIZE*4(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE*4(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE*4(a0)
            move.w  #3,CR_STEP+CR_SIZE*4(a0)

            move.w  #0,saved_count
            move.w  #0,lost_count
            move.w  #GAME_TITLE,game_state

            ; --- Draw title screen ---
            bsr     draw_title

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

            lea     sprite3_data,a0
            move.l  a0,d0
            swap    d0
            lea     spr3pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr3ptl_val,a1
            move.w  d0,(a1)

            lea     sprite4_data,a0
            move.l  a0,d0
            swap    d0
            lea     spr4pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr4ptl_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

            ; --- Check game state ---
            move.w  game_state,d0
            cmp.w   #GAME_TITLE,d0
            beq     .title_update
            cmp.w   #GAME_COMPLETE,d0
            beq     .complete_update

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

.creature_loop:
            move.w  saved_count,d6
            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  saved_count,d0
            add.w   lost_count,d0
            cmp.w   #NUM_CREATURES,d0
            blt.s   .not_done
            move.w  #GAME_COMPLETE,game_state
            bsr     draw_result
.not_done:

            ; 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

            lea     creatures+CR_SIZE*3,a2
            lea     sprite3_data,a0
            bsr     write_sprite_pos

            lea     creatures+CR_SIZE*4,a2
            lea     sprite4_data,a0
            bsr     write_sprite_pos

            bra     .frame_end

            ; --- TITLE: wait for fire ---
.title_update:
            btst    #6,$bfe001
            bne     .frame_end
            ; Fire pressed — start game
            bsr     start_game
            bra     .frame_end

            ; --- COMPLETE: wait for fire to restart ---
.complete_update:
            btst    #6,$bfe001
            bne     .frame_end
            ; Fire pressed — restart
            bsr     clear_bitplane
            bsr     draw_title
            move.w  #GAME_TITLE,game_state
            move.w  #COLOUR_PANEL_BG,panel_bg_val

.frame_end:
            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

;──────────────────────────────────────────────────────────────
; start_game — Transition from title to gameplay
;──────────────────────────────────────────────────────────────
start_game:
            bsr     clear_bitplane

            ; Reset 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  #48,CR_X+CR_SIZE*3(a0)
            move.w  #140,CR_Y+CR_SIZE*3(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE*3(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE*3(a0)
            move.w  #6,CR_STEP+CR_SIZE*3(a0)

            move.w  #200,CR_X+CR_SIZE*4(a0)
            move.w  #108,CR_Y+CR_SIZE*4(a0)
            move.w  #CREATURE_SPEED,CR_DX+CR_SIZE*4(a0)
            move.w  #STATE_WALKING,CR_STATE+CR_SIZE*4(a0)
            move.w  #3,CR_STEP+CR_SIZE*4(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 score
            bsr     draw_saved_count
            rts

;──────────────────────────────────────────────────────────────
; draw_title — Draw "EXODUS" on the game bitplane
;──────────────────────────────────────────────────────────────
draw_title:
            ; "EXODUS" at row 80, starting at byte 16 (x=128)
            lea     font_E,a1
            move.w  #128,d0
            move.w  #80,d1
            bsr     draw_bitplane_glyph

            lea     font_X,a1
            move.w  #136,d0
            move.w  #80,d1
            bsr     draw_bitplane_glyph

            lea     font_O,a1
            move.w  #144,d0
            move.w  #80,d1
            bsr     draw_bitplane_glyph

            lea     font_D,a1
            move.w  #152,d0
            move.w  #80,d1
            bsr     draw_bitplane_glyph

            lea     font_U,a1
            move.w  #160,d0
            move.w  #80,d1
            bsr     draw_bitplane_glyph

            lea     font_S,a1
            move.w  #168,d0
            move.w  #80,d1
            bsr     draw_bitplane_glyph
            rts

;──────────────────────────────────────────────────────────────
; draw_bitplane_glyph — Draw 8x8 glyph onto game bitplane
;   A1 = glyph data, D0 = x (byte-aligned), D1 = y
;──────────────────────────────────────────────────────────────
draw_bitplane_glyph:
            lea     bitplane,a0
            mulu    #BYTES_PER_ROW,d1
            add.l   d1,a0
            lsr.w   #3,d0
            add.w   d0,a0
            moveq   #7,d3
.draw:
            move.b  (a1)+,(a0)
            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.draw
            rts

;──────────────────────────────────────────────────────────────
; clear_bitplane — Zero the game bitplane
;──────────────────────────────────────────────────────────────
clear_bitplane:
            lea     bitplane,a0
            move.w  #BITPLANE_SIZE/4-1,d0
.clr:
            clr.l   (a0)+
            dbra    d0,.clr
            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

; Letters for "EXODUS"
font_E:
            dc.b    %01111110
            dc.b    %01100000
            dc.b    %01100000
            dc.b    %01111100
            dc.b    %01100000
            dc.b    %01100000
            dc.b    %01111110
            dc.b    %00000000
font_X:
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00011000
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00000000
font_O:
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000
font_D:
            dc.b    %01111100
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01111100
            dc.b    %00000000
font_U:
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %01100110
            dc.b    %00111100
            dc.b    %00000000
font_S:
            dc.b    %00111100
            dc.b    %01100110
            dc.b    %01100000
            dc.b    %00111100
            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    SPR3PTH
spr3pth_val:
            dc.w    $0000
            dc.w    SPR3PTL
spr3ptl_val:
            dc.w    $0000

            dc.w    SPR4PTH
spr4pth_val:
            dc.w    $0000
            dc.w    SPR4PTL
spr4ptl_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

            even
sprite3_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
sprite4_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

  • Only 3 creatures visible? Check that sprite 3 and 4 pointers are patched in the init section, and that spr3pth_val/spr4pth_val labels exist in the Copper list.
  • Creatures 3-4 don’t move? Make sure they’re initialised in both the startup code and start_game. Check that NUM_CREATURES is 5.
  • Score shows wrong total? The total digit draws NUM_CREATURES. If it shows 3 instead of 5, the constant wasn’t updated.
  • Sprite glitches? Each sprite needs its own data block (sprite3_data, sprite4_data). Sharing data blocks between sprites causes display corruption.

What You’ve Learnt

  • Multiple hardware sprites — the Amiga supports 8 sprites via DMA. Each needs its own data block, Copper pointer entries, and position updates.
  • Staggered initialisation — spreading creatures across different positions and step timers prevents clustering and creates natural movement patterns.
  • System scaling — the data table, processing loop, and sprite update pattern scale linearly. Adding creatures means adding data, not changing logic.
  • Integration — combining all previous systems (Copper, bitplanes, sprites, audio, state machine) into a cohesive game. Each piece was built incrementally and fits together cleanly.

What’s Next

Phase 1 is complete. In Phase 2, the Blitter arrives — Amiga’s hardware-accelerated drawing engine. Creatures gain abilities, terrain becomes interactive, and the game transforms from a walking demo into a genuine puzzle.