Skip to content
Game 1 Unit 2 of 64 1 hr learning time

Moving the Frog

Read the joystick. Move the sprite. Interactivity in Unit 2.

3% of Signal

What You’re Building

Joystick control. The frog responds to your input.

By the end of this unit:

  • Push joystick up — frog moves up
  • Push joystick down — frog moves down
  • Left and right work too
  • Frog stays within screen bounds

Unit 2 Screenshot

From Static to Interactive

In Unit 1, the frog just sat there. The custom chipset drew it automatically, but it never moved. That’s not a game—that’s a painting.

A game needs input. The player pushes, the game responds. That feedback loop is everything.

The Joystick Hardware

The Amiga reads joystick data through the JOY1DAT register at $DFF00C. This register contains the position of joystick port 2 (the primary game port).

But the data isn’t straightforward. It’s not four bits saying “up, down, left, right”. Instead, it uses a quirky encoding inherited from the Atari design:

JOY1DAT register ($DFF00C):
  Bit 9 = Left XOR'd with vertical
  Bit 8 = Up XOR'd with vertical
  Bit 1 = Right XOR'd with vertical
  Bit 0 = Down XOR'd with vertical

The vertical axis affects the horizontal bits through XOR. To decode it properly, we XOR the register with itself shifted right by one bit:

; Joystick Reading
; Port 2 joystick data is at JOY1DAT ($dff00c)
; Bits use Gray code - XOR adjacent bits to decode direction

read_joystick:
            move.w  JOY1DAT(a5),d0      ; Read raw joystick data
            move.w  d0,d1
            lsr.w   #1,d1               ; Shift for XOR
            eor.w   d1,d0               ; Decode Gray code

            ; After decode:
            ; Bit 0 = Down
            ; Bit 1 = Right
            ; Bit 8 = Up
            ; Bit 9 = Left
            rts

; Example: Check if up is pressed
            btst    #8,d0
            beq.s   .not_up
            ; Up pressed...
.not_up:

After decoding, the bits become meaningful:

  • Bit 8 = Up pressed
  • Bit 0 = Down pressed
  • Bit 9 = Left pressed
  • Bit 1 = Right pressed

The Main Loop

Unit 1’s main loop just waited for vertical blank and checked the mouse button. Now it does real work:

mainloop:
            bsr.s   wait_vblank         ; Wait for vertical blank
            bsr.s   read_joystick       ; Read joystick into D0
            bsr.s   move_frog           ; Move frog based on input
            bsr     update_sprite       ; Update sprite control words
            bra.s   mainloop

Every frame:

  1. Wait for VBlank — synchronises with display refresh (50 times per second on PAL)
  2. Read joystick — get current direction
  3. Move frog — update position if direction pressed
  4. Update sprite — write new position to sprite control words

The hardware does the rest. We just change numbers; the chipset draws.

Moving the Frog

The move_frog routine checks each direction and adjusts the frog’s position:

; Move Frog with Boundaries
; Pixel-based movement with screen boundary checking

move_frog:
            bsr     read_joystick

            ; --- Check UP ---
            btst    #8,d0
            beq.s   .not_up
            move.w  frog_y,d1
            cmp.w   #44,d1              ; Top boundary
            beq.s   .not_up
            subq.w  #2,d1
            move.w  d1,frog_y
.not_up:

            ; --- Check DOWN ---
            btst    #0,d0
            beq.s   .not_down
            move.w  frog_y,d1
            cmp.w   #220,d1             ; Bottom boundary
            beq.s   .not_down
            addq.w  #2,d1
            move.w  d1,frog_y
.not_down:

            ; --- Check LEFT ---
            btst    #9,d0
            beq.s   .not_left
            move.w  frog_x,d1
            cmp.w   #48,d1              ; Left boundary
            beq.s   .not_left
            subq.w  #2,d1
            move.w  d1,frog_x
.not_left:

            ; --- Check RIGHT ---
            btst    #1,d0
            beq.s   .not_right
            move.w  frog_x,d1
            cmp.w   #288,d1             ; Right boundary
            beq.s   .not_right
            addq.w  #2,d1
            move.w  d1,frog_x
.not_right:
            rts

Each direction:

  1. Test the bit for that direction
  2. Load current position
  3. Add or subtract MOVE_SPEED
  4. Check against boundary
  5. Store new position (if within bounds)

