We just released a Feb. 5 '89 prototype of DuckTales for the NES!
If you'd like to support our preservation efforts (and this wasn't cheap), please consider donating or supporting us on Patreon. Thank you!

Notes:The Adventures of Captain Comic (DOS)

From The Cutting Room Floor
Revision as of 20:30, 14 March 2020 by Mouser (talk | contribs) (→‎Sound Effects: Offsets for possible unused sound.)
Jump to navigation Jump to search

This page contains notes for the game The Adventures of Captain Comic (DOS).


Revisions

The Internet Archive has all five revisions under the item TheAdventuresOfCaptainComic:

It also has three other versions:

  • swh11988 (file_id.diz labels it "Hacked/grafitti version 1"). This is identical to sw1988 except that it has 4 bytes NOPed out for an infinite-lives cheat. (The hack wouldn't have worked in later revisions because of the instant-win mode, which is perhaps why the mode exists, to prevent a trivial infinite-lives hack.)
  • swh21988 (file_id.diz labels it "Hacked/grafitti version 2")
  • R4a1sw1989 (file_id.diz labels it "Alternate version 1"). This is identical to R4sw1989 except for the startup text. It says that this version is for distribution exclusively through PUBLIC BRAND SOFTWARE.

File Formats

Malvineous has documented the file formats here.

Executable Packing

In every revision, COMIC.EXE is packed with EXEPACK. Revision 1 uses an earlier version of EXEPACK that not all unpackers can handle. Try these ones:

R1swh21988 (a hacked version) doesn't have COMIC.EXE but rather MENU.EXE, and the EXEPACK layer appears to be wrapped in another layer that adds an advertisement for "Red Point".

Reverse Engineering Tools

Various reverse engineering tools, and annotated disassemblies of every revision, are available in my Comic-related Git/git-annex repo:

git clone https://www.bamsoftware.com/git/comic.git
cd comic/
git annex get

-- Mouser (talk) 23:46, 15 December 2019 (EST)

Sound Effects

The format of a sound effect is an array of

struct {
    uint16_t freq_divider;
    uint16_t duration;
};

The 16-bit values are stored little-endian. Each freq_divider divides the Programmable Interval Timer base frequency of 1193182 Hz. For example, a freq_divider of 1c3f would result in an output frequency of 1193182 / 0x1c3f ≈ 165 Hz. The special freq_divider value 0028 indicates a rest. duration is in number of beats; each beat is about 55 ms (one tick of the 18.2 Hz IRQ 0 timer). The end of the sound is marked by a freq_divider of 0000.

Table of offsets to sound effects in the unpacked executable. Assumes that the unpacked executable has an EXE header of 512 bytes. Changed sounds are highlighted.

R1sw1988 R1swh11988 R2sw1988 R3sw1989 R4a1sw1989 R4sw1989 R5sw1991
title 2521 2521 25a2 25d2 27a2 27a2 29c4
start 2665 2665 2bc0 2bf2 2daa 2d9e
unused 26a5 26a5 2c00 2c32 2dee 2dde
door eda9 eda9 f309 f339 f4f9 f4e9 fc29
player death edd1 edd1 f331 f361 f521 f511 fc51
teleport edf9 edf9 f359 f389 f549 f539 fc79
bonus ee19 ee19 f379 f3a9 f569 f559 fc99
materialize ee25 ee25 f385 f3b5 f575 f565 fca5
game over ee85 ee85 f3e5 f415 f5d5 f5c5 fd05
screen transition eede eede f43e f46f f62f f61f fd3f
lost life eefe eefe f45e f48f f64f f63f fd5f
shoot ef4e ef4e f4ae f4de f69e f68e fdae
enemy death ef5a ef5a f4ba f4ea f6aa f69a fdba
player damaged ef71 ef71 f4d1 f501 f6c1 f6b1 fdd1
item get ef81 ef81 f4e1 f511 f6d1 f6c1 fde1
extra life ef95 ef95 f4f5 f525 f6e5 f6d5 fdf5

References for Code Changes

R2 Key Status

Revision 1 @ 3bc Revision 2 @ 419
int9_handler_continue:	; al is scancode, dl is press/release state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
    3c01	cmp al, 1	; scancode == Esc?
    7415	je int9_handler_esc
    2c38	sub al, 0x38	; scancode < LAlt?
|
    7216	jb int9_handler_done
    3c1b	cmp al, 0x1b	; scancode >= Del?
|
    7312	jae int9_handler_done
    8d1e8703	lea bx, word [keys_state]
|
    32e4	xor ah, ah
    03d8	add bx, ax
    2e8817	mov byte cs:[bx], dl	; keys_state[scancode-0x38] = dl
    eb05	jmp int9_handler_done
int9_handler_esc:
    2e88168603	mov byte cs:[key_esc_state], dl
int9_handler_done:
int9_handler_continue:	; al is scancode, dl is press/release state
    2e3a061900	cmp al, byte cs:[scancode_jump]
    742a	je int9_handler_jump
    2e3a061a00	cmp al, byte cs:[scancode_fire]
    742a	je int9_handler_fire
    2e3a061b00	cmp al, byte cs:[scancode_left]
    742a	je int9_handler_left
    2e3a061c00	cmp al, byte cs:[scancode_right]
    742a	je int9_handler_right
    2e3a061d00	cmp al, byte cs:[scancode_open]
    742a	je int9_handler_open
    2e3a061e00	cmp al, byte cs:[scancode_teleport]
    752a	jne int9_handler_special
int9_handler_teleport:
    2e88161500	mov byte cs:[key_teleport_state], dl
    eb41	jmp int9_handler_done
int9_handler_jump:
    2e88161400	mov byte cs:[key_jump_state], dl
    eb3a	jmp int9_handler_done
int9_handler_fire:
    2e88161800	mov byte cs:[key_fire_state], dl
    eb33	jmp int9_handler_done
int9_handler_left:
    2e88161600	mov byte cs:[key_left_state], dl
    eb2c	jmp int9_handler_done
int9_handler_right:
    2e88161700	mov byte cs:[key_right_state], dl
    eb25	jmp int9_handler_done
int9_handler_open:
    2e88161300	mov byte cs:[key_open_state], dl
    eb1e	jmp int9_handler_done
int9_handler_special:
    3c01	cmp al, 1	; scancode == Esc?
    7415	je int9_handler_esc
|
    2c3b	sub al, 0x3b	; scancode <= F1?
    7216	jb int9_handler_done
|
    3c04	cmp al, 4	; scancode > F4?
    7312	jae int9_handler_done
|
    8d1e0f00	lea bx, word [keys_F_state]
    32e4	xor ah, ah
    03d8	add bx, ax
    2e8817	mov byte cs:[bx], dl	; keys_F_state[scancode-0x3b] = dl
    eb05	jmp int9_handler_done
int9_handler_esc:
    2e88160e00	mov byte cs:[key_esc_state], dl
int9_handler_done:

R3 Esc

Revision 2 @ 76 Revision 3 @ 76
    cd16	int 0x16	; get keystroke; al=ASCII code
    3c6b	cmp al, 0x6b	; 'k'
    7407	je setup_keyboard
    3c4b	cmp al, 0x4b	; 'K'
    7403	je setup_keyboard
|
|
|
|
    e95001	jmp game_start
setup_keyboard:
    cd16	int 0x16	; get keystroke; al=ASCII code
    3c6b	cmp al, 0x6b	; 'k'
    740e	je setup_keyboard
    3c4b	cmp al, 0x4b	; 'K'
    740a	je setup_keyboard
    3c1b	cmp al, 0x1b	; Esc
    7503	jne any_other_key
    e95203	jmp terminate_program
any_other_key:
    e95401	jmp game_start
setup_keyboard:

R3 Keyboard Setup Hack

Revision 2 @ 83 Revision 3 @ 8a
setup_keyboard:
    1e		push ds
    b8b70f	mov ax, 0xfb7	; segment containing keyboard-related strings
    8ed8	mov ds, ax	; set ds to it
    e8f200	call input_key_mapping	; eat the 'k' already released
    b80200	mov ax, 2	; video mode 2 (80x25 text)
    cd10	int 0x10	; set video mode (clear screen)
setup_keyboard:
    1e		push ds
    b8b70f	mov ax, 0xfb7	; segment containing keyboard-related strings
    8ed8	mov ds, ax	; set ds to it
|
    b80200	mov ax, 2	; video mode 2 (80x25 text)
    cd10	int 0x10	; set video mode (clear screen)

R3 Keyboard Buffer

The keyboard setup code to wait for a keypress was moved into a subroutine (which also clears the BIOS keyboard buffer after every input):

Revision 2 @ 10a Revision 3 @ 10e
    e87100	call input_key_definition
    2ea21e00	mov byte cs:[key_teleport], al
    33c0	xor ax, ax
    8ec0	mov es, ax
    ; clear BIOS keyboard buffer
    268a0e1a04	mov cl, byte es:[0x41a] 
    26880e1c04	mov byte es:[0x41c], cl	; set end = beginning
    8d16d93f	lea dx, word [str.This_setup_OK]
    b409	mov ah, 9
    cd21	int 0x21	; print string
    e86300	call input_key_definition
    2ea21e00	mov byte cs:[key_teleport], al
|
|
|
|
|
    8d16d93f	lea dx, word [str.This_setup_OK]
    b409	mov ah, 9
    cd21	int 0x21	; print string
Revision 2 @ 17e Revision 3 @ 174
    2ec6061f0000	mov byte cs:[last_key], 0
poll_last_key:
    2e803e1f0000	cmp byte cs:[last_key], 0
    74f8		je poll_last_key
    2e8a1e1f00		mov bl, byte cs:[last_key]
|
|
    32ff		xor bh, bh
    ; bl = keypress
; ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
    e84400		call wait_for_keypress
    8ad8		mov bl, al
    32ff		xor bh, bh
    ; bl = keypress
; ...
wait_for_keypress:
    2ec6061f0000	mov byte cs:[last_key], 0
poll_last_key:
    2e803e1f0000	cmp byte cs:[last_key], 0
    74f8		je poll_last_key
    33c0		xor ax, ax
    8ec0		mov es, ax
    2ea01f00		mov al, byte cs:[last_key]
    ; clear BIOS keyboard buffer
    fa			cli			; disable interrupts
    268a0e1a04		mov cl, byte es:[0x41a]
    26880e1c04		mov byte es:[0x41c], cl	; set end = beginning
    fb			sti			; enable interrupts
    c3			ret

Interrupts were disabled while clearing the BIOS keyboard buffer in two places:

Revision 2 @ 115 Revision 3 @ 1d1
    ; 0040:1a is pointer to beginning of the buffer of pending keystrokes
    ; 0040:1c is pointer to end
|
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl ; set end = beginning
|
    ; 0040:1a is pointer to beginning of the buffer of pending keystrokes
    ; 0040:1c is pointer to end
    fa		cli			; disable interrupts
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl ; set end = beginning
    fb		sti			; enable interrupts
Revision 2 @ 2b4 Revision 3 @ 2bf
    ; 0040:1a is pointer to beginning of the buffer of pending keystrokes
    ; 0040:1c is pointer to end
|
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl ; set end = beginning
|
    ; 0040:1a is pointer to beginning of the buffer of pending keystrokes
    ; 0040:1c is pointer to end
    fa		cli			; disable interrupts
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl ; set end = beginning
    fb		sti			; enable interrupts

Interrupts were not disabled at four other instances of the same pattern:

Revision 2 @ ff5 Revision 3 @ 1004
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl
Revision 2 @ 103f Revision 3 @ 104e
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl
Revision 2 @ 110b Revision 3 @ 111a
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl
Revision 2 @ 1228 Revision 3 @ 1237
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl
    268a0e1a04	mov cl, byte es:[0x41a]
    26880e1c04	mov byte es:[0x41c], cl

R3 Key Mapping

Revision 2 @ 17e Revision 3 @ 174
input_key_mapping:
    2ec6061f0000	mov byte cs:[last_key], 0	; set in int9_handler
    2e803e1f0000	cmp byte cs:[last_key], 0
    74f8		je input_key_mapping	; loop until a key is released
    2e8a1e1f00		mov bl, byte cs:[last_key]
|
|
    32ff		xor bh, bh	; bx = scancode
|
    2e3a1e1900		cmp bl, byte cs:[scancode_jump]
    74e4		je input_key_mapping
    2e3a1e1b00		cmp bl, byte cs:[scancode_left]
    74dd		je input_key_mapping
    2e3a1e1c00		cmp bl, byte cs:[scancode_right]
    74d6		je input_key_mapping
    2e3a1e1a00		cmp bl, byte cs:[scancode_fire]
    74cf		je input_key_mapping
    2e3a1e1d00		cmp bl, byte cs:[scancode_open]
    74c8		je input_key_mapping
    4b			dec bx		; scancode == 0x1 (Esc)?
    74c5		je input_key_mapping
    83fb51		cmp bx, 0x51	; scancode > 0x52 (Ins)?
|
    7fc0		jg input_key_mapping
    d1e3		shl bx, 1
    d1e3		shl bx, 1
    d1e3		shl bx, 1
    8d164a40		lea dx, word [key_names]
    03d3		add dx, bx	; look up the key's name
    b409		mov ah, 9
    cd21		int 0x21	; print string
    2ea01f00		mov al, byte cs:[last_key]	; return scancode
|
    c3			ret
|
|
|
|
|
|
|
|
|
|
|
|
|
input_key_mapping:
|
|
|
|
    e84400		call wait_for_keypress
    8ad8		mov bl, al
    32ff		xor bh, bh	; bx = scancode
    8bf3		mov si, bx
    2e3a1e1900		cmp bl, byte cs:[scancode_jump]
    74f0		je input_key_mapping
    2e3a1e1b00		cmp bl, byte cs:[scancode_left]
    74e9		je input_key_mapping
    2e3a1e1c00		cmp bl, byte cs:[scancode_right]
    74e2		je input_key_mapping
    2e3a1e1a00		cmp bl, byte cs:[scancode_fire]
    74db		je input_key_mapping
    2e3a1e1d00		cmp bl, byte cs:[scancode_open]
    74d4		je input_key_mapping
    4b			dec bx		; scancode == 0x1 (Esc)?
    74d1		je input_key_mapping
|
    83fb52		cmp bx, 0x52	; scancode > 0x53 (Del)?
    7fcc		jg input_key_mapping
    d1e3		shl bx, 1
    d1e3		shl bx, 1
    d1e3		shl bx, 1
    8d164a40		lea dx, word [key_names]
    03d3		add dx, bx	; look up the key's name
    b409		mov ah, 9
    cd21		int 0x21	; print string
|
    8bc6		mov ax, si	; return scancode
    c3			ret
wait_for_keypress:
    2ec6061f0000	mov byte cs:[last_key], 0	; set in int9_handler
    2e803e1f0000	cmp byte cs:[last_key], 0
    74f8		je wait_for_keypress		; loop until a key is pressed
    ; clear BIOS keyboard buffer
    33c0		xor ax, ax
    8ec0		mov es, ax
    2ea01f00		mov al, byte cs:[last_key]	; return scancode
    fa			cli
    268a0e1a04		mov cl, byte es:[0x41a]
    26880e1c04		mov byte es:[0x41c], cl
    fb			sti
    c3			ret

R3 Key Press/Release

Revision 2 @ 3f8 Revision 3 @ 405
saved_int9_handler:	; pointer to the original handler for interrupt 9
    00000000	dw	0x0000, 0x0000
int9_handler:	; IRQ 1, keyboard data ready, called for every keyboard event
    50		push ax
    53		push bx
    51		push cx
    52		push dx
    e460	in al, 0x60		; al = scancode
    50		push ax			; save al
    9c		pushf
    2eff1ef803	lcall cs:[saved_int9_handler]	; call original interrupt handler
    58		pop ax			; restore al = scancode
    ba0100	mov dx, 1		; initially set dl = 1 to indicate key press
    a880	test al, 0x80		; check the high bit of the scancode
    7408	jz int9_handler_continue	; if 0, it was indeed a key press
|
    247f	and al, 0x7f		; otherwise, it was a key release
    2ea21f00	mov byte cs:[last_key], al	; clear high bit and store scancode
    33d2	xor dx, dx		; set dl = 0 to indicate key release
|
|
|
int9_handler_continue:
saved_int9_handler:	; pointer to the original handler for interrupt 9
    00000000	dw	0x0000, 0x0000
int9_handler:	; IRQ 1, keyboard data ready, called for every keyboard event
    50		push ax
    53		push bx
    51		push cx
    52		push dx
    e460	in al, 0x60		; al = scancode
    50		push ax			; save al
    9c		pushf
    2eff1e0504	lcall cs:[saved_int9_handler]	; call original interrupt handler
    58		pop ax			; restore al = scancode
    ba0100	mov dx, 1		; initially set dl = 1 to indicate key press
    a880	test al, 0x80		; check the high bit of the scancode
|
    7406	jz key_was_pressed	; if 0, it was indeed a key press
    247f	and al, 0x7f		; otherwise, it was a key release
|
    33d2	xor dx, dx		; set dl = 0 to indicate key release
    eb04	jmp int9_handler_continue	; do not store scancode
key_was_pressed:
    2ea21f00	mov byte cs:[last_key], al	; clear high bit and store scancode
int9_handler_continue:

R3 Unpause

Revision 2 @ 1374 Revision 3 @ 1383
    2e803e0e0001	cmp byte cs:[key_esc_state], 1	; Esc is pressed?
    7503		jne .L1
    e851fc		call pause_game			; returns after any key is pressed
|
|
|
|
.L1:
    2e803e0e0001	cmp byte cs:[key_esc_state], 1	; Esc is pressed?
    750f		jne .L1
    e851fc		call pause_game			; returns after any key is pressed
wait_for_esc_release:
    2e803e0e0001	cmp byte cs:[key_esc_state], 1
    74f8		je wait_for_esc_release		; wait until Esc is not pressed
    8b364000		mov si, word [comic_pos]
.L1:

R3 Initialize Jump Counter

Revision 2 @ 147c Revision 3 @ 1497
    c60616cf00	mov byte [comic_y_vel], 0
    c60617cf00	mov byte [jump_counter], 0
|
    c6061acf00	mov byte [comic_graphic], 0	; standing graphic
    c60616cf00	mov byte [comic_y_vel], 0
|
    c60618cf04	mov byte [jump_counter], 4
    c6061bcf00	mov byte [comic_graphic], 0	; standing graphic

R3 Jump Into Ceiling

Revision 2 @ 1498 Revision 3 @ 14b3
handle_falling_and_jumping:
    fe0e17cf	dec byte [jump_counter]
    740f	je .L1
    2e803e140001	cmp byte cs:[key_jump_state], 1
    750b	jne .L3
    802e16cf07	sub byte [comic_y_vel], 7	; still jumping upward
    eb04	jmp .L3
.L1:
    fe0617cf	inc byte [jump_counter]	; jump_counter bottoms out at 1
|
|
.L3:
    a016cf	mov al, byte [comic_y_vel]
    8ac8	mov cl, al
    d0f8	sar al, 1
    d0f8	sar al, 1
    d0f8	sar al, 1
    8bde	mov bx, si		; bl = comic_y_pos, bh = comic_x_pos
    02d8	add bl, al		; bl = comic_y_pos + comic_y_vel/8
    7d02	jge .L4			; hit the top of the screen?
    b300	mov bl, 0
.L4:
|
|
    8bf3	mov si, bx		; comic_y_pos = max(0, comic_y_pos + comic_y_vel/8)
    80fb11	cmp bl, 0x11		; comic_y_pos >= 8.5 tiles?
    7203	jb .L5
    e946ff	jmp comic_dies		; die from falling in a pit
.L5:
    803e3e0002	cmp byte [current_level_number], 2	; is this the space level?
    7503	jne .L6
    80e902	sub cl, 2		; if so, comic_y_vel += 3
.L6:
    80c105	add cl, 5		; otherwise, comic_y_vel += 5
    80f918	cmp cl, 0x18		; comic_y_vel > 23?
    7c02	jl .L7
    b117	mov cl, 0x17		; maximum falling velocity is 23
.L7:
    880e16cf	mov byte [comic_y_vel], cl
    8a0e15cf	mov cl, byte [comic_x_momentum]
check_key_left:
    2e803e160001	cmp byte cs:[key_left_state], 1
    750e	jne check_key_right
    c60619cf05	mov byte [comic_facing], 5	; face left
    ; comic_x_momentum = max(comic_x_momentum - 1, -5)
    fec9	dec cl
    80f9fb	cmp cl, -5
    7d02	jge check_key_right
    b1fb	mov cl, -5
check_key_right:
    2e803e170001	cmp byte cs:[key_right_state], 1
    750e	jne .L8
    c60619cf00	mov byte [comic_facing], 0	; face right
    ; comic_x_momentum = min(comic_x_momentum + 1, +5)
    fec1	inc cl
    80f905	cmp cl, +5
    7e02	jle .L8
    b105	mov cl, +5
.L8:
    880e15cf	mov byte [comic_x_momentum], cl
check_move_left:
    0ac9	or cl, cl		; comic_x_momentum < 0?
    7d07	jge check_move_right
    fe0615cf	inc byte [comic_x_momentum]
    e87d00	call comic_moves_left
    a015cf	mov al, byte [comic_x_momentum]
check_move_right:
    0ac0	or al, al		; comic_x_momentum > 0?
    7e07	jle check_for_head_bonk
    fe0e15cf	dec byte [comic_x_momentum]
    e8cb00	call comic_moves_right
check_for_head_bonk:
    8bc6	mov ax, si		; al = comic_y_pos, ah = comic_x_pos
    e890f4	call lookup_tile_at_coordinates	; tile at Comic's head
    8a164007	mov dl, byte [last_solid_tile]
    3817	cmp byte [bx], dl	; is it solid?
    7f0a	jg in_ceiling
    f6c401	test ah, 1		; is Comic halfway between tiles?
    740a	je check_for_solid_ground
    385701	cmp byte [bx + 1], dl	; if so, also check the next tile to the right
    7e05	jle check_for_solid_ground
in_ceiling:
    c60616cf08	mov byte [comic_y_vel], 8	; bounce downward off the ceiling
|
|
|
|
|
check_for_solid_ground:
    803e16cf00	cmp byte [comic_y_vel], 0	; is Comic moving downward?
    7e1b	jle not_on_solid_ground
    8bce	mov cx, si		; cl = comic_y_pos, ch = comic_x_pos
    8bc1	mov ax, cx		; al = comic_y_pos, ah = comic_x_pos
    0405	add al, 5		; comic_y_pos + 5
    e869f4	call lookup_tile_at_coordinates	; tile at Comic's feet (Comic is 4 units tall)
    8a164007	mov dl, byte [last_solid_tile]
    3817	cmp byte [bx], dl	; is it solid?
    7f12	jg check_if_too_low
    f6c401	test ah, 1		; is Comic halfway between tiles?
    7405	je not_on_solid_ground
    385701	cmp byte [bx + 1], dl	; if so, also check the next tile to the right
    7f08	jg check_if_too_low
not_on_solid_ground:
    c6061acf04	mov byte [comic_graphic], 4	; jumping graphic
    e9f7fd	jmp return_to_game_loop
check_if_too_low:
    80f90f	cmp cl, 0xf		; comic_y_pos < 7.5 tiles?
    7202	jb on_solid_ground	; if so, tile check was meaningful
    ebf1	jmp not_on_solid_ground	; if not, Comic is too low for there to be ground
on_solid_ground:
    fec1	inc cl
    80e1fe	and cl, 0xfe		; clamp Comic's feet to even tile offset
    8bf1	mov si, cx		; comic_y_pos = (comic_y_pos + 1) & 0xfe
    c60613cf00	mov byte [comic_is_airborne], 0
    c60616cf00	mov byte [comic_y_vel], 0
    e9dcfd	jmp return_to_game_loop
handle_falling_and_jumping:
    fe0e18cf	dec byte [jump_counter]
    740f	je .L1
    2e803e140001	cmp byte cs:[key_jump_state], 1
    750b	jne .L2
    802e16cf07	sub byte [comic_y_vel], 7	; still jumping upward
    eb09	jmp .L3
.L1:
    fe0618cf	inc byte [jump_counter]	; jump_counter bottoms out at 1
.L2:
    c60617cf00	mov byte [in_ceiling_flag], 0	; no longer in ceiling if jump is finished
.L3:
    a016cf	mov al, byte [comic_y_vel]
    8ac8	mov cl, al
    d0f8	sar al, 1
    d0f8	sar al, 1
    d0f8	sar al, 1
    8bde	mov bx, si		; bl = comic_y_pos, bh = comic_x_pos
    02d8	add bl, al		; bl = comic_y_pos + comic_y_vel/8
    7d02	jge .L4			; hit the top of the screen?
    b300	mov bl, 0
.L4:
    021e17cf	add bl, byte [in_ceiling_flag]
    c60617cf00	mov byte [in_ceiling_flag], 0
    8bf3	mov si, bx		; comic_y_pos = max(0, comic_y_pos + comic_y_vel/8 + in_ceiling_flag)
    80fb11	cmp bl, 0x11		; comic_y_pos >= 8.5 tiles?
    7203	jb .L5
    e938ff	jmp comic_dies		; die from falling in a pit
.L5:
    803e3e0002	cmp byte [current_level_number], 2	; is this the space level?
    7503	jne .L6
    80e902	sub cl, 2		; if so, comic_y_vel += 3
.L6:
    80c105	add cl, 5		; otherwise, comic_y_vel += 5
    80f918	cmp cl, 0x18		; comic_y_vel > 23?
    7c02	jl .L7
    b117	mov cl, 0x17		; maximum falling velocity is 23
.L7:
    880e16cf	mov byte [comic_y_vel], cl
    8a0e15cf	mov cl, byte [comic_x_momentum]
check_key_left:
    2e803e160001	cmp byte cs:[key_left_state], 1
    750e	jne check_key_right
    c6061acf05	mov byte [comic_facing], 5	; face left
    ; comic_x_momentum = max(comic_x_momentum - 1, -5)
    fec9	dec cl
    80f9fb	cmp cl, -5
    7d02	jge check_key_right
    b1fb	mov cl, -5
check_key_right:
    2e803e170001	cmp byte cs:[key_right_state], 1
    750e	jne .L8
    c6061acf00	mov byte [comic_facing], 0	; face right
    ; comic_x_momentum = min(comic_x_momentum + 1, +5)
    fec1	inc cl
    80f905	cmp cl, +5
    7e02	jle .L8
    b105	mov cl, +5
.L8:
    880e15cf	mov byte [comic_x_momentum], cl
check_move_left:
    0ac9	or cl, cl		; comic_x_momentum < 0?
    7d07	jge check_move_right
    fe0615cf	inc byte [comic_x_momentum]
    e88b00	call comic_moves_left
    a015cf	mov al, byte [comic_x_momentum]
check_move_right:
    0ac0	or al, al		; comic_x_momentum > 0?
    7e07	jle check_for_head_bonk
    fe0e15cf	dec byte [comic_x_momentum]
    e8d900	call comic_moves_right
check_for_head_bonk:
    8bc6	mov ax, si		; al = comic_y_pos, ah = comic_x_pos
    e876f4	call lookup_tile_at_coordinates	; tile at Comic's head
    8a164007	mov dl, byte [last_solid_tile]
    3817	cmp byte [bx], dl	; is it solid?
    7f0a	jg in_ceiling
    f6c401	test ah, 1		; is Comic halfway between tiles?
    7418	je check_for_solid_ground
    385701	cmp byte [bx + 1], dl	; if so, also check the next tile to the right
    7e13	jle check_for_solid_ground
in_ceiling:
|
    803e16cf00	cmp byte [comic_y_vel], 0	; is Comic moving downward?
    7f0c	jg check_for_solid_ground
    c60617cf01	mov byte [in_ceiling_flag], 1	; stick to the ceiling
    c60616cf00	mov byte [comic_y_vel], 0	; but stop moving upward
    eb22	jmp not_on_solid_ground
check_for_solid_ground:
    803e16cf00	cmp byte [comic_y_vel], 0	; is Comic moving downward?
    7e1b	jle not_on_solid_ground
    8bce	mov cx, si		; cl = comic_y_pos, ch = comic_x_pos
    8bc1	mov ax, cx		; al = comic_y_pos, ah = comic_x_pos
    0405	add al, 5		; comic_y_pos + 5
    e841f4	call lookup_tile_at_coordinates	; tile at Comic's feet (Comic is 4 units tall)
    8a164007	mov dl, byte [last_solid_tile]
    3817	cmp byte [bx], dl	; is it solid?
    7f12	jg check_if_too_low
    f6c401	test ah, 1		; is Comic halfway between tiles?
    7405	je not_on_solid_ground
    385701	cmp byte [bx + 1], dl	; if so, also check the next tile to the right
    7f08	jg check_if_too_low
not_on_solid_ground:
    c6061bcf04	mov byte [comic_graphic], 4	; jumping graphic
    e9cffd	jmp return_to_game_loop
check_if_too_low:
    80f90f	cmp cl, 0xf		; comic_y_pos < 7.5 tiles?
    7202	jb on_solid_ground	; if so, tile check was meaningful
    ebf1	jmp not_on_solid_ground	; if not, Comic is too low for there to be ground
on_solid_ground:
    fec1	inc cl
    80e1fe	and cl, 0xfe		; clamp Comic's feet to even tile offset
    8bf1	mov si, cx		; comic_y_pos = (comic_y_pos + 1) & 0xfe
    c60613cf00	mov byte [comic_is_airborne], 0
    c60616cf00	mov byte [comic_y_vel], 0
    e9b4fd	jmp return_to_game_loop