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

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_vallabels 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 thatNUM_CREATURESis 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.