Dr. Andrew Broad
Sinclair ZX Spectrum
Manic Miner/Jet Set Willy
Disassemblies of MM/JSW


This page contains copyright code which was written by Matthew Smith for the long-since dissolved companies Bug-Byte and Software Projects. In a private email of 31st August 1999, he gave me his permission to post disassemblies of MM and JSW, provided that they include the following line at the top and bottom of the file:
(C) 1983,1984,1999 Matthew Smith - all rights reserved

There are several annotated disassemblies of Manic Miner and Jet Set Willy in existence:

  1. John Elliott's disassemblies of:
    1. Manic Miner (Internet Explorer users: right-click the link, Save Target As... open with WordPad)
    2. Jet Set Willy (hosted by JODI)
    3. Differences between JSW48 and JSW128
    4. Jet Set Willy: Miscellaneous patches
    5. Source code for some JSW64 patch vectors
    6. (only accessible to members of the Manic Miner and Jet Set Willy Yahoo! Group)
  2. J.G. Harston's disassemblies of:
    1. Jet Set Willy
    2. Extensions to the JSW48 Game Engine
  3. Geoff Eddy's disassemblies of:
    1. The Geoff Mode engine
    2. The patch-vectors in his games
  4. Various code-fragments that I have disassembled, annotated and rewritten can be found in the TECHNICA.TXT files of Jet Set Willy: The Lord of the Rings, Goodnite Luddite, Party Willy and Manic Miner: Neighbours - Allana Truman.
  5. 28th May 2007: The source-code for the 16K version of Jonathan Cauldwell's Area 51.


This page presents Carl Woffenden's annotated disassemblies of various code-fragments in MM/JSW - thanks to him for sending me the last revisions and for permission to host them:

  1. Manic Miner: Willy's Movement
  2. Manic Miner/Jet Set Willy: Comparison of Willy's Movement
  3. Jet Set Willy: Core
  4. Jet Set Willy: Horizontal and Vertical Guardians
  5. Jet Set Willy: Arrows
  6. Jet Set Willy: Ropes


1. Manic Miner: Willy's Movement

B8068:    DEFB 0               ; Willy's y * 2
B8069:    DEFB 0               ; Willy's anim frame 0 - 3
B806A:    DEFB 0               ; Willy's walking/facing flag:
                               ;    bit-0 = off - right, on - left
                               ;    bit-1 = off - still, on - walk
B806B:    DEFB 0               ; Willy's action:
                               ;    -1: dead
                               ;     0: walking/standing
                               ;     1: jumping
                               ;     2: falling
                               ;    >2: fall height
W806C:    DEFW 0               ; Willy's position in attribute buffer
B806E:    DEFB 0               ; Willy's jump phase counter (0x00 - 0x12)

; movement lookup table:
T8408:    DEFB 0,1,0,1,1,3,1,3,2,0,2,0,0,1,2,3

; *************************** start of movement code *************************

C8ABB:
     LD   A,(B806B)            ; action states
     CP   1
     JR   NZ,R8B10             ; branch (+50) if not currently JUMPING
     LD   A,(B806E)            ; JUMP phase counter
     RES  0,A
     SUB  8
     LD   HL,B8068             ; address of Willy's y * 2
     ADD  A,(HL)
     LD   (HL),A               ; jump height algorithm is (y * 2 & 0xFE) - 8
     CALL C8B82                ; update Willy's attribute buffer position
     LD   A,(B803B)            ; wall attr
     CP   (HL)                 ; HL now holding attribute buffer position
     JP   Z,L8BA2              ; branch if Willy's head hits wall
     INC  HL                   ; move one col right
     CP   (HL)
     JP   Z,L8BA2              ; branch if Willy's head hits wall
     LD   A,(B806E)            ; JUMP phase counter
     INC  A
     LD   (B806E),A            ; store incremented JUMP phase
     SUB  8                    ; **** beeper starts here ****
     JP   P,L8AEB              ; branch if SUB 8 goes below 0
     NEG                       ; else phase = 0 - phase
L8AEB:
     INC  A
     RLCA
     RLCA
     RLCA                      ; multiply height by 16
     LD   D,A
     LD   C,20h
     LD   A,(BORDER)           ; maintain border colour
R8AF5:
     OUT  (0FEh),A
     XOR  18h                  ; toggle ear/mic bits
     LD   B,D
R8AFA:
     DJNZ R8AFA                ; wait/pitch based on height
     DEC  C
     JR   NZ,R8AF5             ; **** beeper ends here (after 32 cycles) ****
     LD   A,(B806E)            ; JUMP phase counter
     CP   12h                  ; 18 steps in a JUMP
     JP   Z,L8B96              ; branch if at end of JUMP phase
     CP   10h                  ; [dunno yet why 16?]
     JR   Z,R8B10              ; [+07]
     CP   0Dh                  ; [dunno yet why 12?]
     JP   NZ,L8C83             ; branch if not 12 (do horizontal movement)
; entry for manual control
R8B10:
     LD   A,(B8068)            ; Willy's y * 2
     AND  0Fh                  ; **** JSW is 0x0E ****
     JR   NZ,R8B51             ; branch (+3C) if y isn't row aligned
     LD   HL,(W806C)           ; Willy's position in attribute buffer
     LD   DE,64
     ADD  HL,DE                ; move two rows down
     LD   A,(B8032)            ; crumble attr
     CP   (HL)
     CALL Z,C8BBA              ; matched crumble
     LD   A,(B804D)            ; nasty (1) attr
     CP   (HL)
     JR   Z,R8B51              ; branch (+28) if matched nasty
     LD   A,(B8056)            ; nasty (2) attr
     CP   (HL)
     JR   Z,R8B51              ; branch (+22) if matched nasty
     INC  HL                   ; move one col right (and redo the matches)
     LD   A,(B8032)            ; crumble attr
     CP   (HL)
     CALL Z,C8BBA              ; branch if matched crumble
     LD   A,(B804D)            ; nasty (1) attr
     CP   (HL)
     JR   Z,R8B51              ; branch (+14) if matched nasty
     LD   A,(B8056)            ; nasty (2) attr
     CP   (HL)
     JR   Z,R8B51              ; branch (+0E) if matched nasty
     LD   A,(B8020)            ; bkgnd attr
     CP   (HL)
     DEC  HL                   ; move one col left
     JP   NZ,L8BDD             ; not matched bkgnd (from earlier CP before DEC)
     CP   (HL)
     JP   NZ,L8BDD             ; branch if not matched bkgnd
R8B51:
     LD   A,(B806B)            ; Willy's action
     CP   1
     JP   Z,L8C83              ; branch if JUMPING (do horizontal movement)
     LD   HL,B806A             ; walking/facing flags
     RES  1,(HL)               ; reset walking bit
     OR   A
     JP   Z,L8B9C              ; branch if STANDING (this sets FALLING flag)
     INC  A                    ; else falling so increment fall height
     LD   (B806B),A            ; then store it
     RLCA                      ; **** beeper starts here ****
     RLCA
     RLCA
     RLCA                      ; multiply height by 16
     LD   D,A
     LD   C,20h
     LD   A,(BORDER)           ; maintain border colour
R8B70:
     OUT  (0FEh),A             ; make a noise
     XOR  18h                  ; toggle ear/mic bits
     LD   B,D
R8B75:
     DJNZ R8B75                ; wait/pitch based on height
     DEC  C
     JR   NZ,R8B70 ;$-08       ; **** beeper ends here (after 32 cycles) ****
     LD   A,(B8068)            ; Willy's y * 2
     ADD  A,8                  ; fall 4 pixels down
     LD   (B8068),A            ; then store value

C8B82:                         ; arrive here with A holding Willy's y * 2
     AND  0F0h                 ; align to nearest row (0, 16, 32, etc.)
     LD   L,A
     XOR  A                    ; clear A
     RL   L                    ; double (y * 2) using carry for next ADC
     ADC  A,5Ch                ; 0x5C00 being start of attribute buffer
     LD   H,A
     LD   A,(W806C)            ; Willy's position in attribute buffer
     AND  1Fh                  ; col mask
     OR   L                    ; current row (minus its MSB)
     LD   L,A
     LD   (W806C),HL           ; Willy's position updated
     RET

L8B96:                         ; change from JUMPING to FALLING
     LD   A,6
     LD   (B806B),A            ; sets action/fall distance to 6?
     RET

L8B9C:
     LD   A,2
     LD   (B806B),A            ; sets action flag to FALLING
     RET