Boundaries

Without boundaries, the frog would fly off screen and disappear into memory. We define limits:

MIN_X           equ 48          ; Left edge
MAX_X           equ 280         ; Right edge
MIN_Y           equ 44          ; Top of play area
MAX_Y           equ 196         ; Bottom of play area

These keep the frog within the visible playfield. The numbers match where our Copper list draws the game zones.

Updating the Sprite

When frog_x or frog_y changes, we must update the sprite’s control words:

; Update Sprite Control Words
; Pack X/Y coordinates into hardware sprite format

FROG_HEIGHT     equ 16

update_sprite:
            lea     frog_data,a0
            move.w  frog_y,d0           ; Y position
            move.w  frog_x,d1           ; X position

            ; First control word: VSTART<<8 | HSTART>>1
            move.w  d0,d2
            lsl.w   #8,d2               ; VSTART in high byte
            lsr.w   #1,d1               ; HSTART / 2
            or.b    d1,d2               ; Combine
            move.w  d2,(a0)             ; Write to sprite data

            ; Second control word: VSTOP<<8 | control bits
            add.w   #FROG_HEIGHT,d0     ; VSTOP = VSTART + height
            lsl.w   #8,d0               ; VSTOP in high byte
            move.w  d0,2(a0)            ; Write to sprite data
            rts

; Sprite control word format:
; Word 0: [VSTART 7:0] [HSTART 8:1]
; Word 1: [VSTOP 7:0] [V8 H8 ATT SH V0]

The sprite’s first two words control its position:

  • Word 1: Vertical start (high byte) and horizontal start divided by 2 (low byte)
  • Word 2: Vertical stop (high byte) and control bits (low byte)

We pack these values every frame. The chipset reads them during display and draws the sprite at the new position.

Run It

Build and create the disk:

vasmm68k_mot -Fhunkexe -kick1hunks -o signal signal.asm

xdftool signal.adf create
xdftool signal.adf format "Signal"
xdftool signal.adf makedir s
xdftool signal.adf boot install
echo "signal" > startup-sequence
xdftool signal.adf write startup-sequence s/startup-sequence
rm startup-sequence
xdftool signal.adf write signal

Run in FS-UAE with a joystick (or keyboard arrows mapped to joystick). Push any direction. The frog moves.

Experiment: Change the Feel

Open signal.asm and find the tweakable values:

MOVE_SPEED      equ 2           ; Pixels per frame

Try these:

  • MOVE_SPEED equ 1 — Slow, precise movement
  • MOVE_SPEED equ 4 — Fast, twitchy movement
  • MOVE_SPEED equ 8 — Too fast! Hard to control

The boundaries are also tweakable:

  • Make MIN_Y larger to prevent reaching the home zone
  • Make MAX_Y smaller to shrink the play area

What We Added

Comparing Unit 2 to Unit 1:

FeatureUnit 1Unit 2
Joystick readingNoYes
Sprite movementNoYes
Boundary checkingNoYes
Variables (frog_x, frog_y)NoYes
Code size326 bytes506 bytes

The extra 180 bytes give us interactivity.

Key Takeaways

  • JOY1DAT ($DFF00C) contains joystick port 2 data
  • The joystick encoding requires XOR decoding
  • Update frog_x/frog_y variables, then write to sprite control words
  • Boundary checking prevents the sprite from leaving the play area
  • 50 updates per second (PAL VBlank rate) gives smooth movement

What’s Next

In Unit 3, we’ll step back and understand everything we’ve built. How does the Copper really work? What are those sprite control word bits doing? A theory unit to cement the concepts.

The Code

;──────────────────────────────────────────────────────────────
; SIGNAL - A Frogger-style game for the Commodore Amiga
; Unit 2: Moving the Frog
;
; Push the joystick, the frog moves. Interactivity!
;──────────────────────────────────────────────────────────────

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

FROG_START_X    equ 160         ; Starting horizontal position
FROG_START_Y    equ 180         ; Starting vertical position

MOVE_SPEED      equ 2           ; Pixels per frame (try 1, 2, or 4)

; Boundaries (where the frog can move)
MIN_X           equ 48          ; Left edge
MAX_X           equ 280         ; Right edge
MIN_Y           equ 44          ; Top of play area
MAX_Y           equ 196         ; Bottom of play area