; when Willy's head hits a wall while JUMPING
L8BA2:
     LD   A,(B8068)            ; Willy's y * 2
     ADD  A,10h                ; 8 pixels (one block) down
     AND  0F0h                 ; rounded to nearest block
     LD   (B8068),A            ; then store it
     CALL C8B82                ; set Willy's attribute buffer position
     LD   A,2
     LD   (B806B),A            ; sets action flag to FALLING
     LD   HL,B806A             ; walking/facing flag
     RES  1,(HL)               ; reset WALKING bit
     RET
     
; Sent here because a crumble block matched. HL contains the block's address
; in the attribute buffer. Basically it handles the crumbling blocks until
; they finally become bkgnd blocks.
C8BBA: 
     LD   C,L
     LD   A,H
     ADD  A,1Bh
     OR   7
     LD   B,A
R8BC1:
     DEC  B
     LD   A,(BC)
     INC  B
     LD   (BC),A
     DEC  B
     LD   A,B
     AND  7
     JR   NZ,R8BC1 ;$-08
     XOR  A
     LD   (BC),A
     LD   A,B
     ADD  A,7
     LD   B,A
     LD   A,(BC)
     OR   A
     RET  NZ
     LD   A,(B8020)            ; bkgnd attr
     INC  H
     INC  H
     LD   (HL),A               ; set the block to bkgnd
     DEC  H
     DEC  H
     RET

L8BDD:                         ; HL is currently attributes under Willy's feet
     LD   A,(B806B)            ; Willy's action/fall distance
     CP   0Ch
     JP   NC,L8D06             ; branch if fallen beyond 12 (dead)
     LD   E,0FFh               ; equivalent to nothing pressed (active low)
     XOR  A
     LD   (B806B),A            ; Willy's action set to STANDING
     LD   A,(B8044)            ; conveyor attr
     CP   (HL)
     JR   Z,R8BF5              ; branch (+06) if matched conveyor
     INC  HL                   ; move one col right
     CP   (HL)
     JR   NZ,R8BFB             ; branch (+08) if not matched conveyor
R8BF5:
     LD   A,(B806F)            ; conveyor direction
     SUB  3                    ; left = 253 (LSBs 01), right 254 (LSBs 10)
     LD   E,A

R8BFB:                         ; the input routines
     LD   BC,0DFFEh
     IN   A,(C)                ; read Y, U, I, O, P
     AND  1Fh                  ; mask key bits (0-4)
     OR   20h                  ; set bit-5 (used for LEFT presses later)
     AND  E
     LD   E,A
     LD   BC,0FBFEh
     IN   A,(C)                ; read Q, W, E, R, T
     AND  1Fh                  ; mask key bits (0-4)
     RLC  A                    ; changes key sequence to L-R not R-L
     OR   1                    ; 'unpresses' a RIGHT key
     AND  E
     LD   E,A
     LD   B,0F7h
     IN   A,(C)                ; read 1, 2, 3, 4, 5
     RRCA                      ; only interested in key 5 (now at bit-3)
     OR   0F7h                 ; so mask (active low)
     AND  E
     LD   E,A
     LD   B,0EFh
     IN   A,(C)                ; read 6, 7, 8, 9, 0
     OR   0FBh                 ; mask key 8 (bit-2, active low)
     AND  E
     LD   E,A
     LD   A,(KEMP)
     OR   A
     JR   Z,R8C34              ; branch (+0C) if no joystick connected
     LD   BC,1Fh
     IN   A,(C)                ; else read joystick port
     AND  3                    ; left and right mask (bits are xxxFUDLR)
     CPL                       ; because Kempston is active high
     AND  E
     LD   E,A
R8C34:                         ; reach here with E holding key/joystick data
     LD   C,0
     LD   A,E
     AND  2Ah                  ; 0x2A = 101010 - mask all LEFT keys
     CP   2Ah
     JR   Z,R8C3F              ; branch if no LEFT keys pressed inc. conveyor
     LD   C,4                  ; else set LEFT flag (bit-2)
R8C3F:
     LD   A,E
     AND  15h                  ; 0x15 = 010101 - mask all RIGHT keys
     CP   15h
     JR   Z,R8C48              ; branch if no RIGHT keys pressed inc. conveyor
     SET  3,C                  ; else set RIGHT flag (bit-3)
R8C48:
     LD   A,(B806A)            ; flag bits (bit-0 direction, bit-1 walking)
     ADD  A,C                  ; combined with current key presses
     LD   C,A
     LD   B,0
     LD   HL,T8408             ; movement lookup table
     ADD  HL,BC                ; offset into table
     LD   A,(HL)
     LD   (B806A),A            ; flag bits are now set from the table
     LD   BC,07EFEh
     IN   A,(C);               ; read bottom row CAPS to SPACE
     AND  1Fh                  ; mask key bits (0-4)
     CP   1Fh
     JR   NZ,R8C7B             ; branch (+1B) if any key at all is pressed
     LD   B,0EFh
     IN   A,(C)                ; read 6, 7, 8, 9, 0
     AND  9                    ; mask keys 7 and 0
     CP   9
     JR   NZ,R8C7B             ; branch (+11) if any key at all is pressed
     LD   A,(KEMP)             ; no jump key pressed so we check the joystick
     OR   A
     JR   Z,L8C83              ; none connected so do horizontal movement (+13)
     LD   BC,1Fh
     IN   A,(C)                ; read Kempston joystick
     BIT  4,A                  ; fire bit
     JR   Z,L8C83              ; fire not pressed so do horizontal movement
R8C7B:
     XOR  A
     LD   (B806E),A            ; set the jump counter to 0
     INC  A
     LD   (B806B),A            ; set the action flag to 1 (for jumping)

; horizontal movement

L8C83:
     LD   A,(B806A)            ; walking/facing flags
     AND  2                    ; walking bit
     RET  Z                    ; not currently moving so return
     LD   A,(B806A)
     AND  1                    ; facing flag
     JP   Z,L8CCA              ; flag = 0 so go right
                               ; else go left
     LD   A,(B8069)            ; Willy's anim frame
     OR   A
     JR   Z,R8C9C              ; branch (+07) if frame 0 (far left of 16x16)
     DEC  A                    ; else decrement it
     LD   (B8069),A
     RET                       ; nothing else to check