; Sprite dimensions
FROG_HEIGHT     equ 16

; Colours
COLOUR_HOME     equ $0080       ; Home zone: green
COLOUR_WATER    equ $0048       ; Water: dark blue
COLOUR_WAVE     equ $006b       ; Water highlight: lighter blue
COLOUR_MEDIAN   equ $0080       ; Safe median: green
COLOUR_ROAD     equ $0444       ; Road: dark grey
COLOUR_MARKER   equ $0666       ; Road marking: light grey
COLOUR_START    equ $0080       ; Start zone: green
COLOUR_BORDER   equ $0070       ; Border: darker green

; Frog colours
COLOUR_FROG     equ $00f0       ; Frog body: bright green
COLOUR_EYES     equ $0ff0       ; Frog eyes: yellow
COLOUR_OUTLINE  equ $0000       ; Frog outline: black

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

CUSTOM      equ $dff000

; Custom chip register offsets
DMACONR     equ $002
VPOSR       equ $004
JOY1DAT     equ $00c            ; Joystick port 2 data
COP1LC      equ $080
COPJMP1     equ $088
DMACON      equ $096
INTENA      equ $09a
INTREQ      equ $09c
COLOR00     equ $180
SPR0PTH     equ $120
SPR0PTL     equ $122

;══════════════════════════════════════════════════════════════
; CODE SECTION (in chip RAM)
;══════════════════════════════════════════════════════════════
            section code,code_c

start:
            lea     CUSTOM,a5           ; Custom chip base in A5

            ; --- Take over the machine ---
            move.w  #$7fff,INTENA(a5)   ; Disable all interrupts
            move.w  #$7fff,INTREQ(a5)   ; Clear pending interrupts
            move.w  #$7fff,DMACON(a5)   ; Disable all DMA

            ; --- Initialise frog position ---
            move.w  #FROG_START_X,frog_x
            move.w  #FROG_START_Y,frog_y

            ; --- Set sprite pointer in copper list ---
            lea     frog_data,a0
            move.l  a0,d0
            swap    d0
            lea     sprpth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     sprptl_val,a1
            move.w  d0,(a1)

            ; --- Update sprite position ---
            bsr     update_sprite

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

            ; --- Enable DMA ---
            move.w  #$83a0,DMACON(a5)   ; Master + copper + sprites + bitplanes

;══════════════════════════════════════════════════════════════
; MAIN LOOP
;══════════════════════════════════════════════════════════════

mainloop:
            bsr.s   wait_vblank         ; Wait for vertical blank
            bsr.s   read_joystick       ; Read joystick into D0
            bsr.s   move_frog           ; Move frog based on input
            bsr     update_sprite       ; Update sprite control words

            ; Check left mouse button for exit
            btst    #6,$bfe001
            bne.s   mainloop

            ; Button pressed - exit
            bra.s   mainloop            ; (Actually just loop - reset to exit)

;──────────────────────────────────────────────────────────────
; Wait for vertical blank
;──────────────────────────────────────────────────────────────
wait_vblank:
            move.l  #$1ff00,d1
.wait:
            move.l  VPOSR(a5),d0
            and.l   d1,d0
            bne.s   .wait
            rts

;──────────────────────────────────────────────────────────────
; Read joystick
; Output: D0 = decoded directions
;         Bit 8 = up, Bit 0 = down, Bit 9 = left, Bit 1 = right
;──────────────────────────────────────────────────────────────
read_joystick:
            move.w  JOY1DAT(a5),d0      ; Read joystick data
            move.w  d0,d1
            lsr.w   #1,d1               ; Shift for XOR decode
            eor.w   d1,d0               ; XOR decode for up/down
            rts

;──────────────────────────────────────────────────────────────
; Move frog based on joystick input
; Input: D0 = decoded joystick directions
;──────────────────────────────────────────────────────────────
move_frog:
            ; --- Check Up ---
            btst    #8,d0
            beq.s   .no_up
            move.w  frog_y,d1
            sub.w   #MOVE_SPEED,d1
            cmp.w   #MIN_Y,d1
            blt.s   .no_up
            move.w  d1,frog_y
.no_up:
            ; --- Check Down ---
            btst    #0,d0
            beq.s   .no_down
            move.w  frog_y,d1
            add.w   #MOVE_SPEED,d1
            cmp.w   #MAX_Y,d1
            bgt.s   .no_down
            move.w  d1,frog_y
.no_down:
            ; --- Check Left ---
            btst    #9,d0
            beq.s   .no_left
            move.w  frog_x,d1
            sub.w   #MOVE_SPEED,d1
            cmp.w   #MIN_X,d1
            blt.s   .no_left
            move.w  d1,frog_x
.no_left:
            ; --- Check Right ---
            btst    #1,d0
            beq.s   .no_right
            move.w  frog_x,d1
            add.w   #MOVE_SPEED,d1
            cmp.w   #MAX_X,d1
            bgt.s   .no_right
            move.w  d1,frog_x
.no_right:
            rts

;──────────────────────────────────────────────────────────────
; Update sprite control words from frog_x/frog_y
;──────────────────────────────────────────────────────────────
update_sprite:
            lea     frog_data,a0
            move.w  frog_y,d0           ; D0 = Y position
            move.w  frog_x,d1           ; D1 = X position

            ; --- Control word 1: VSTART<<8 | HSTART>>1 ---
            move.w  d0,d2               ; D2 = VSTART
            lsl.w   #8,d2               ; Shift to high byte
            lsr.w   #1,d1               ; HSTART / 2
            or.b    d1,d2               ; Combine
            move.w  d2,(a0)             ; Write to sprite

            ; --- Control word 2: VSTOP<<8 | control bits ---
            add.w   #FROG_HEIGHT,d0     ; VSTOP = VSTART + height
            lsl.w   #8,d0               ; Shift to high byte
            move.w  d0,2(a0)            ; Write to sprite

            rts

;──────────────────────────────────────────────────────────────
; Variables
;──────────────────────────────────────────────────────────────
frog_x:     dc.w    160
frog_y:     dc.w    180

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

copperlist:
            dc.w    COLOR00,$0000       ; Black border at top

            ; --- Sprite 0 palette (colours 17-19) ---
            dc.w    $01a2,COLOUR_FROG
            dc.w    $01a4,COLOUR_EYES
            dc.w    $01a6,COLOUR_OUTLINE

            ; --- Sprite 0 pointer ---
            dc.w    SPR0PTH
sprpth_val: dc.w    $0000
            dc.w    SPR0PTL
sprptl_val: dc.w    $0000

            ; === HOME ZONE ===
            dc.w    $2c07,$fffe
            dc.w    COLOR00,COLOUR_HOME

            ; === WATER ZONE (5 lanes) ===
            dc.w    $4007,$fffe
            dc.w    COLOR00,COLOUR_WATER

            dc.w    $4c07,$fffe
            dc.w    COLOR00,COLOUR_WAVE

            dc.w    $5407,$fffe
            dc.w    COLOR00,COLOUR_WATER

            dc.w    $5c07,$fffe
            dc.w    COLOR00,COLOUR_WAVE

            dc.w    $6407,$fffe
            dc.w    COLOR00,COLOUR_WATER

            ; === MEDIAN (safe zone) ===
            dc.w    $6c07,$fffe
            dc.w    COLOR00,COLOUR_MEDIAN

            ; === ROAD ZONE (4 lanes) ===
            dc.w    $7807,$fffe
            dc.w    COLOR00,COLOUR_ROAD

            dc.w    $8407,$fffe
            dc.w    COLOR00,COLOUR_MARKER

            dc.w    $8807,$fffe
            dc.w    COLOR00,COLOUR_ROAD

            dc.w    $9407,$fffe
            dc.w    COLOR00,COLOUR_MARKER

            dc.w    $9807,$fffe
            dc.w    COLOR00,COLOUR_ROAD

            dc.w    $a407,$fffe
            dc.w    COLOR00,COLOUR_MARKER

            dc.w    $a807,$fffe
            dc.w    COLOR00,COLOUR_ROAD

            ; === START ZONE ===
            dc.w    $b407,$fffe
            dc.w    COLOR00,COLOUR_START

            dc.w    $c007,$fffe
            dc.w    COLOR00,COLOUR_BORDER

            ; === BOTTOM ===
            dc.w    $f007,$fffe
            dc.w    COLOR00,$0000

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

;──────────────────────────────────────────────────────────────
; SPRITE DATA
;──────────────────────────────────────────────────────────────
            even