R8C9C:
     LD   HL,(W806C)           ; Willy's position on attribute buffer
     DEC  HL                   ; move one col left
     LD   DE,32
     ADD  HL,DE                ; move one row down
     LD   A,(B803B)            ; wall attr
     CP   (HL)
     RET  Z                    ; return if we hit a wall (Willy's feet block)
     LD   A,(B8068)            ; Willy's y * 2
     AND  0Fh
     JR   Z,R8CB9              ; branch (+0B) if y is block aligned
     LD   A,(B803B)            ; wall attr
     ADD  HL,DE                ; move one row down again 
     CP   (HL)
     RET  Z                    ; return if we hit a wall (below Willy's feet)
     OR   A                    ; [this must be to reset the carry flag?]
     SBC  HL,DE                ; move one row up
R8CB9:
     LD   A,(B803B)            ; wall attr
     OR   A                    ; [this must be to reset the carry flag?]
     SBC  HL,DE                ; move one row up again
     CP   (HL)
     RET  Z                    ; return if we hit a wall (Willy's head block)
     LD   (W806C),HL           ; update Willy's position
     LD   A,3                  ; frame 3 (far right if 16x16)
     LD   (B8069),A            ; update Willy's anim frame
     RET

; moving right

L8CCA:
     LD   A,(B8069)            ; Willy's anim frame
     CP   3
     JR   Z,R8CD6              ; branch (+07) if frame 3 (far right of 16x16)
     INC  A                    ; else increment it
     LD   (B8069),A
     RET                       ; nothing else to check

R8CD6:
     LD   HL,(W806C)           ; Willy's position on attribute buffer
     INC  HL
     INC  HL                   ; move 2 cols right (block to right of Willy)
     LD   DE,32
     LD   A,(B803B)            ; wall attr
     ADD  HL,DE                ; move one row down
     CP   (HL)
     RET  Z                    ; return if we hit a wall (Willy's feet block)
     LD   A,(B8068)            ; Willy's y * 2
     AND  0Fh
     JR   Z,R8CF4              ; branch (+0B) if y is block aligned
     LD   A,(B803B)            ; wall attr
     ADD  HL,DE                ; move one row down again 
     CP   (HL)
     RET  Z                    ; return if we hit a wall (below Willy's feet)
     OR   A                    ; [this must be to reset the carry flag?]
     SBC  HL,DE                ; move one row back up
R8CF4:
     LD   A,(B803B)            ; wall attr
     OR   A                    ; [this must be to reset the carry flag?]
     SBC  HL,DE                ; move one row up again
     CP   (HL)
     RET  Z                    ; return if we hit a wall (Willy's head block)
     DEC  HL                   ; all was ok so move back left by one col
     LD   (W806C),HL           ; and update Willy's position 
     XOR  A                    ; sets frame to zero (far left of 16x16)
     LD   (B8069),A            ; update Willy's anim frame
     RET

L8D05:
     POP  HL
L8D06:
     POP  HL
L8D07:
     LD   A,0FFh               ; Willy has been killed (#806B = 0xFF)
     LD   (B806B),A
     JP   L87A2

; *************************** end of movement code ***************************

; *********************** Some extra collision checking **********************
C923A:
     LD   HL,(W806C)           ; Willy's position in attribute buffer
     LD   DE,31
     LD   C,0Fh
     CALL C925F
     INC  HL                   ; move one col right
     CALL C925F
     ADD  HL,DE                ; move one row down and one col left
     CALL C925F
     INC  HL                   ; move one col right
     CALL C925F
     LD   A,(B8068)            ; Willy's y * 2
     LD   C,A
     ADD  HL,DE                ; move one row down and one col left
     CALL C925F
     INC  HL                   ; move one col right
     CALL C925F
     JR   R927F

C925F:
     LD   A,(B8020)            ; bkgnd attr
     CP   (HL)
     JR   NZ,R9270             ; branch (+0D) if not bkgnd
     LD   A,C
     AND  0Fh
     JR   Z,R9270              ; branch (+08) if y is block aligned
     LD   A,(B8020)            ; bkgnd attr
     OR   7
     LD   (HL),A
R9270:
     LD   A,(B804D)            ; nasty (1) attr
     CP   (HL)
     JP   Z,L8D05              ; killed
     LD   A,(B8056)            ; nasty (2) attr
     CP   (HL)
     JP   Z,L8D05              ; killed
     RET

; draw Willy (run out of steam here but the result is obvious enough)
R927F:
     LD   A,(B8068)            ; Willy's y * 2
     LD   IXH,83h
     LD   IXL,A
     LD   A,(B806A)            ; Willy's walking/facing flags
     AND  1                    ; facing mask
     RRCA
     LD   E,A
     LD   A,(B8069)            ; Willy's anim frame
     AND  3                    ; frames mask
     RRCA
     RRCA
     RRCA                      ; frame multiplied by 8
     OR   E
     LD   E,A
     LD   D,82h                ; as in 0x8200, start address for Willy's frames
     LD   B,10h                ; 16 lines
     LD   A,(W806C)            ; Willy's position in attribute buffer
     AND  1Fh                  ; just the col
     LD   C,A
R92A2:
     LD   A,(IX+0)
     LD   H,(IX+1)
     OR   C
     LD   L,A
     LD   A,(DE)
     OR   (HL)
     LD   (HL),A
     INC  HL
     INC  DE
     LD   A,(DE)
     OR   (HL)
     LD   (HL),A
     INC  IX
     INC  IX
     INC  DE
     DJNZ R92A2                ; loop (-15) until all lines are drawn
     RET
; ************************** End of collision checking ***********************

2. Manic Miner/Jet Set Willy: Comparison of Willy's Movement

Jet Set Willy           Manic Miner (Bug Byte)

900A:                   8C9C:
    LD   HL, (#85D3)        LD   HL,(#806C)     ; Willy's attr buffer position
    LD   A, L                                   
    AND  #1F                                    
    JP   Z, #948A                               ; branch if exit right of room
    ADD  HL, BC                                 
    DEC  HL                 DEC  HL             ; one col left
    LD   DE, 32             LD   DE, 32         
    ADD  HL, DE             ADD  HL, DE         ; one row down
    LD   A, (#80B2)         LD   A, (#803B)     ; wall attr from working room
    CP   (HL)               CP   (HL)           
    RET  Z                  RET  Z              ; return if we hit a wall
    LD   A, (#85CF)         LD   A, (#8068)     ; Willy's y (really y * 2)
    SRA  C                                      ; BC contains ramp offset
    ADD  C                                      
    LD   B, A                                   
    AND  #0F                AND  #0F
    JR   Z, #9032           JR   Z, #8CB9       ; branch if y is block aligned
    LD   A, (#80B2)         LD   A, (#803B)     ; wall attr from working room
    ADD  HL, DE             ADD  HL, DE         ; one row down
    CP   (HL)               CP   (HL)           
    RET  Z                  RET  Z              ; return if we hit a wall
    OR   A                  OR   A              
    SBC  HL, DE             SBC  HL, DE         ; one row back up
9032:                   8CB9:                   
                            LD   A, (#803B)     ; wall attr from working room
    OR   A                  OR   A              
    SBC  HL, DE             SBC  HL,DE          ; one row up again
                            CP   (HL)           
                            RET  Z              ; return if we hit a wall
    LD   (#85D3), HL        LD   (#806C), HL    ; update Willy's position
    LD   A, B                                   
    LD   (#85CF), A                             ; store updated y
    LD   A, 3               LD   A, 3           ; frame 3 (far right of 16x16)
    LD   (#85D2), A         LD   (#8069),A      ; update Willy's anim frame
    RET                     RET

3. Jet Set Willy: Core

; Original disassembly taken from Geoff Eddy's website.

8000          DEFS #0100 ; data for current room
8100          DEFS #0048 ; guardians in current room
8148          DEFS #00B8 ; blank
8200          DEFS #0100 ; screen coord lookup table
                         ; (#8200 + 2y) contains address of first
                         ; byte in line y (of 128).
8300          DEFS #0080 ; x-offsets between successive dots in a rope.
                         ; All bytes are 0-6, thus:
                         ; 00000000000000000000000000000000
                         ; 11111111111122222222222222222222
                         ; 22122112112232323333330000000000
                         ; 00000000000000000000000000000000
8380          DEFS #0080 ; y-offsets, similarly
                         ; 66666666666666666666666666666666
                         ; 66666666666666664664646464446444
                         ; 44444444444444444444440000000000
                         ; 00000000000000000000000000000000

8400          DEFS #0020 ; attributes of the "Press Enter" message
                         ; on the title screen. @@@
8420 21       DEFB #21   ; current room number
8421 00010001 DEFS 4     ; Willy state transition table
8425 01030103 DEFS 4
8429 02000200 DEFS 4
842D 00010203 DEFS 4

8431          DEFS #20   ; UDGs for title screen

8451 FF       DEFB #FF   ; total number of items @@@ (was at #A3FF)
8452 6F6F     DEFM "oo"  ; spare bytes
8454          DEFS #0100 ; scrolling message on title screen
8554          DEFS #20   ; "Items collected" "time left" string
8574          DEFM "Game Over00099999999999999"
858D          DEFS #3E   ; zeros (used to be security messages @@@)


85CB          DEFS #1A   ; timer; just counts from 0 to ff

85CC 10       DEFB #10   ; number of lives @@@ (was 7)

85CD 00       DEFB #00   ; counter
85CE 00       DEFB #00   ; Kempston flag

; These variables are set up when a room is entered and copied to the
; set at 85D7-DD. They are copied back when a life is lost.

85CF D0       DEFB #D0         ; Willy's y * 2 (used for buffer table offset)
85D0 00       DEFB #00         ; Willy's walking/facing flag:
                               ;    bit-0 = off - right, on - left
                               ;    bit-1 = off - still, on - walk
85D1 00       DEFB #00         ; Willy's action:
                               ;    -1: dead
                               ;     0: walking/standing
                               ;     1: jumping
                               ;     2: falling
                               ;    >2: fall height
85D2 00       DEFB #00         ; Willy's anim frame 0-3
85D3 B45D     DEFW #5DB4       ; Willy's position 0101|110Y|YYYX|XXXX
                               ; actually an address in the attribute buffer
85D5 00       DEFB #00         ; Willy's jump phase counter (0 - 18)
85D6 00       DEFB #00         ; Willy's rope dot position or rope grab delay
85D7          DEFS 7           ; copies of #85CF-#85D5 used after dying

85DE 00       DEFB #00         ; number of items collected
85DF 00       DEFB #00         ; game/item status:
                               ;     0: normal
                                     1: all collected
                                     2: running from bedroom
                                     3: in toilet
85E0 00       DEFB #00         ; pause counter

; *************************** start of movement code *************************

8DD3 3AD685   LD   A, (#85D6)  ; Willy's dot when on a rope
8DD6 3D       DEC  A           ; not on a rope = -1
8DD7 CB7F     BIT  7, A        ; not on rope or rope delay signified by bit-7
8DD9 CAD48E   JP   Z, #8ED4    ; branch if on rope
8DDC 3AD185   LD   A, (#85D1)  ; action states
8DDF FE01     CP   #01
8DE1 2053     JR   NZ, #8E36   ; branch if not currently JUMPING
8DE3 3AD585   LD   A, (#85D5)  ; JUMP phase counter
8DE6 E6FE     AND  #FE
8DE8 D608     SUB  #08
8DEA 21CF85   LD   HL, #85CF   ; address of Willy's y * 2
8DED 86       ADD  (HL)
8DEE 77       LD   (HL), A     ; jump height is (y * 2 & 0xFE) - 8
8DEF FEF0     CP   #F0
8DF1 D2B094   JP   NC, #94B0   ; jumped into room above
8DF4 CD9C8E   CALL #8E9C       ; update Willy's attribute buffer position
8DF7 3AB280   LD   A, (#80B2)  ; wall attr
8DFA BE       CP   (HL)
8DFB CABC8E   JP   Z, #8EBC    ; branch if Willy's head hits wall
8DFE 23       INC  HL          ; move one col right
8DFF BE       CP   (HL)
8E00 CABC8E   JP   Z, #8EBC    ; branch if Willy's head hits wall
8E03 3AD585   LD   A, (#85D5)  ; JUMP phase counter
8E06 3C       INC  A
8E07 32D585   LD   (#85D5), A  ; store incremented JUMP phase
8E0A D608     SUB  #08         ; **** beeper starts here ****
8E0C F2118E   JP   P, #8E11    ; branch if SUB 8 goes below 0

8E0F ED44     NEG              ; else phase = 0 - phase
8E11 3C       INC  A
8E12 07       RLCA
8E13 07       RLCA
8E14 07       RLCA             ; multiply height by 16
8E15 57       LD   D, A
8E16 0E20     LD   C, #20
8E18 3ADE80   LD   A, (#80DE)  ; maintain border colour
8E1B D3FE     OUT  (#FE), A
8E1D EE18     XOR  #18         ; toggle ear/mic bits
8E1F 42       LD   B, D
8E20 10FE     DJNZ  #8E20      ; wait/pitch based on height

8E22 0D       DEC  C
8E23 20F6     JR   NZ, #8E1B   ; **** beeper ends here (after 32 cycles) ****
8E25 3AD585   LD   A, (#85D5)  ; JUMP phase counter
8E28 FE12     CP   #12         ; 0x12 being 18 steps in a JUMP
8E2A CAB08E   JP   Z, #8EB0    ; branch if at end of JUMP phase
8E2D FE10     CP   #10
8E2F 2805     JR   Z, #8E36
8E31 FE0D     CP   #0D
8E33 C2BC8F   JP   NZ, #8FBC   ; branch if not 12 (do horizontal movement)

8E36 3ACF85   LD   A, (#85CF)  ; Willy's y * 2
8E39 E60E     AND  #0E         ; *** note: this is 0x0E in MM ***
8E3B 2025     JR   NZ, #8E62   ; branch if y isn't row aligned
8E3D 2AD385   LD   HL, (#85D3) ; Willy's position in attribute buffer
8E40 114000   LD   DE, 64
8E43 19       ADD  HL, DE
8E44 CB4C     BIT  1, H
8E46 C2D294   JP   NZ, #94D2   ; branch if we're in the room below

8E49 3ABB80   LD   A, (#80BB)  ; nasty attr
8E4C BE       CP   (HL)
8E4D 2813     JR   Z, #8E62    ; branch if matched nasty
8E4F 23       INC  HL          ; move one col right
8E50 3ABB80   LD   A, (#80BB)  ; nasty attr
8E53 BE       CP   (HL)
8E54 280C     JR   Z, #8E62    ; branch if matched nasty

8E56 3AA080   LD   A, (#80A0)  ; bkgnd attr
8E59 BE       CP   (HL)
8E5A 2B       DEC  HL          ; move one col left (resets HL to left foot)
8E5B C2D48E   JP   NZ, #8ED4   ; branch if not matched bkgnd
8E5E BE       CP   (HL)
8E5F C2D48E   JP   NZ, #8ED4   ; branch if not matched bkgnd

; There is no ground below Willy; why?

8E62 3AD185   LD   A, (#85D1)  ; Willy's action (standing/jumping/falling)
8E65 FE01     CP   #01
8E67 CABC8F   JP   Z, #8FBC    ; branch if JUMPING (do horizontal movement)
8E6A 21D085   LD   HL, #85D0   ; walking/facing flags
8E6D CB8E     RES  1, (HL)     ; reset walking bit
8E6F 3AD185   LD   A, (#85D1)  ; Willy's action (dunno why it's LDed again?)
8E72 B7       OR   A
8E73 CAB68E   JP   Z, #8EB6    ; branch if STANDING (this sets FALLING flag)
8E76 3C       INC  A           ; else falling so increment fall height
8E77 FE10     CP   #10         ; *** MM differences ***
8E79 2002     JR   NZ, #8E7D   ; *** need to investigate!!! ***

8E7B 3E0C     LD   A, #0C
8E7D 32D185   LD   (#85D1), A  ; then store it
8E80 07       RLCA             ; **** beeper starts here ****
8E81 07       RLCA
8E82 07       RLCA
8E83 07       RLCA             ; multiply height by 16
8E84 57       LD   D, A
8E85 0E20     LD   C, #20
8E87 3ADE80   LD   A, (#80DE)  ; maintain border colour
8E8A D3FE     OUT  (#FE), A    ; make a noise
8E8C EE18     XOR  #18         ; toggle ear/mic bits
8E8E 42       LD   B, D
8E8F 10FE     DJNZ  #8E8F
8E91 0D       DEC  C
8E92 20F6     JR   NZ, #8E8A   ; **** beeper ends here (after 32 cycles) ****
8E94 3ACF85   LD   A, (#85CF)  ; Willy's y * 2
8E97 C608     ADD  #08         ; 4 pixels down
8E99 32CF85   LD   (#85CF), A  ; then store it

                               ; arrive here with A holding Willy's y * 2
8E9C E6F0     AND  #F0         ; align to nearest row (0, 16, 32, etc.)
8E9E 6F       LD   L, A
8E9F AF       XOR  A
8EA0 CB15     RL   L
8EA2 CE5C     ADC  #5C         ; 0x5C00 being start of attribute buffer
8EA4 67       LD   H, A
8EA5 3AD385   LD   A, (#85D3)  ; Willy's attribute buffer position
8EA8 E61F     AND  #1F         ; col mask
8EAA B5       OR   L
8EAB 6F       LD   L, A
8EAC 22D385   LD   (#85D3), HL ; Willy's position updated
8EAF C9       RET

;
                                ; change from JUMPING to FALLING
8EB0 3E06     LD   A, #06
8EB2 32D185   LD   (#85D1), A   ; sets action/fall distance to 6?
8EB5 C9       RET

;

8EB6 3E02     LD   A, #02
8EB8 32D185   LD   (#85D1), A  ; sets action flag to FALLING
8EBB C9       RET

; Willy has hit something while jumping.

8EBC 3ACF85   LD   A, (#85CF)  ; Willy's y * 2
8EBF C610     ADD  #10         ; 8 pixels (one block) down
8EC1 E6F0     AND  #F0         ; rounded to nearest block
8EC3 32CF85   LD   (#85CF), A  ; then store it
8EC6 CD9C8E   CALL #8E9C       ; set Willy's attribute buffer position
8EC9 3E02     LD   A, #02
8ECB 32D185   LD   (#85D1), A  ; sets action flag to FALLING
8ECE 21D085   LD   HL, #85D0   ; walking/facing flag
8ED1 CB8E     RES  1, (HL)     ; reset WALKING bit
8ED3 C9       RET

                               ; HL is currently attributes under Willy's feet
8ED4 1EFF     LD   E, #FF      ; equivalent to nothing pressed (active low)
8ED6 3AD685   LD   A, (#85D6)  ; Willy's y when on a rope
8ED9 3D       DEC  A
8EDA CB7F     BIT  7, A
8EDC 281C     JR   Z, #8EFA    ; branch if on a rope
8EDE 3AD185   LD   A, (#85D1)  ; Willy's action/fall distance
8EE1 FE0C     CP   #0C
8EE3 D2B790   JP   NC, #90B7   ; branch if fallen beyond 12 (dead)
8EE6 AF       XOR  A
8EE7 32D185   LD   (#85D1), A  ; Willy's action set to STANDING (0)
8EEA 3ACD80   LD   A, (#80CD)  ; conveyor attr
8EED BE       CP   (HL)
8EEE 2804     JR   Z, #8EF4    ; branch if matched conveyor
8EF0 23       INC  HL          ; move one col right
8EF1 BE       CP   (HL)
8EF2 2006     JR   NZ, #8EFA   ; branch if not matched conveyor
8EF4 3AD680   LD   A, (#80D6)  ; conveyor direction
8EF7 D603     SUB  #03         ; left = 253 (LSBs 01), right 254 (LSBs 10)
8EF9 5F       LD   E, A
                               ; the input routines
8EFA 01FEDF   LD   BC, #DFFE
8EFD ED78     IN   A, (C)      ; read Y, U, I, O, P
8EFF E61F     AND  #1F         ; mask only key bits (0-4)
8F01 F620     OR   #20         ; set bit-5 (used for LEFT presses later)
8F03 A3       AND  E
8F04 5F       LD   E, A
8F05 3ADF85   LD   A, (#85DF)  ; All items collected? *** Not MM ***
8F08 E602     AND  #02
8F0A 0F       RRCA
8F0B AB       XOR  E
8F0C 5F       LD   E, A        ; *** End of not MM section ***
8F0D 01FEFB   LD   BC, #FBFE
8F10 ED78     IN   A, (C)      ; read Q, W, E, R, T
8F12 E61F     AND  #1F         ; mask key bits (0-4)
8F14 CB07     RLC  A           ; changes key sequence to L-R not R-L
8F16 F601     OR   #01         ; 'unpresses' a RIGHT key
8F18 A3       AND  E
8F19 5F       LD   E, A
8F1A 06EF     LD   B, #EF
8F1C ED78     IN   A, (C)      ; read 6, 7, 8, 9, 0
8F1E 0F       RRCA             ; only interested in key 6 (now at bit-3)
8F1F F6F7     OR   #F7         ; so mask (active low)
8F21 A3       AND  E
8F22 5F       LD   E, A
8F23 06EF     LD   B, #EF
8F25 ED78     IN   A, (C)      ; read 6, 7, 8, 9, 0
8F27 F6FB     OR   #FB         ; mask key 8 (bit-2, active low)
8F29 A3       AND  E
8F2A 5F       LD   E, A
8F2B ED78     IN   A, (C)      ; read 6, 7, 8, 9, 0
8F2D 0F       RRCA             ; only interested in key 7 (now at bit-2)
8F2E F6FB     OR   #FB         ; so mask (active low)
8F30 A3       AND  E
8F31 5F       LD   E, A
8F32 3ACE85   LD   A, (#85CE)
8F35 B7       OR   A
8F36 280A     JR   Z, #8F42    ; branch if no joystick connected
8F38 011F00   LD   BC, #001F
8F3B ED78     IN   A, (C)      ; else read joystick port
8F3D E603     AND  #03         ; left and right mask (bits are xxxFUDLR)
8F3F 2F       CPL              ; because Kempston is active high
8F40 A3       AND  E           ; reach here with E holding key/joystick data
8F41 5F       LD   E, A
8F42 0E00     LD   C, #00
8F44 7B       LD   A, E
8F45 E62A     AND  #2A
8F47 FE2A     CP   #2A
8F49 2806     JR   Z, #8F51
8F4B 0E04     LD   C, #04
8F4D AF       XOR  A
8F4E 32E085   LD   (#85E0), A
8F51 7B       LD   A, E
8F52 E615     AND  #15
8F54 FE15     CP   #15
8F56 2806     JR   Z, #8F5E
8F58 CBD9     SET  3, C
8F5A 8F       ADC  A
8F5B 32E085   LD   (#85E0), A
8F5E 3AD085   LD   A, (#85D0)
8F61 81       ADD  C
8F62 4F       LD   C, A
8F63 0600     LD   B, #00
8F65 212184   LD   HL, #8421
8F68 09       ADD  HL, BC
8F69 7E       LD   A, (HL)
8F6A 32D085   LD   (#85D0), A
8F6D 01FE7E   LD   BC, #7EFE
8F70 ED78     IN   A, (C)      ; read bottom row CAPS to SPACE
8F72 E61F     AND  #1F         ; mask key bits (0-4)
8F74 FE1F     CP   #1F
8F76 2017     JR   NZ, #8F8F   ; branch if any key at all is pressed
8F78 06EF     LD   B, #EF
8F7A ED78     IN   A, (C)      ; read 6, 7, 8, 9, 0
8F7C CB47     BIT  0, A
8F7E 280F     JR   Z, #8F8F    ; branch if 0 (bit-0) is pressed
8F80 3ACE85   LD   A, (#85CE)  ; no jump key pressed so we check the joystick
8F83 B7       OR   A
8F84 2836     JR   Z, #8FBC    ; none connected so do horizontal movement
8F86 011F00   LD   BC, #001F
8F89 ED78     IN   A, (C)      ; read Kempston joystick
8F8B CB67     BIT  4, A        ; fire bit
8F8D 282D     JR   Z, #8FBC    ; fire not pressed so do horizontal movement
8F8F 3ADF85   LD   A, (#85DF)  ; *** Item to collect? ***
8F92 CB4F     BIT  1, A
8F94 2026     JR   NZ, #8FBC
8F96 AF       XOR  A
8F97 32D585   LD   (#85D5), A  ; set JUMP phase counter to 0
8F9A 32E085   LD   (#85E0), A  ; set PAUSE counter to 0
8F9D 3C       INC  A
8F9E 32D185   LD   (#85D1), A  ; set the action flag to 1 (for jumping)
8FA1 3AD685   LD   A, (#85D6)  ; Willy's y when on a rope
8FA4 3D       DEC  A
8FA5 CB7F     BIT  7, A
8FA7 2013     JR   NZ, #8FBC   ; branch if not on a rope
8FA9 3EF0     LD   A, #F0
8FAB 32D685   LD   (#85D6), A
8FAE 3ACF85   LD   A, (#85CF)
8FB1 E6F0     AND  #F0
8FB3 32CF85   LD   (#85CF), A
8FB6 21D085   LD   HL, #85D0
8FB9 CBCE     SET  1, (HL)
8FBB C9       RET

; could be move decision code?

8FBC 3AD085   LD   A, (#85D0)  ; move state
8FBF E602     AND  #02
8FC1 C8       RET  Z
8FC2 3AD685   LD   A, (#85D6)  ; Willy's y when on a rope
8FC5 3D       DEC  A
8FC6 CB7F     BIT  7, A
8FC8 C8       RET  Z           ; return if on rope
8FC9 3AD085   LD   A, (#85D0)
8FCC E601     AND  #01
8FCE CA4290   JP   Z, #9042    ; move left

8FD1 3AD285   LD   A, (#85D2)  ; Willy's phase
8FD4 B7       OR   A
8FD5 2805     JR   Z, #8FDC
8FD7 3D       DEC  A
8FD8 32D285   LD   (#85D2), A
8FDB C9       RET

; move Willy left

8FDC 3AD185   LD   A, (#85D1)  ; Willy's action states
8FDF 010000   LD   BC, #0000
8FE2 FE00     CP   #00
8FE4 2024     JR   NZ, #900A
8FE6 2AD385   LD   HL, (#85D3)
8FE9 010000   LD   BC, #0000
8FEC 3ADA80   LD   A, (#80DA)  ; ramp direction (0 = L, 1 = R)
8FEF 3D       DEC  A
8FF0 F6A1     OR   #A1
8FF2 EEE0     XOR  #E0         ; ramp direction is now #1F = L, #41 = R
8FF4 5F       LD   E, A
8FF5 1600     LD   D, #00
8FF7 19       ADD  HL, DE
8FF8 3AC480   LD   A, (#80C4)  ; ramp attr
8FFB BE       CP   (HL)
8FFC 200C     JR   NZ, #900A
8FFE 012000   LD   BC, #0020
9001 3ADA80   LD   A, (#80DA)
9004 B7       OR   A
9005 2003     JR   NZ, #900A
9007 01E0FF   LD   BC, #FFE0
900A 2AD385   LD   HL, (#85D3) ; lots of stuff jumps to here...
900D 7D       LD   A, L
900E E61F     AND  #1F
9010 CA8A94   JP   Z, #948A
9013 09       ADD  HL, BC
9014 2B       DEC  HL
9015 112000   LD   DE, 32
9018 19       ADD  HL, DE      ; move one row down
9019 3AB280   LD   A, (#80B2)  ; wall attr
901C BE       CP   (HL)
901D C8       RET  Z           ; return if we hit a wall (Willy's feet block)
901E 3ACF85   LD   A, (#85CF)
9021 CB29     SRA  C
9023 81       ADD  C
9024 47       LD   B, A
9025 E60F     AND  #0F
9027 2809     JR   Z, #9032    ; branch if y is block aligned
9029 3AB280   LD   A, (#80B2)  ; wall attr
902C 19       ADD  HL, DE
902D BE       CP   (HL)
902E C8       RET  Z
902F B7       OR   A
9030 ED52     SBC  HL, DE      ; move one row up
9032 B7       OR   A
9033 ED52     SBC  HL, DE      ; move one row up again **MM tests this block**
9035 22D385   LD   (#85D3), HL ; update Willy's position
9038 78       LD   A, B
9039 32CF85   LD   (#85CF), A
903C 3E03     LD   A, #03      ; frame 3 (far right if 16x16)
903E 32D285   LD   (#85D2), A  ; update Willy's anim frame
9041 C9       RET

; moving right

9042 3AD285   LD   A, (#85D2)  ; Willy's anim frame
9045 FE03     CP   #03
9047 2805     JR   Z, #904E    ; branch if frame 3 (far right of 16x16)
9049 3C       INC  A           ; else increment it
904A 32D285   LD   (#85D2), A
904D C9       RET              ; nothing else to check

904E 3AD185   LD   A, (#85D1)  ; Willy's action states
9051 010000   LD   BC, #0000
9054 B7       OR   A
9055 2021     JR   NZ, #9078   ; if fall/move isn't 0 (ie not standing)
9057 2AD385   LD   HL, (#85D3) ; Willy's position on attribute buffer
905A 3ADA80   LD   A, (#80DA)  ; ramp direction (0 = L, 1 = R)
905D 3D       DEC  A
905E F69D     OR   #9D
9060 EEBF     XOR  #BF         ; ramp direction is now #40 = L, #22 = R
9062 5F       LD   E, A
9063 1600     LD   D, #00      ; attribute we're looking at is now either
9065 19       ADD  HL, DE      ; two rows or one row and two cols below
9066 3AC480   LD   A, (#80C4)  ; ramp attr
9069 BE       CP   (HL)
906A 200C     JR   NZ, #9078   ; branch if we're not on a ramp
906C 012000   LD   BC, #0020
906F 3ADA80   LD   A, (#80DA)  ; ramp direction
9072 B7       OR   A
9073 2803     JR   Z, #9078    ; ramp is left
9075 01E0FF   LD   BC, #FFE0   ; either #0020 (left) or #FFE0 (right) or 0
9078 2AD385   LD   HL, (#85D3) ; Willy's position on attribute buffer
907B 09       ADD  HL, BC      ; now we're either one row below or one up...
907C 23       INC  HL          ; ...if on a ramp, or none otherwise
907D 23       INC  HL          ; and two cols right (block to right of Willy)
907E 7D       LD   A, L
907F E61F     AND  #1F
9081 CA9E94   JP   Z, #949E    ; off the left of the screen
9084 112000   LD   DE, 32
9087 3AB280   LD   A, (#80B2)  ; wall attr
908A 19       ADD  HL, DE      ; move one row down
908B BE       CP   (HL)
908C C8       RET  Z           ; return if we hit a wall (Willy's feet block)
908D 3ACF85   LD   A, (#85CF)  ; Willy's y
9090 CB29     SRA  C
9092 81       ADD  C
9093 47       LD   B, A        ; store y * 2 for later
9094 E60F     AND  #0F
9096 2809     JR   Z, #90A1    ; branch if y is block aligned
9098 3AB280   LD   A, (#80B2)  ; wall attr
909B 19       ADD  HL, DE      ; move one row down again 
909C BE       CP   (HL)
909D C8       RET  Z           ; return if we hit a wall (below Willy's feet)
909E B7       OR   A           ; [this must be to reset the carry flag?]
909F ED52     SBC  HL, DE      ; move one row back up

90A1 3AB280   LD   A, (#80B2)  ; wall attr
90A4 B7       OR   A           ; [this must be to reset the carry flag?]
90A5 ED52     SBC  HL, DE      ; move one row up again
90A7 BE       CP   (HL)
90A8 C8       RET  Z           ; return if we hit a wall (Willy's head block)
90A9 2B       DEC  HL          ; all was ok so move back left by one col
90AA 22D385   LD   (#85D3), HL ; and update Willy's position 
90AD AF       XOR  A           ; sets frame to zero (far left of 16x16)
90AE 32D285   LD   (#85D2), A  ; update Willy's anim frame
90B1 78       LD   A, B        
90B2 32CF85   LD   (#85CF), A  ; updates Willy's y * 2
90B5 C9       RET

90B6 E1       POP  HL
90B7 E1       POP  HL
90B8 3EFF     LD   A, #FF      ; Willy has been killed (#85D1 = 0xFF)
90BA 32D185   LD   (#85D1), A
90BD C3F589   JP   #89F5


; *************************** end of movement code ***************************

; *********************** Some extra collision checking **********************

95C8 2AD385   LD   HL, (#85D3) ; Willy's position on attribute buffer
95CB 0600     LD   B, #00
95CD 3ADA80   LD   A, (#80DA)  ; ramp direction (0 = L, 1 = R)
95D0 E601     AND  #01
95D2 C640     ADD  #40         ; down 2 rows
95D4 5F       LD   E, A
95D5 1600     LD   D, #00
95D7 19       ADD  HL, DE      ; L ramps move 2 rows; R moves 1 col & 2 rows
95D8 3AC480   LD   A, (#80C4)  ; ramp attr
95DB BE       CP   (HL)
95DC 201A     JR   NZ, #95F8   ; branch if we're not on a ramp
95DE 3AD185   LD   A, (#85D1)  ; Willy's action/fall distance
95E1 B7       OR   A
95E2 2014     JR   NZ, #95F8   ; branch if we're not walking or standing
95E4 3AD285   LD   A, (#85D2)  ; Willy's anim frame
95E7 E603     AND  #03         ; frames mask
95E9 07       RLCA
95EA 07       RLCA             ; frame multiplied by 4
95EB 47       LD   B, A
95EC 3ADA80   LD   A, (#80DA)  ; ramp direction (0 = L, 1 = R)
95EF E601     AND  #01
95F1 3D       DEC  A           ; gives L = 0xFF or R = 0x00
95F2 EE0C     XOR  #0C         ; gives L = 0xF3 or R = 0x0C
95F4 A8       XOR  B           ; basically L ramps give an offset of FRAME * 2
95F5 E60C     AND  #0C         ; whereas R ramps offset by (3 - FRAME) * 2
95F7 47       LD   B, A        ; (given that Willy's y is multiplied by 2)

; Normal MM-style code starts here.

95F8 2AD385   LD   HL, (#85D3) ; Willy's position in attribute buffer
95FB 111F00   LD   DE, #001F
95FE 0E0F     LD   C, #0F
9600 CD1E96   CALL #961E       ; test for nasty (head left)
9603 23       INC  HL          ; move one col right
9604 CD1E96   CALL #961E       ; test for nasty (head right)
9607 19       ADD  HL, DE      ; move one row down and one col left
9608 CD1E96   CALL #961E       ; test for nasty (legs left)
960B 23       INC  HL          ; move one col right
960C CD1E96   CALL #961E       ; test for nasty (legs right)
960F 3ACF85   LD   A, (#85CF)  ; Willy's y * 2
9612 80       ADD  B           ; compensate for ramp (see #95C8)
9613 4F       LD   C, A
9614 19       ADD  HL, DE      ; move one row down and one col left
9615 CD1E96   CALL #961E       ; test for nasty (under left foot)
9618 23       INC  HL          ; move one col right
9619 CD1E96   CALL #961E       ; test for nasty (under right foot)
961C 1819     JR   #9637       ; no nasties so draw Willy

; code to test for nasty blocks (also sets Willy's colour attributes)
961E 3AA080   LD   A, (#80A0)  ; bkgnd attr
9621 BE       CP   (HL)
9622 200B     JR   NZ, #962F   ; branch if not bkgnd
9624 79       LD   A, C        ; either contains 0x0F or y * 2 + ramp offset
9625 E60F     AND  #0F         ; block mask (given that y is multiplied by 2)
9627 2806     JR   Z, #962F    ; branch if block aligned (no need to colour)
9629 3AA080   LD   A, (#80A0)  ; bkgnd attr
962C F607     OR   #07         ; set the ink to white
962E 77       LD   (HL), A     ; then put this colour in the attribute buffer

962F 3ABB80   LD   A, (#80BB)  ; nasty attr
9632 BE       CP   (HL)
9633 CAB690   JP   Z, #90B6    ; branch if killed
9636 C9       RET

; code to draw Willy
9637 3ACF85   LD   A, (#85CF)  ; Willy's y * 2
963A 80       ADD  B           ; compensate for ramp (see #95C8)

; the remaining draw code doesn't need commenting

; ****************************************************************************

4. Jet Set Willy: Horizontal and Vertical Guardians

IX+0 -- 3 MSBs are continuous frame count (HGs use MSB as direction)

; Horizontal guardian
; -------------------
L9133:    BIT  7,(IX+&00)     ; direction
          JR   NZ,L915C       ; branch if moving right (bit-7 == 1)

; Move horizontal guardian left
; -----------------------------
          LD   A,(IX+&00)
          SUB  &20            ; decrement 'frame' counter by 1
          AND  &7F            ; set MSB to 0 (moving left flag)
          LD   (IX+&00),A     ; update it
          CP   &60
          JR   C,L91B6        ; branch if frame != 3
          LD   A,(IX+&02)     ; frame and col whatnot
          AND  &1F            ; mask just the col
          CP   (IX+&06)       ; compare with min (left) boundary
          JR   Z,L9156        ; branch if reached boundary (flip direction)
          DEC  (IX+&02)       ; else decrement the current col
          JR   L91B6          ; do next guardian

L9156:    LD   (IX+&00),&81   ; set to moving right and zero frame count
          JR   L91B6          ; do next guardian

; Move horizontal guardian right
; ------------------------------
L915C:    LD   A,(IX+&00)
          ADD  A,&20          ; increment 'frame' counter by 1
          OR   &80            ; set MSB to 1 (moving right flag)
          LD   (IX+&00),A     ; update it
          CP   &A0
          JR   NC,L91B6
          LD   A,(IX+&02)     ; frame and col whatnot
          AND  &1F            ; mask just the col
          CP   (IX+&07)       ; compare with max (right) boundary
          JR   Z,L9179        ; branch if reached boundary (flip direction)
          INC  (IX+&02)       ; else increment the current col
          JR   L91B6          ; do next guardian

L9179:    LD   (IX+&00),&61   ; set to moving left and on last frame
          JR   L91B6          ; do next guardian
          
; Vertical guardian
; -----------------
L917F:    LD   A,(IX+&00)
          XOR  &08            ; flip bit-3
          LD   (IX+&00),A
          AND  &18            ; bit-4 and bit-3 (anim speed and cycling thing)
          JR   Z,L9193        ; branch to skip every other frame (anim slow)
          LD   A,(IX+&00)
          ADD  A,&20          ; else increment the frame
          LD   (IX+&00),A
L9193:    LD   A,(IX+&03)     ; y (x2)
          ADD  A,(IX+&04)     ; add the step (x2)
          LD   (IX+&03),A     ; update y (x2)
          CP   (IX+&07)       ; compare with max (bottom) boundary
          JR   NC,L91AE       ; branch if >= max
          CP   (IX+&06)       ; compare with min (top) boundary
          JR   Z,L91A8        ; branch if == min
          JR   NC,L91B6       ; branch if >= min (do next guard)
L91A8:    LD   A,(IX+&06)
          LD   (IX+&03),A     ; set the y (x2) to min (top) boundary
L91AE:    LD   A,(IX+&04)
          NEG                 ; negate velocity
          LD   (IX+&04),A
          
L91B6:    LD   DE,&0008       ; 8 bytes per record
          ADD  IX,DE
          JP   L90C4          ; do next guardian

5. Jet Set Willy: Arrows

IX+0 bit-7 direction
IX+2 
IX+4 start col, later used for actual working col


L9237:    BIT  7,(IX+&00)      ; arrow direction (0 = L, 1 = R)
          JR   NZ,L9244        ; branch if moving right
          DEC  (IX+&04)        ; move left one col
          LD   C,&2C           ; 44
          JR   L9249

L9244:    INC  (IX+&04)        ; move right one col
          LD   C,&F4           ; 244
L9249:    LD   A,(IX+&04)      ; arrow col/timer
          CP   C
          JR   NZ,L9262        ; branch if not equal to 44 (L) or 244 (R)
          LD   BC,&0280
          LD   A,(BORDER)
L9255:    OUT  (&FE),A         ; else do beep
          XOR  &18
L9259:    DJNZ L9259
          LD   B,C
          DEC  C
          JR   NZ,L9255
          JP   L93B3           ; after beeping move on to next guardian

L9262:    AND  &E0
          JP   NZ,L93B3        ; branch if the current col is off-screen
          LD   E,(IX+&02)      ; y * 2 [need to look at - block aligned?]
          LD   D,&82           ; 0x8200 buffer row lookup table
          LD   A,(DE)
          ADD  A,(IX+&04)      ; current col
          LD   L,A             ; L holds low-byte of attribute buffer
          LD   A,E
          AND  &80
          RLCA
          OR   &5C
          LD   H,A             ; HL now points to arrow's attr buffer position
          LD   (IX+&05),&00    ; (for later testing *no* pixels for collision)
          LD   A,(HL)          ; get the attr where the arrow would be
          AND  &07             ; ink mask
          CP   &07
          JR   NZ,L9286        ; branch if not white (white ink is Willy)
          DEC  (IX+&05)        ; else IX+5 = 255 (test for collision later)
L9286:    LD   A,(HL)          ; get the attr where the arrow would be
          OR   &07             ; set the ink to white
          LD   (HL),A          ; then put it back in the buffer
          INC  DE              ; down one row? [arrows appear not to start @ 0]
          LD   A,(DE)
          LD   H,A
          DEC  H               ; [too tired right now! Anyway HL is pixel addr]
          LD   A,(IX+&06)      ; top/bottom pixel pattern
          LD   (HL),A          ; draw the top of the arrow
          INC  H               ; down one row
          LD   A,(HL)          ; get the pixels where the arrow middle wil be
          AND  (IX+&05)        ; holds 255 if the ink is white or 0 otherwise
          JP   NZ,DIED1        ; branch if anything is there (and on white)
          LD   (HL),&FF        ; draw the middle of the arrow (an 8px line)
          INC  H               ; down one row
          LD   A,(IX+&06)      ; top/bottom pixel pattern
          LD   (HL),A          ; draw the bottom of the arrow
          JP   L93B3

6. Jet Set Willy: Ropes

0 guardian type (with bit-7 for swing direction)
1 [moving] swing table index offset
2 [init] start col
3 [drawing] dot col (currently being drawn into)
4 rope length
5 [drawing] dot bitmap pattern
6 *not used* (I'm guessing it was pointing to the swing table)
7 maximum rope swing
8 
9 [drawing] dot y * 2 (offset into lookup table)
A 
B [drawing] Willy is on THIS rope
C 

#85CF:    Willy's current rope dot

T8100:    DEFS #0048           ; guardian records
T8200:    DEFS #0100           ; screen buffer y lookup table

; Rope swing table
T8300:    DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; x offsets
          DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
          DEFB 1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2
          DEFB 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
          DEFB 2,2,1,2,2,1,1,2,1,1,2,2,3,2,3,2
          DEFB 3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0
          DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
          DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
          DEFB 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6 ; y offsets
          DEFB 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6
          DEFB 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6
          DEFB 4,6,6,4,6,4,6,4,6,4,4,4,6,4,4,4
          DEFB 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4
          DEFB 4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0
          DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
          DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

; ******************************** Move rope *********************************
          BIT  7,(IX+&00)      ; rope direction (0 = left, 1 = right)
          JR   Z,L90FF         ; branch if moving left else moving right
          LD   A,(IX+&01)      ; rope swing table index offset
          BIT  7,A
          JR   Z,L90F5         ; branch if not negative
          SUB  &02             ; subract 2 (drawing LHS)
          CP   &94
          JR   NC,L911D
          SUB  &02             ; subract another 2 if less than -108
          CP   &80
          JR   NZ,L911D
          XOR  A               ; set the swing to 0 if equal to -128
          JR   L911D

L90F5:    ADD  A,&02           ; add 2 (drawing RHS)
          CP   &12
          JR   NC,L911D
          ADD  A,&02           ; add another 2 if less than 18
          JR   L911D
                               ; right rope swing code
L90FF:    LD   A,(IX+&01)
          BIT  7,A
          JR   NZ,L9115        ; branch if negative
          SUB  &02             ; subract 2 (drawing RHS)
          CP   &14
          JR   NC,L911D
          SUB  &02             ; subract another 2 if less than 20
          OR   A
          JR   NZ,L911D
          LD   A,&80           ; set the swing to -128 if equal to 0
          JR   L911D

L9115:    ADD  A,&02           ; add 2 (drawing LHS)
          CP   &92
          JR   NC,L911D
          ADD  A,&02           ; add another 2 if less than -112
          
L911D:    LD   (IX+&01),A      ; update the current rope swing
          AND  &7F
          CP   (IX+&07)        ; maximum rope swing
          JP   NZ,L91B6        ; branch if rope can continue swinging
          LD   A,(IX+&00)
          XOR  &80             ; else reverse the rope's direction
          LD   (IX+&00),A
          JP   L91B6
          
L91B6:                         ; continue for the other guardians
          
; ***************************** Draw guardians *******************************

L91BE:    LD   IX,&8100        ; current room's guardian records
L91C2:    LD   A,(IX+&00)      ; guardian type
          CP   &FF
          RET  Z               ; return to main loop if at end of records
          AND  &07             ; guardian type mask
          JP   Z,L93B3         ; branch if an empty record
          CP   &03
          JP   Z,L92A4         ; branch if type 3 (rope)
          CP   &04
          JR   Z,L9237         ; branch if type 4 (arrow)
                               ; [code continues for H and V guardians]

; rope entry point
                               ; [initialise this rope]
L92A4:    LD   IY,&8200        ; screen buffer y lookup table
          LD   (IX+&09),&00    ; current dot y * 2 (offset into lookup table)
          LD   A,(IX+&02)      ; rope start col
          LD   (IX+&03),A      ; current dot col (reset to start col)
          LD   (IX+&05),&80    ; dot bitmap pattern (LHS pixel to start with)
                               ; [start of the loop to draw the dots]
L92B6:    LD   A,(IY+&00)      ; low-byte of buffer y table
          ADD  A,(IX+&03)      ; add current dot col to get screen low-byte
          LD   L,A
          LD   H,(IY+&01)      ; HL at screen address of current dot col
          LD   A,(#85D6)       ; rope dot Willy is on (0 when not on a rope)
          OR   A
          JR   NZ,L92D6        ; branch if Willy is already on a rope
                               ; [find if Willy is touching a rope]
          LD   A,(IX+&05)      ; bitmap pattern of previous dot
          AND  (HL)            ; compare the dot pattern with screen buffer
          JR   Z,L930E         ; branch if nothing is where the rope should be
          LD   A,(IX+&09)      ; else Willy is touching so take current dot y
          LD   (#85D6),A       ; set Willy's rope dot to current dot
          SET  0,(IX+&0B)      ; set the flag to show Willy is on this rope
                               ; [set Willy's frame and position]
L92D6:    CP   (IX+&09)        ; compare Willy's dot with this rope dot
          JR   NZ,L930E        ; branch if Willy is not at this dot
          BIT  0,(IX+&0B)
          JR   Z,L930E         ; branch if Willy is not on this rope
          LD   B,(IX+&03)      ; used later for setting Willy's col to dot col
          LD   A,(IX+&05)      ; dot bitmap pattern
          LD   C,&01           ; used later for setting Willy's frame to 1
          CP   &04
          JR   C,L92FC         ; branch if dot pattern fits ......XX
          LD   C,&00           ; used later for setting Willy's frame to 0
          CP   &10
          JR   C,L92FC         ; branch if dot pattern fits ....XX..
          DEC  B               ; used later for moving Willy one col left
          LD   C,&03           ; used later for setting Willy's frame to 3
          CP   &40
          JR   C,L92FC         ; branch if dot pattern fits ..XX....
          LD   C,&02           ; else Willy's frame is 2 (pattern XX......)
L92FC:    LD   (#85D2),BC      ; Willy's anim frame (and col)
          LD   A,IYL           ; row taken from lookup index low-byte
          SUB  &10             ; up 8 pixels
          LD   (#85CF),A       ; update Willy's y * 2
          PUSH HL
          CALL L8E9C           ; update Willy's attribute buffer position
          POP  HL
          JR   L930E
                               ; [draw the current dot/create the next dot]
L930E:    LD   A,(IX+&05)      ; bitmap pattern for drawing the dot
          OR   (HL)            ; combine with the existing screen pixels
          LD   (HL),A          ; draw it back to screen
          LD   A,(IX+&09)      ; current dot's y * 2
          ADD  A,(IX+&01)      ; add the rope swing table index offset
          LD   L,A             ; put it into low-byte of lookup address
          SET  7,L             ; using y offsets in rope table
          LD   H,&83           ; as in 0x8300, the rope table
          LD   E,(HL)          ; y offset
          LD   D,&00
          ADD  IY,DE           ; move down the screen by the offset rows
          RES  7,L             ; using x offsets in rope table
          LD   A,(HL)          ; x offset
          OR   A
          JR   Z,L9350         ; branch if 0 (bit pattern remains the same)
                               ; [create the dot pattern]
          LD   B,A
          BIT  7,(IX+&01)      ; rope swing table index offset sign
          JR   Z,L9341         ; branch if a positive number
L9330:    RLC  (IX+&05)        ; else move the dot pattern to the left
          BIT  0,(IX+&05)
          JR   Z,L933D         ; branch if the dot is not in a new col
          DEC  (IX+&03)        ; else move current dot col left
L933D:    DJNZ L9330           ; looping until the dot pattern is finished
          JR   L9350           ; [skipping over code for moving right]
L9341:    RRC  (IX+&05)        ; move the dot pattern to the right
          BIT  7,(IX+&05)
          JR   Z,L934E         ; branch if the dot is not in a new col
          INC  (IX+&03)        ; else move current dot col right
L934E:    DJNZ L9341           ; looping until the dot pattern is finished
                               ; [repeat to draw the remaining rope sections]
L9350:    LD   A,(IX+&09)      ; current dot row
          CP   (IX+&04)        ; rope length
          JR   Z,L935E         ; branch if reached end of rope
          INC  (IX+&09)        ; else move down to the next dot row
          JP   L92B6           ; and repeat for the next dot
                               ; [test whether Willy is still on this rope]
L935E:    LD   A,(85D6)        ; Willy's rope dot position
          BIT  7,A
          JR   Z,L936F         ; branch if is still on the rope (bit-7 = 0)
          INC  A               ; else he's off but there's a delay before...
          LD   (85D6),A        ; ...he can get back on (16 ticks)
          RES  0,(IX+&0B)      ; flag him as not being on this rope
          JR   L93B3           ; no need to perform next section so done
                               ; [allow Willy to climb up or down this rope]
L936F:    BIT  0,(IX+&0B)
          JR   Z,L93B3         ; branch if Willy is not on this rope
          LD   A,(#85D0)       ; Willy's walking/facing flags
          BIT  1,A
          JR   Z,L93B3         ; branch if Willy is standing still
          RRCA                 ; facing flag now at bit-7 (walking at bit-0)
          XOR  (IX+&00)        ; XOR with rope swing direction - left rope...
          RLCA                 ; ...is normal Willy L/R, right rope reverses
          RLCA                 ; facing result in bit-1
          AND  &02             ; mask just bit-1
          DEC  A               ; result is now 1 or -1
          LD   HL,#85D6        ; Willy's rope dot address
          ADD  A,(HL)          ; move either up one dot or down one dot
          LD   (HL),A
          LD   A,(#80EB)       ; room above this room
          LD   C,A
          LD   A,(#8420)       ; current room
          CP   C
          JR   NZ,L939B        ; branch if going up leads to a different room
          LD   A,(HL)          ; Willy's rope dot
          CP   &0C
          JR   NC,L939B        ; branch if Willy's rope dot > 12
          LD   (HL),&0C        ; else set Willy's rope dot to 12 (can't go up)
L939B:    LD   A,(HL)          ; Willy's rope dot
          CP   (IX+&04)        ; rope length
          JR   C,L93B3         ; branch if Willy's rope dot < the rope length
          JR   Z,L93B3         ; branch if Willy's rope dot = the rope length
          LD   (HL),&F0        ; else set bit-7 (not attached) and prep delay
          LD   A,(#85CF)       ; Willy's y * 2
          AND  &F8             ; align it to half a block
          LD   (#85CF),A       ; then update it
          XOR  A
          LD   (#85D1),A       ; change Willy's action to standing
          JR   L93B3
          
L93B3:    LD   DE,&0008
          ADD  IX,DE           ; move to next guardian record
          JP   L91C2

(C) 1983,1984,1999 Matthew Smith - all rights reserved


Email me