frog_data:
            dc.w    $b450,$c400         ; Control words (updated by code)

            ; 16 lines of sprite data (plane0, plane1)
            dc.w    $0000,$0000
            dc.w    $07e0,$0000
            dc.w    $1ff8,$0420
            dc.w    $3ffc,$0a50
            dc.w    $7ffe,$1248
            dc.w    $7ffe,$1008
            dc.w    $ffff,$2004
            dc.w    $ffff,$0000
            dc.w    $ffff,$0000
            dc.w    $7ffe,$2004
            dc.w    $7ffe,$1008
            dc.w    $3ffc,$0810
            dc.w    $1ff8,$0420
            dc.w    $07e0,$0000
            dc.w    $0000,$0000
            dc.w    $0000,$0000

            ; End marker
            dc.w    $0000,$0000

What Changed

Unit 1 → Unit 2
+187-76
11 ;──────────────────────────────────────────────────────────────
22 ; SIGNAL - A Frogger-style game for the Commodore Amiga
3-; Unit 1: Hello Amiga
3+; Unit 2: Moving the Frog
44 ;
5-; This is your scaffold. Run it, see the frog, change the values.
5+; Push the joystick, the frog moves. Interactivity!
66 ;──────────────────────────────────────────────────────────────
77
88 ;══════════════════════════════════════════════════════════════
9-; TWEAKABLE VALUES - Change these and see what happens!
9+; TWEAKABLE VALUES
1010 ;══════════════════════════════════════════════════════════════
1111
12-FROG_X equ 160 ; Frog horizontal position (try 64-280)
13-FROG_Y equ 140 ; Frog vertical position (try 44-220)
12+FROG_START_X equ 160 ; Starting horizontal position
13+FROG_START_Y equ 180 ; Starting vertical position
1414
15-; Colours are $0RGB (0-15 for each component)
15+MOVE_SPEED equ 2 ; Pixels per frame (try 1, 2, or 4)
16+
17+; Boundaries (where the frog can move)
18+MIN_X equ 48 ; Left edge
19+MAX_X equ 280 ; Right edge
20+MIN_Y equ 44 ; Top of play area
21+MAX_Y equ 196 ; Bottom of play area
22+
23+; Sprite dimensions
24+FROG_HEIGHT equ 16
25+
26+; Colours
1627 COLOUR_HOME equ $0080 ; Home zone: green
1728 COLOUR_WATER equ $0048 ; Water: dark blue
1829 COLOUR_WAVE equ $006b ; Water highlight: lighter blue
...
2233 COLOUR_START equ $0080 ; Start zone: green
2334 COLOUR_BORDER equ $0070 ; Border: darker green
2435
25-; Frog colours (sprite palette: colours 17-19)
26-COLOUR_FROG equ $0f0 ; Frog body: bright green (contrasts with dark green zones)
27-COLOUR_EYES equ $ff0 ; Frog eyes: yellow
28-COLOUR_OUTLINE equ $000 ; Frog outline: black
36+; Frog colours
37+COLOUR_FROG equ $00f0 ; Frog body: bright green
38+COLOUR_EYES equ $0ff0 ; Frog eyes: yellow
39+COLOUR_OUTLINE equ $0000 ; Frog outline: black
2940
3041 ;══════════════════════════════════════════════════════════════
3142 ; HARDWARE REGISTERS
...
3546
3647 ; Custom chip register offsets
3748 DMACONR equ $002
49+VPOSR equ $004
50+JOY1DAT equ $00c ; Joystick port 2 data
51+COP1LC equ $080
52+COPJMP1 equ $088
3853 DMACON equ $096
3954 INTENA equ $09a
4055 INTREQ equ $09c
41-COP1LC equ $080
42-COPJMP1 equ $088
43-VPOSR equ $004
4456 COLOR00 equ $180
4557 SPR0PTH equ $120
4658 SPR0PTL equ $122
4759
4860 ;══════════════════════════════════════════════════════════════
49-; CODE SECTION (in chip RAM for sprites/copper to work)
61+; CODE SECTION (in chip RAM)
5062 ;══════════════════════════════════════════════════════════════
5163 section code,code_c
5264
...
5769 move.w #$7fff,INTENA(a5) ; Disable all interrupts
5870 move.w #$7fff,INTREQ(a5) ; Clear pending interrupts
5971 move.w #$7fff,DMACON(a5) ; Disable all DMA
72+
73+ ; --- Initialise frog position ---
74+ move.w #FROG_START_X,frog_x
75+ move.w #FROG_START_Y,frog_y
6076
6177 ; --- Set sprite pointer in copper list ---
62- lea frog_data,a0 ; A0 = sprite data address
63- move.l a0,d0 ; D0 = sprite data address
64- swap d0 ; High word first
78+ lea frog_data,a0
79+ move.l a0,d0
80+ swap d0
6581 lea sprpth_val,a1
66- move.w d0,(a1) ; Write high word
67- swap d0 ; Low word
82+ move.w d0,(a1)
83+ swap d0
6884 lea sprptl_val,a1
69- move.w d0,(a1) ; Write low word
85+ move.w d0,(a1)
86+
87+ ; --- Update sprite position ---
88+ bsr update_sprite
7089
7190 ; --- Install copper list ---
7291 lea copperlist,a0
73- move.l a0,COP1LC(a5) ; Point copper at our list
74- move.w d0,COPJMP1(a5) ; Strobe to start copper
92+ move.l a0,COP1LC(a5)
93+ move.w d0,COPJMP1(a5)
7594
7695 ; --- Enable DMA ---
77- move.w #$83a0,DMACON(a5) ; Master + copper + sprites (+ bitplanes)
96+ move.w #$83a0,DMACON(a5) ; Master + copper + sprites + bitplanes
7897
79- ; === Main Loop ===
98+;══════════════════════════════════════════════════════════════
99+; MAIN LOOP
100+;══════════════════════════════════════════════════════════════
101+
80102 mainloop:
81- ; Wait for vertical blank
103+ bsr.s wait_vblank ; Wait for vertical blank
104+ bsr.s read_joystick ; Read joystick into D0
105+ bsr.s move_frog ; Move frog based on input
106+ bsr update_sprite ; Update sprite control words
107+
108+ ; Check left mouse button for exit
109+ btst #6,$bfe001
110+ bne.s mainloop
111+
112+ ; Button pressed - exit
113+ bra.s mainloop ; (Actually just loop - reset to exit)
114+
115+;──────────────────────────────────────────────────────────────
116+; Wait for vertical blank
117+;──────────────────────────────────────────────────────────────
118+wait_vblank:
82119 move.l #$1ff00,d1
83-.vbwait:
120+.wait:
84121 move.l VPOSR(a5),d0
85122 and.l d1,d0
86- bne.s .vbwait
123+ bne.s .wait
124+ rts
87125
88- ; Check left mouse button (run until reset)
89- btst #6,$bfe001
90- bne.s mainloop
126+;──────────────────────────────────────────────────────────────
127+; Read joystick
128+; Output: D0 = decoded directions
129+; Bit 8 = up, Bit 0 = down, Bit 9 = left, Bit 1 = right
130+;──────────────────────────────────────────────────────────────
131+read_joystick:
132+ move.w JOY1DAT(a5),d0 ; Read joystick data
133+ move.w d0,d1
134+ lsr.w #1,d1 ; Shift for XOR decode
135+ eor.w d1,d0 ; XOR decode for up/down
136+ rts
91137
92- ; Button pressed - loop forever (machine takeover, reset to exit)
93- bra.s mainloop
138+;──────────────────────────────────────────────────────────────
139+; Move frog based on joystick input
140+; Input: D0 = decoded joystick directions
141+;──────────────────────────────────────────────────────────────
142+move_frog:
143+ ; --- Check Up ---
144+ btst #8,d0
145+ beq.s .no_up
146+ move.w frog_y,d1
147+ sub.w #MOVE_SPEED,d1
148+ cmp.w #MIN_Y,d1
149+ blt.s .no_up
150+ move.w d1,frog_y
151+.no_up:
152+ ; --- Check Down ---
153+ btst #0,d0
154+ beq.s .no_down
155+ move.w frog_y,d1
156+ add.w #MOVE_SPEED,d1
157+ cmp.w #MAX_Y,d1
158+ bgt.s .no_down
159+ move.w d1,frog_y
160+.no_down:
161+ ; --- Check Left ---
162+ btst #9,d0
163+ beq.s .no_left
164+ move.w frog_x,d1
165+ sub.w #MOVE_SPEED,d1
166+ cmp.w #MIN_X,d1
167+ blt.s .no_left
168+ move.w d1,frog_x
169+.no_left:
170+ ; --- Check Right ---
171+ btst #1,d0
172+ beq.s .no_right
173+ move.w frog_x,d1
174+ add.w #MOVE_SPEED,d1
175+ cmp.w #MAX_X,d1
176+ bgt.s .no_right
177+ move.w d1,frog_x
178+.no_right:
179+ rts
180+
181+;──────────────────────────────────────────────────────────────
182+; Update sprite control words from frog_x/frog_y
183+;──────────────────────────────────────────────────────────────
184+update_sprite:
185+ lea frog_data,a0
186+ move.w frog_y,d0 ; D0 = Y position
187+ move.w frog_x,d1 ; D1 = X position
188+
189+ ; --- Control word 1: VSTART<<8 | HSTART>>1 ---
190+ move.w d0,d2 ; D2 = VSTART
191+ lsl.w #8,d2 ; Shift to high byte
192+ lsr.w #1,d1 ; HSTART / 2
193+ or.b d1,d2 ; Combine
194+ move.w d2,(a0) ; Write to sprite
195+
196+ ; --- Control word 2: VSTOP<<8 | control bits ---
197+ add.w #FROG_HEIGHT,d0 ; VSTOP = VSTART + height
198+ lsl.w #8,d0 ; Shift to high byte
199+ move.w d0,2(a0) ; Write to sprite
200+
201+ rts
202+
203+;──────────────────────────────────────────────────────────────
204+; Variables
205+;──────────────────────────────────────────────────────────────
206+frog_x: dc.w 160
207+frog_y: dc.w 180
94208
95209 ;══════════════════════════════════════════════════════════════
96-; CHIP RAM DATA (copper list and sprites - same section as code)
210+; COPPER LIST
97211 ;══════════════════════════════════════════════════════════════
98212
99213 copperlist:
100214 dc.w COLOR00,$0000 ; Black border at top
101215
102216 ; --- Sprite 0 palette (colours 17-19) ---
103- dc.w $01a2,COLOUR_FROG ; Colour 17: body
104- dc.w $01a4,COLOUR_EYES ; Colour 18: eyes
105- dc.w $01a6,COLOUR_OUTLINE ; Colour 19: outline
217+ dc.w $01a2,COLOUR_FROG
218+ dc.w $01a4,COLOUR_EYES
219+ dc.w $01a6,COLOUR_OUTLINE
106220
107- ; --- Sprite 0 pointer (filled by CPU) ---
108- dc.w SPR0PTH ; SPR0PTH register
109-sprpth_val: dc.w $0000 ; High word (patched by code)
110- dc.w SPR0PTL ; SPR0PTL register
111-sprptl_val: dc.w $0000 ; Low word (patched by code)
221+ ; --- Sprite 0 pointer ---
222+ dc.w SPR0PTH
223+sprpth_val: dc.w $0000
224+ dc.w SPR0PTL
225+sprptl_val: dc.w $0000
112226
113227 ; === HOME ZONE ===
114228 dc.w $2c07,$fffe
...
116230
117231 ; === WATER ZONE (5 lanes) ===
118232 dc.w $4007,$fffe
119- dc.w COLOR00,COLOUR_WATER ; Lane 1
233+ dc.w COLOR00,COLOUR_WATER
120234
121235 dc.w $4c07,$fffe
122- dc.w COLOR00,COLOUR_WAVE ; Wave highlight
236+ dc.w COLOR00,COLOUR_WAVE
123237
124238 dc.w $5407,$fffe
125- dc.w COLOR00,COLOUR_WATER ; Lane 2
239+ dc.w COLOR00,COLOUR_WATER
126240
127241 dc.w $5c07,$fffe
128- dc.w COLOR00,COLOUR_WAVE ; Wave highlight
242+ dc.w COLOR00,COLOUR_WAVE
129243
130244 dc.w $6407,$fffe
131- dc.w COLOR00,COLOUR_WATER ; Lane 3
245+ dc.w COLOR00,COLOUR_WATER
132246
133247 ; === MEDIAN (safe zone) ===
134248 dc.w $6c07,$fffe
...
136250
137251 ; === ROAD ZONE (4 lanes) ===
138252 dc.w $7807,$fffe
139- dc.w COLOR00,COLOUR_ROAD ; Lane 1
253+ dc.w COLOR00,COLOUR_ROAD
140254
141255 dc.w $8407,$fffe
142- dc.w COLOR00,COLOUR_MARKER ; Road marking
256+ dc.w COLOR00,COLOUR_MARKER
143257
144258 dc.w $8807,$fffe
145- dc.w COLOR00,COLOUR_ROAD ; Lane 2
259+ dc.w COLOR00,COLOUR_ROAD
146260
147261 dc.w $9407,$fffe
148- dc.w COLOR00,COLOUR_MARKER ; Road marking
262+ dc.w COLOR00,COLOUR_MARKER
149263
150264 dc.w $9807,$fffe
151- dc.w COLOR00,COLOUR_ROAD ; Lane 3
265+ dc.w COLOR00,COLOUR_ROAD
152266
153267 dc.w $a407,$fffe
154- dc.w COLOR00,COLOUR_MARKER ; Road marking
268+ dc.w COLOR00,COLOUR_MARKER
155269
156270 dc.w $a807,$fffe
157- dc.w COLOR00,COLOUR_ROAD ; Lane 4
271+ dc.w COLOR00,COLOUR_ROAD
158272
159273 ; === START ZONE ===
160274 dc.w $b407,$fffe
161275 dc.w COLOR00,COLOUR_START
162276
163277 dc.w $c007,$fffe
164- dc.w COLOR00,COLOUR_BORDER ; Bottom border
278+ dc.w COLOR00,COLOUR_BORDER
165279
166280 ; === BOTTOM ===
167281 dc.w $f007,$fffe
168- dc.w COLOR00,$0000 ; Black
282+ dc.w COLOR00,$0000
169283
170284 ; End of copper list
171285 dc.w $ffff,$fffe
...
175289 ;──────────────────────────────────────────────────────────────
176290 even
177291 frog_data:
178- ; Control words: Y=180 ($B4), X=160/2=80 ($50)
179- dc.w $b450 ; VSTART<<8 | HSTART
180- dc.w $c400 ; VSTOP<<8 | control bits
292+ dc.w $b450,$c400 ; Control words (updated by code)
181293
182294 ; 16 lines of sprite data (plane0, plane1)
183- ; Colours: 00=transparent, 01=green, 10=yellow, 11=black
184- dc.w $0000,$0000 ; ................
185- dc.w $07e0,$0000 ; .....XXXXXX.....
186- dc.w $1ff8,$0420 ; ...XXXXXXXXXX...
187- dc.w $3ffc,$0a50 ; ..XXXXXXXXXXXX..
188- dc.w $7ffe,$1248 ; .XXXXXXXXXXXXXX.
189- dc.w $7ffe,$1008 ; .XXXXXXXXXXXXXX.
190- dc.w $ffff,$2004 ; XXXXXXXXXXXXXXXX
191- dc.w $ffff,$0000 ; XXXXXXXXXXXXXXXX
192- dc.w $ffff,$0000 ; XXXXXXXXXXXXXXXX
193- dc.w $7ffe,$2004 ; .XXXXXXXXXXXXXX.
194- dc.w $7ffe,$1008 ; .XXXXXXXXXXXXXX.
195- dc.w $3ffc,$0810 ; ..XXXXXXXXXXXX..
196- dc.w $1ff8,$0420 ; ...XXXXXXXXXX...
197- dc.w $07e0,$0000 ; .....XXXXXX.....
198- dc.w $0000,$0000 ; ................
199- dc.w $0000,$0000 ; ................
295+ dc.w $0000,$0000
296+ dc.w $07e0,$0000
297+ dc.w $1ff8,$0420
298+ dc.w $3ffc,$0a50
299+ dc.w $7ffe,$1248
300+ dc.w $7ffe,$1008
301+ dc.w $ffff,$2004
302+ dc.w $ffff,$0000
303+ dc.w $ffff,$0000
304+ dc.w $7ffe,$2004
305+ dc.w $7ffe,$1008
306+ dc.w $3ffc,$0810
307+ dc.w $1ff8,$0420
308+ dc.w $07e0,$0000
309+ dc.w $0000,$0000
310+ dc.w $0000,$0000
200311
201- ; End marker (required for hardware)
312+ ; End marker
202313 dc.w $0000,$0000
203314