Jumpman Reverse Engineering Notes

 (for the Atari 8-bit version)

Our descriptions below are: Copyright © 2016-2020, Rob McMullen & Kay Savetz. Licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license. See https://creativecommons.org/licenses/by-nc-sa/4.0/

The game Jumpman is: Copyright © 1983 Randy Glover

Sector Map

Jumpman Memory Map

0000-0600: Low Memory

0a00-0fff: Code

1000-2000: Scratch area for screen construction

2000-2800: Code

2800-3000: Level Storage

3000-3100: Working Data

3100-6fff: Code

7000 - 7e64: gameplay display memory

Boot Process

Memory Map Refresher

Level Definition Table Examples

Easy Does It

List of Objects

Obscure Objects

Drawing an Object Code

Harvest Table & Painting Table

Easy Does It

Robots I

Jumpman Coordinates

Harvest Table Encoding

Designing a Level

Notes on The Original 30 Levels

Hand coding

The Peanut Harvest Error Screen

Music

Music List

Custom Code

Main game loop code

Code to run when a particular peanut is taken

Code to run with any/every peanut is taken

Code to run constantly during the level

Useful Code

Appendix 1: Notes On Our Reference Disk Image

Introduction

A Note on Terminology

In the documentation for the game, you are described as running around defusing bombs by touching them. We started calling them peanuts in our documentation because Kay called them that during his original gameplay experience in the 1980s, and so we wouldn't get flagged and placed on watchlists because our email traffic contained hundreds of references to bombs.

peanut: The bombs from the original manual; what you're collecting to advance to the next level

harvest: The act of colliding with a peanut and removing it from the level

trigger function: A pointer to a routine that will run when that particular peanut is harvested. This is not the same as trigger painting described below.

trigger painting: A pointer to an entry in the trigger painting table that draws or erases level elements when the peanut is harvested. Note that this is entirely separate from the trigger function above. A peanut may have one, both, or neither defined.

Sector Map

Start        layout        name

17        25        easy does it

33        41        robots 1

49        57        bombs away

65        73        jumping blocks

81        89        vampire

97        105        invasion

113        121        GP I

129        137        builder

145        153        look out below

161        169        hotfoot

177        185        runaway

193        201        robots I  

209        217        hailstones

225        233        dragonslayer

241        249        GP II

257        265        ride around

273        281        roost

289        297        roll me over

395        313        ladder challenge

321        329        figureit

337        345        jump n run

353        361        freeze

369        377        follow the leader

385        393        the jungle

401        409        mystery maze

417        425        mystery maze

433        441        mystery maze

449        457        gunfighter

465        473        robots III

481        489        now you see it

497        505        going down

513        521        GP III

        677        demo (search for fc0040fd)

Our reference disk image is available on AtariMania; see the Appendix for additional notes on the format and contents of that particular disk image.

NOTE: all values in hex unless either super-obvious or otherwise noted.

Jumpman Memory Map

0000-0600: Low Memory

034f-035e: working data for level construction?

0a00-0fff: Code

0a00: jump target: display number of players screen and wait for keypress in location 30ff

0bb8: jump target: from 69cc, after high score

0bfc-?: local storage

    0bfc: important var; used by 0f08 to see if 7de8 array needs to be cleared?

0c00: subroutine: called from 5620

0ca0: entry point: called after girders crumbled? jumps to 5300 after a few instructions

0ed0-?: local storage

    0ed0: number of players pressed

    0ed2: number of players copy? from 2632

0f08: after level completed; start of new level init?

1000-2000: Scratch area for screen construction

1000: temporary ram for construction of screen

1800: working copy of level storage data?

    1831: ? init from 5590, copied value from $0700

2000-2800: Code

23eb: jump target: from 5687; game options entry point?

2476: jump target for OPTION pressed during game options list

2481: CMP #06: number of items in the game options list

248a: STA $32fe when using "ba r 32fe", so this is where the game option value is stored

24ec: called after START pressed, in turn calls 0a00

2507,x: table starting sector, low byte; index based on 2603 (starts at 1)

250d,x: table starting sector, high byte

2508: 11, 91, 31, 11, c1

250e: 00, 00, 01, 00, 00

2513: subroutine: set menu colors to default values

2603: index value (01 - 05) of level selected in GAME OPTIONS screen (01 = beginner, 05 = randomizer). Used in an a DLI interrept?

2680: subroutine:

2700: subroutine: draw an object. The object description appears to be in triples, a color and x and y offsets. As input to this function, the color is stored in $0358, x coord in $55 and y coord in $54.

27e0: subroutine:

2800-3000: Level Storage

(Labels that the Omnivore assembler recognizes are in bold. If the assembler finds these labels in the assembly code, the address of the label will be copied into the proper location in the level storage area.)

2800-2801: level number in ATASCII, copied to 4636-4637 and screen RAM

2802-2809: These 8 bytes comprise a jump table to 4 vertical blank routines that each level can use for custom game logic. In typical levels, they are used for bullet hits and levels with custom code can use them to perform special tasks like moving robots or update falling objects.

    2802-2803: vbi1
   2804-2805:
vbi2

    2806-2807: vbi3
   2808-2809:
vbi4 if you override vbi4, you will need to either end it with a JMP $49a0 or handle player/missile collisions yourself to test for Jumpman death.

In these routines, the address 311b is used to do nothing special. If all 4 are 311b, there will be no bullet hit recognition, no robots, etc.

If you write custom code for vbi1 through 3, end it with JMP $311b — not RTS. See the note above for vbi4, and for an example, check out Kay’s video: https://www.youtube.com/watch?v=_RRrynvRbZs 

(Note that the 6502 is little-endian, so when you look at the hex values in Omnivore or in a disassembly, the address 311b will appear as 1b31 in consecutive bytes).

2802-2803 EDL superjumpman turned 1b31 to 0029

               lookout: changed to 1b31, no falling stuff

2804-2805 ladder b829 - change 0029 to not die when shot

                   edl change 0029 everything s l o w

2806-2807  more of the same

2808-2809 edl change to 1b31 no die when shot

280a-f is always c694aa780f00

280a color of score/bonus area border

280b probably unused color register

280c color of "L=" in the score area

280d color of words SCORE and BONUS

280e color of remaining lives jumpmen

280f color of score/bonus area background

2810-2817 is always the same

    2810,2811: dead_begin: addr: vector to call when start to fall to death, copied to 30b7. If you create a custom routine, end it with JMP $4200

    2812,2813: dead_at_bottom: addr: vector called when dying and bounced down to the bottom, before music. copied to 30b9. If you create a custom routine, end it with JMP $4580

    2814,2815: dead_falling: addr: vector called while falling to death, after the 2810 routine but before 2812. Usually points to 311b. If you create a custom routine, end it with JMP $311b

    2816-2817: ???????: addr: always vector to 30e0, which does CLC, RTS.

The next 18 bytes clarify the position of ladders and down ropes. The game uses PM-playfield collision detection to know what Jumpman is touching. Girders and up-ropes are the same color (defined at 282e): if Jumpman touches that color, he will always climb. Ladders and down-ropes are also the same color (defined at 282f), but Jumpman must behave differently with each (ladders are self-selected up and down; down-ropes force downward movement.) So these 18 bytes tell the game: when Jumpman is touching that color, how to tell a ladder from a down-rope. This would be unnecessary if there was one more color to work with.

 

2818-2823 12 bytes that describe which vertical columns contain ladders

        So ladders can appear in 12 columns.

        This number is the x position of the ladder from level description plus 30.

        For instance if the x position is 0c (fc 2c 40 fd 0c 05 0b fd)

                this number should be 3c

        Don't have to define every ladder; just every column with 1+ ladders

        So multiple ladders in the same column only get one entry here.

2824-2829 6 bytes that describe which vertical columns contain down ropes.

        So ropes can appear in 6 columns.

        This number is the x position of the rope from level description plus 2e.

        For instance if the x position is 04 (fc c0 40 fd 04 07 04 fd)

                this number should be 32

                

282a dunno. always 0f

282b Jumpman shadow (player1(?)) color        

282c player2(?) color

282d player3(?) color

282e girder and up ropes color

282f ladder and down ropes color

2830 peanut color

2831 bullet color; missiles combined to player4 color

2832 background color

2833-4 How many points is a peanut worth, low and hi bytes

2835,2836: bonus value for level (e.g. dc,05 = 1500 decimal

2837,2838: addr: start of level definition table (usually 2c00), copied to $3042

2839 Jumpman starting position X. Left is 34. Middle is 7c. Right is c6.

283a Jumpman starting position Y. Top is 20. Bottom is c0

283b,283c: gameloop: addr: vector to the level’s main game loop code. (this is usually set to 2880 or 2860), called from level setup routine at $4460. More on this in the custom code notes, below.

283d Max number of bullets. Glover’s levels use 0 (no shooting), 1, 2, and 4. 3 works. Anything else breaks things, sometimes to hilarious effect.

283e # of peanuts you need to take to complete the level. This location is decremented as you take peanuts as the level progresses, which is super useful for custom level code.

283f is always 4c (JMP) because it and the next two bytes are a jump target from the game loop

2840-2841: out_of_lives: addr: vector when lost last life. Always points to $4ffd except Grand Puzzle II. If you create a custom routine, end it with JMP $4ffd

2842 -> 30ee # sector number of next level

2843 -> 30ef

2844-2845: level_complete: addr: successful completion of level. Always points to $4c00 except on Grand Puzzle II & The Jungle. If you create a custom routine, end it with JMP $4c00

        The Jungle: The next level is Mystery Maze. It's setting which maze to go to depending on the number of lives you have left. If you have less than 3, it goes to maze #1. Less than 5, goes to maze #3. 5 or more goes to maze #2.

        GP II: ???

2846: X harvest offset

2847: Y harvest offset

2848: always 20 (i.e. JSR)

2849-284a: collect_callback: subroutine called every time a peanut is taken. If that functionality isn’t needed, the default is 284b (e.g. the next byte because that’s always RTS). Vampire uses a custom routine at $2b00, Look Out Below has one at $2980, etc. If you create a custom routine, end it with RTS.

284b: always 60 (i.e. RTS)

284c: always FF (target for harvest table if no action to be taken)

284e,284f: addr: start of the harvest table

2850-2851: sometimes used as data storage (flags, timers) in levels

2860 (sometimes): start of gameplay loop or setup code (pointed to by $283b)

2880 (usually): start of gameplay loop code (pointed to by $283b)

This routine is customizable per level, but tends to be the same most of the time. Makes jumpman fade in and play the appearance sound, checks to see if all peanuts are taken (if so, level complete), checks to see if you’re out of reserve jumpmen (if so, game over).

All the game mechanics (collision detection, jumpman movement, enemy logic, etc.) seem runs in the vertical blank. This routine is called in a continuous loop in the normal processing time until the end of level condition is reached, whereupon it jumps to the vector at 2844. If the player runs out of jumpmen before completing the level, it jumps to 283f which is another jump (usually to $4ffc).

2900: VBI code

2a80-4 something to do with PM graphics Missiles through PM3

2b00: VBI code

   

2bec-2bff: title

   

2c00 (usually): level definition table (see Level Definition Table Examples)

           

2cxx: harvest table (pointed to by 284e), usally immediately after level def table

        tuples of 7 bytes: (See the Harvest Table Encoding section for more details)

       

        0: index/identifier (x/y encoding), FF ends the table

        1: x position

        2: y position

        3,4: addr: trigger function, called when harvested

        5,6: addr: what painting action to take, points to $284c if no painting action

3000-3100: Working Data

3000-3017: vbi_vector_table vertical blank vector table

3018: vbi_next_index next vertical blank vector number (incremented by 2)

3019: vbi_counter1 These 6 counters are incremented every VBI. Counters that levels can use for whatever? e.g. in demo level, the 301e counter is used to change colors to make "SELECT" flash

301a: vbi_counter2

301b: vbi_counter3

301c: vbi_counter4

301d: vbi_counter5

301e: vbi_counter6

301f: always set to 1 during 311b VBI

3020: joystick 0 horizontal result: 0 = centered, 1 = right, ff = left

3021: joystick 1 value

3024: joystick 0 vertical result: 0 = centered, 1 = down, ff = up

3028: joystick direction. Normally read from, but writing to this is used by the demo level to automate jumpman, and by Jumping Blocks to force jumps. See “5cb0: demo level autoplayer data” below.

302c: trig0 result: 1 = not pressed, 0 = pressed. Again, this is written to by demo level and Jumping Blocks.

3030: music_flag0 music flag for voice #0

3032: music_flag1 music flag for voice #1

3034: music_flag2 music flag for voice #2

3036: music_flag3 music flag for voice #3

3038,3039: music_list0 music list for voice #0

303a,303b: music_list1 music list for voice #1

303c,303d: music_list2 music list for voice #2

303e,303f: music_list3 music list for voice #3

3040,3041: addr: music_start_parameter for 32b0 subroutine: music list to load into VBI player

3042,3043: addr: level definition table, copied from $2837

3044: fc param #0: object code

3045: fc param #1: always $40?

3046: fd param #0: x coord

3047: fd param #1: y coord

3048: fe param #0: x step value

3049: fe param #1: y step value

304a: fd param #2: repeat count

304f: PMBASE page number for player/missile graphics page ($60?)

3050: SIZEP? value for gameplay (two bits per player, lsb = player 0)

3051: SIZEM value for gameplay

3052: SDMCTL value for gameplay

3053: GRACTL value for gameplay

3054: GPRIOR value for gameplay

3055 Combined missile-player active  — 0 is off, 1 is on

3056 P0 active (always, silly, it’s jumpman)

3057 P1 active (always, it’s the shadow)

3058 P2 active — 0 is off, 1 is on

3059 P3 active — 0 is off, 1 is on

305a-e where the player image data begins for each player, low byte. See 3073-7 for details.

305a image data LB for the Combined Missile Player

305b image data LB for Player 0, Jumpman. Leave this alone.

305c image data LB for Player 1, shadow. Leave this alone.

305d image data LB for Player 2

305e image data LB for Player 3

305f-3063 where the player image data begins for each player, high byte. See 3073-7 for details.

305f image data HB for the Combined Missile Player

3060 image data HB for Player 0. Leave this alone.

3061 image data HB for Player 1. Leave this alone.

3062 image data HB for Player 2

3063 image data HB for Player 3

3064-3068 image data bytes per image. E.g. a typical PM graphic might be 8 bytes tall. See 3073-7 for details.

3064 image data bytes per image for the Combined Missile Player

3065 image data bytes per image for Player 0. Leave this alone.

3066 image data bytes per image for Player 1. Leave this alone.

3067 image data bytes per image for Player 2

3068 image data bytes per image for Player 3

3069 Horizontal position of combined-Missile Player

306a Horizontal position of player 0 (always Jumpman) hosp0 -> D000

        (X location of Jumpman; p/m coords)

306b Horizontal position of player 1 hosp1 -> D001

306c Horizontal position of player 2 hosp2 -> D002

306d Horizontal position of player 3 hosp3 -> D003

306e Y position of the combined-Missile Player

306f: Y position of Jumpman; p/m coords. C6 = dead, c0 is value on lowest possible girder,

3070: Y position of the shadow, you don’t need to mess with this

3071: Y position of Player 2

3072: Y position of Player 3

3073-7 Set Player to a particular graphic from image data.

3073 Set image data for the Combined-Missile Player

3074 Set image data for Player 0 (Jumpman) — don’t do this!

3075 Set image data for Player 1 (Shadow) — don’t do this!

3076 Set image data for Player 2

3077 Set image data for Player 3

when you STA a number there, it changes the corresponding player to that graphic from the level’s list of graphics. If the image data is rooster going up, rooster going right, rooster going right with wings out, rooster going left, etc., you can STA a 4 into 3077 to turn Player 3 to rooster going right. Note that the numbering starts at 1, not 0. Before using this, initialize 305a-e (player image data, low byte), 305f-3063 (player image data, high byte) and 3064-3068 (image data bytes per image). Glover wasn’t shy about setting graphic data to the same location for all enemies.

Screenshot 2016-08-16 17.59.37.png

3078 through 3086 is where the game stores the previous versions of Player location and which graphic is being used. It compares the current information to this old information, to decide which Players need to be moved around the screen. This is all done in the code around 12caf. As a custom level builder, you don’t need to mess with anything in this span except during PM graphics initialization: Glover liked to set 3080, 3081, 3085, and 3086 to 0 or 1 during PM initialization: I think this is simply to force the first frame to be drawn correctly because the position and/or image choice will have changed. I’m not sure why he didn’t also initialize 307d and 3082, for the combined missile player is also often used for enemies. I guess that is automatically handled a special case. Also: It’s probably a bad idea to mess with the locations that contain copies of the Jumpman and Shadow info.

3078 copy of 3069 (X position of Combined Missile Player)

3079 copy of 306a (X position of Player 0 (Jumpman))

307a copy of 306b (X position of Player 1 (Shadow))

307b copy of 306c (X position of Player 2)

307c copy of 306d (X position of Player 3)

307d copy of 306e (Y position of the combined-Missile Player)

307e copy of 306f (Y position of Player 0 (Jumpman))

307f  copy of 3070 (Y position of Player 1 (Shadow))

3080 copy of 3071 (Y position of Player 2)

3081 copy of 3072 (Y position of Player 3)

3082 copy of 3073 (image data for Combined Missile Player)

3083 copy of 3074 (image data for Player 0 (Jumpman))

3084 copy of 3075 (image data for Player 1 (Shadow))

3085 copy of 3076 (image data for Player 2)

3086 copy of 3077 (image data for Player 3)

3090-309f: copies of $d000-d00f collision registers during vblank. See 12d92.

3090 copy of $d000

3091 copy of $d001

3092 copy of $d002

3093 copy of $d003

3094 copy of $d004

3095 copy of $d005 (P1PF)

3096 copy of $d006

3097 copy of $d007

3098 copy of $d008

3099 copy of $d009

309a copy of $d00a

309b copy of $d00b

309c copy of $d00c

309d copy of $d00d

309e copy of $d00e

309f copy of $d00f

30a0: Combined Missile Player to Playfield collision byte. E.g. > 0 means the Combined Missile Player hit something. This is an AND of D000-D003. See 12da5-12dab.

30a1: Combined Missile Player to Player collision byte.  This is an AND of D008-D00B. See 12dae-12db4.

30a2: Missile 0 activated 1=enabled; 0=disabled

30a3: Missile 1 activated

30a4: Missile 2 activated

30a5: Missile 3 activated

30a6: Missile 0 X

30a7: Missile 1 X

30a8: Missile 2 X

30a9: Missile 3 X

30aa: Missile 0 Y

30ab: Missile 1 Y

30ac: Missile 2 Y

30ad: Missile 3 Y

30ae: previous Missile 0 X, used internally to detect change for redraw. You probably never need to touch this.

30af: previous Missile 1 X

30b0: previous Missile 2 X

30b1: previous Missile 3 X

30b2: previous Missile 0 Y, used internally to detect change for redraw. You probably never need to touch this.

30b3: previous Missile 1 Y

30b4: previous Missile 2 Y

30b5: previous Missile 3 Y

30b6: Missile (bullet) height, in pixels. Standard bullets are 02. Jumping Blocks is 04 (twice as tall as normal.) You can use larger numbers for very tall Missiles. Very big numbers (eg FF) tends to crash eventually.

30b7: color of divider background, used in DLI between game playfield and score area

30b8: color 0 in score area. LDA $30b8 is a good source of constantly changing colors.

30b9: color 1 in score area

30ba: color 2 in score area

30bb: color 3 in score area

30bc: background color of score area, this and previous 4 set in DLI 3c7b. 0 or 1?

30bd: Jumpman alive flag: 0 = alive, 1 = falling to his death, bump bump bump.. 2 = dead at bottom of screen with birdies floating over his head, or Super Dead (not even on screen/fading back in)

30be: Jumpman play speed/status.

0 = playing at speed 1 (fast)

1 = speed 2

        Also set to 1 when he is falling to his death or the death music is playing. I think

        you could change JM’s fall speed.

2 = speed 3

3 = speed 4 (default)

4 = speed 5

5 = speed 6

6 = speed 7

7 = speed 8 (slowest)

8 = when he’s not on the screen: that short period where he disappears from the floor and fades back in in his new life starting place (or end the game if out of lives.). (I call this “super dead” as a shorthand in source code comments).

This location is checked by the main loop (typically at $2880 or $2860) to see if Jumpman is dead yet. You don’t write to this location, the system takes care of it. (Caveat: Grand Puzzle III does write to this location: it stores an 8 there, apparently to stave off Jumpman’s appearance while the hidden second screen is drawn.)

30bf: somehow related to 30be

30c0: This counts how many cycles Jumpman has been jumping? If it counts up to 17 (per Game code part 2:3b3c) Jumpman falls to his death.

30c1: Something to do with the shadow and falling. Force to 0 and Jumpman will float gently down rather than fall to his death.

30c2-30ca: copies of 2810-2817

30cb: ??

30cc: 0-4 counter for animating Jumpman

30cd: ??

30ce-30d9: copies of 2818-2823 (ladder positions)

30da-30df: copies of 2824-2829 (downrope positions)

30e0-30e1: CLC, RTS used as target for the vector at 2816. If the CLC is changed to an SEC, Jumpman is killed upon returning from this subroutine. We think that this was intended to be a method to kill Jumpman during the level, but Glover ended up abandoning it despite leaving the code around. This subroutine is still called, but the SEC method of killing Jumpman is not used in any of Glover’s levels.

30e2,30e3: addr: display list interrupt. Will be stuffed into DLI pointer by VBI Vector #0 at 4150

30e5-30ed: copies of 282a-2832 (color registers)

30ee: low byte of sector

30ef: high byte of sector

30f0: number of lives in reserve. 0 means you are on the last life. FF means you are out of lives, game over.

30f1-30f3: working space to convert hex to decimal

30f4: ?

30f5-30f7: score

30ff: number in response to keypress

   

3100-6fff: Code

3100: vbi_init subroutine: initialize vertical blank

3180: vbi_joystick joystick processing

311b: vbi_driver vertical blank interrupt driver, and VBI vectors #2, 3 & 4. Replaces call to XITVBV with call to the current VBI vector stored in 3018.

313b: vbi_increment_counters increment the counters at 3019 - 301e

3155: vbi_read_porta VBI vector #1, reads $d300 to put raw joystick values in 3020, 3021

31ec: vbi_music_player VBI music player

32b0: start_music_list subroutine: music driver: start playing a tune pointed to by $3040, duration passed into via the A register (music plays during the VBI). The sound routine clobbers the X (and Y?) register; save those somewhere first if needed.

32ff: vbi_music_semaphore semaphore used to make sure music player doesn’t make changes during VBI

3300: subroutine: copy 330c-331b to 034f

    330b: 16 bytes, 330c - 331b copied to 034f

331c: start of level construction. Uses address pointer from 3042 (copied from 283c). Grand Puzzle I uses this subroutine to graft new ladders into the existing playfield, at 292e-293e. Runaway uses it to disappear and reappear peanuts in Runaway. (If you need it, save the X register before calling this routine. Don’t know about Y.)

3410: init_pm initialize player/missile graphics for gameplay

3451: clear_pm clear player/missile memory? sets up self-modifying code in the 34ab routine

348b: another clear routine, sets up self-modifying code in the 34ab routine

34ab: self-modifying code to clear 5 pages of memory

    34b3: initialized to value from 304f

34d0: VBI vector #7

34f1: player graphic update: loops through the 5 possible players & updates player memory if player has moved or the animation frame number has changed. It checks the X position and will update the player graphic even though the memory doesn’t have to change with a change in the X position

3600: (inside VBI) copies collision registers

3690: VBI vector #9

3780: subroutine: turns off audio, zeros out working data in the 3000 range

3800: Clear game playfield memory

3820: subroutine: more level initialization

3870: VBI vector #5

3c00: gameplay display list, nominally:

    > dlist

    3C00: 3x 8 BLANK

    3C03: LMS 7000 MODE D

    3C06: 86x MODE D

    3C5C: 2x DLI MODE D

    3C5E: MODE 6

    3C5F: HSCROL MODE 6

    3C60: DLI MODE E

    3C61: MODE D

    3C62: JVB 3C00

   

    or 89*40 + 2*20 + 40 + 40 = 3680 or 0xe60 bytes

   

    There are actually 0xe64 bytes used, maybe 4 extra bytes from the HSCROL on

    that mode 6 line?

3e00: character set during gameplay, set at 3863

4100-410f: local storage

    4100-4101: jump target for START

    4102-4103: jump target for SELECT

    4104-4105: jump target for OPTION

    4106: zero flag if START allowed; otherwise 01

    4107: zero flag if SELECT allowed; otherwise 02

    4108: zero if OPTION allowed; otherwise 04

    4109: 1 if in title screen/attract mode, 0 if regular play

4110: checks ATRACT value. if it gets > $20, sends it back to attract mode

4127: console switch driver; checks flags at 4106-4018 to see which vector to jump to

4150: VBI vector #0

41c8: VBI vector #10

41c0: ? 3 out of 4 VBLANKs it is set to zero and the other time it's set to 1. pattern is 1 0 0 0

41e0: ? 3 out of 4 VBLANKs it is set to zero and the other time it's set to 1. pattern is 0 1 0 0.

41e8: breakpoint when select/start pressed in GAME OPTIONS. Stores value of CONSOL (start/select/option keys) in ATRACT ($4D), which disables attract mode

4200: subroutine: start the Jumpman death process.

4400: load_level_from_disk subroutine to read 16 consecutive sectors from starting sector in 30EE and 30EF

4460: possible start to level initialization routine. Copies stuff from the 2800 region. It's called from the root level; i.e. nothing above it in the stack. Breaking during the 4400 routine shows this:

    > stack

    01FA: 42 45  4540: JSR 4400

    01FC: E2 0F  0FE0: JSR 4540

    01FE: 75 44  4473: JSR 0FE0

46e9: subroutine to force score display to update. The routine clobbers the X (and Y?) register; save those somewhere first if needed.

4730: vbi_update_score update store display, called from music player then exits via 311b

4780: loads bonus value from level?

47b0: part of the level initialization. copies 2801,X -> 3086,X and 2809,X -> 30b6,X

47d4: subroutine: check console switches for bit 0 or bit 1? not used during game options screen, seems to be active during level load

4800: VBI vector #6

4974: music_list_bang BANG sound data (to play, load 4974 into 3040-3041, set A to 4, then JSR 32b0)

49a0: VBI subroutine called by levels for death by bullet or baddie. If JM has collided with Players 2 or 3, or any of the missiles, or the combined missile player, he falls to his death.

49d0: subroutine: called immediately after scrolling the new level into place: play the start-of-level tone and cycle the colors to fade in jumpman. The game loop does not run at this time (VBIs do run), the fade-in of jumpman occurs in a big delay loop within this subroutine.

4a60: VBI vector #8

4b00: Some sort of harvest table setup/maintenance that happens before every Jumpman life.

4ca0: subroutine: modifies display list to set up extra DLI bit at top of screen

4ccd: gameplay DLI routine (set at 4cb2)

4cfd-4cff: total bonus points accumulated?

4d00: subroutine: initialize initial number of players

5000: driver to set up level? Calls 3800, after which screen is scrolled into view

5049: copy screen down one line?

51c7: local data

    51c7-51c8: location $2710 stored here before call to $0a00? Jump vector?

5200: entry point: called on successful level completion to figure out where to go next

5290: entry point

52b8: subroutine:

5300: entry point: show final score, sets display list to $539d

5400: subroutine:

55f8: subroutine:

5590: entry point: new level? Calls 5000 to set up level. Breakpoint at 5593 returns after level has scrolled into view.

5600: jump target: Title screen!

5687: jump target: when SELECT pressed; called by VBI to return to game options screen.

56af: subroutine: 65536 cycle decrement delay

5738: jump target: continuing 5600

5cb0: demo level autoplayer data. 3-tuples: delay time, joystick value, trigger value

    joystick values:

        a0 = left (Huh. Jumping Blocks uses b0 for left. Both work)

        e0 = up

        70 = right

        d0 = down

    trigger values:

        21 = not pressed

        10 = pressed

5ff4: entry point: start the “game over” process

6000 - 67ff: gameplay player/missile memory (single line resolution, so definitions start at 6300) See Mapping The Atari: Appendix 7.

6300 — missile graphics

6400 — player 0 graphics (that’s always Jumpman)

6500 — player 1 graphics (always the shadow)

6600 — player 2 graphics

6700 — player 3 graphics

Through 67ff

606a: entry point: girder crumble routine

6800: entry point for end of game fireworks (called from $3cf9

68fd: entry point, display list to $6fcc: high score table. Ends by jumping to either $0bb8 or $5600

6d22: successful completion entry point: completing a game mode that can be completed (Beginner, Intermediate, Advanced, Grand Loop) and bonus calculation

6e00: high score screen memory (with first entry in top scores and high scores both being "ROB" with a score of 8125)

    > m 6e00

    6E00: 00 C2 C2 C2 00 F4 EF F0 00 F3 E3 EF F2 E5 F3 00  ................

    6E10: C2 C2 C2 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

    6E20: 00 00 00 00 00 00 00 00 00 00 00 91 8E 00 72 6F  ..............ro

    6E30: 62 00 00 00 18 11 12 15 00 00 00 00 00 00 00 92  b...............

    6E40: 8E 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00  ................

    6E50: 00 00 00 93 8E 00 00 00 00 00 00 00 00 00 00 10  ................

    6E60: 00 00 00 00 00 00 00 94 8E 00 00 00 00 00 00 00  ................

    6E70: 00 00 00 10 00 00 00 00 00 00 00 95 8E 00 00 00  ................

    6E80: 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 96  ................

    6E90: 8E 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00  ................

    6EA0: 00 00 00 97 8E 00 00 00 00 00 00 00 00 00 00 10  ................

    6EB0: 00 00 00 00 00 00 00 98 8E 00 00 00 00 00 00 00  ................

    6EC0: 00 00 00 10 00 00 00 00 00 00 00 99 8E 00 00 00  ................

    6ED0: 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 00  ................

    6EE0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

    6EF0: 00 C2 C2 C2 00 E8 E9 E7 E8 00 E2 EF EE F5 F3 00  ................

    6F00: C2 C2 C2 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

    6F10: 00 00 00 00 00 00 00 00 00 00 00 91 8E 00 72 6F  ..............ro

    6F20: 62 00 00 00 11 15 10 10 00 00 00 00 00 00 00 92  b...............

    6F30: 8E 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00  ................

    6F40: 00 00 00 93 8E 00 00 00 00 00 00 00 00 00 00 10  ................

    6F50: 00 00 00 00 00 00 00 94 8E 00 00 00 00 00 00 00  ................

    6F60: 00 00 00 10 00 00 00 00 00 00 00 95 8E 00 00 00  ................

    6F70: 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 96  ................

    6F80: 8E 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00  ................

    6F90: 00 00 00 97 8E 00 00 00 00 00 00 00 00 00 00 10  ................

    6FA0: 00 00 00 00 00 00 00 98 8E 00 00 00 00 00 00 00  ................

    6FB0: 00 00 00 10 00 00 00 00 00 00 00 99 8E 00 00 00  ................

    6FC0: 00 00 00 00 00 00 00 10 00 00 00 00              ............

6fcc: (immediately after 6e00 above): high score display list

    6FC0:                                     70 70 70 70              pppp

    6FD0: 46 00 6E 06 06 06 06 06 06 06 06 06 06 06 06 06  F.n.............

    6FE0: 06 06 06 06 06 06 06 06 06 41 CC 6F              .........A.o

7000 - 7e64: gameplay display memory

    7de8 - 7e10: score

Boot Process

Sectors 2-5 are loaded at $800-$9ff, inits at $800. It sets the loading display list and tests for 32K. If successful, loads sectors 560-719 starting at $2000, then jumps to $2900 (indirect jump to the value set at $09d6).

At $2900, it checks for a keypress to clear high scores (!) then continues on to the routine at $2b00 to load sectors 548-559 at $0a00. It then jumps to $55c0 to begin the non I/O initialization. This in turn jumps to $5600 which is also the routine that can be called at any time to return to the title screen.

Memory Map Refresher

cb-d1 are free 0-page addresses that Jumpman sometimes uses, e.g. for indexed zeropage addressing

0200,0201 VDSLST display list interruptlow, high

022f SDMCTL DMA enable: screen width & player/missile control

0230 SDLSTL display list low

0231 SDLSTH display list high

0304 DBUFLO low byte of disk buffer

0305 DBUFHI high byte of disk buffer

030a DAUX1 low byte of sector number

030b DAUX2 high byte of sector number

d00c SIZEM size for all missiles

d016 COLPF 0 playfield colors

d017 COLPF 1

d018 COLPF 2

d019 COLPF 3

d01a COLBK

d01b PRIOR sprite priority

d01d GRACTL turn on players/missiles

d01f CONSOL R: start/select/option key bitmap. START=6, SELECT=5, OPTION=3

d20a RANDOM

d407 PMBASE page pointing to player missile graphics base

d40a WSYNC wait for horizontal sync

e453 DSKINV disk interface entry point (read a sector)

Level Definition Table Examples

Level Definition table typically starts at $2c00 but it doesn’t absolutely need to. Jump N Run for some reason starts with with the harvest table at $2c00.  Level layout starts at 2c66 rather than the usual 2c00. Why Glover did it differently, I don’t know. But as long as $284e-f points to the harvest table, and the parser can find the level description (fe …) it seems happy.

The other unusual location for level definition is GP II. Level layout is at 2e80 rather than the usual 2c00. This seems to be simply because the custom level code starting at $2b00 is so long, Glover moved the level definition from its usual location.

Easy Does It

fe 04 00        a horizontally drawn thing: drawn X 4 pixels apart, Y 0 pixels apart

                Drawing direction remains in effect until changed

                This is only used for girders

fc 00 40        Draw girders (see table of objects)

fd 04 09 05        Draw the girder: X, Y, length

fd 24 09 06         ...another...

fd 64 09 06

fd 88 09 05

fd 28 19 04

fd 68 19 04

fd 04 1d 05

fd 44 1d 06

fd 88 1d 05

fd 04 2d 05

fd 24 2d 02

fd 38 2d 0c

fd 74 2d 02

fd 88 2d 05

fd 38 3d 0c

fd 04 45 06

fd 84 45 06

fd 04 55 26

fd 48 52 04

fe 04 ff                an up-ramped thing: drawn X 4 pixels apart, Y -1 pixels apart

                This is only used for ramped girders

fd 18 0a 01

fd 7c 0a 01

fd 1c 0b 02

fd 80 0b 02

fd 3c 08 03

fd 5c 1c 03

fd 1c 44 07

fe 04 01        a down-ramped thing: drawn X 4 pixels apart, Y 1 pixels apart

                This is only used for ramped girders

fd 58 06 03

fd 38 1a 03

fd 68 3e 07

fe 00 04         a vertical (drawn top down) thing, X 0 pixels apart, Y 4 pixels apart

                This is used for ladders and ropes

                Note: drawing bottom up is possible (fe 00 fb) but not used by Glover

fc 2c 40         Draw ladders

fd 0c 05 0b         Draw the ladder: X, Y, length

fd 0c 41 05         ...another...

fd 2c 05 05

fd 3c 29 05

fd 4c 19 05

fd 5c 29 05

fd 6c 05 05

fd 8c 05 0b

fd 8c 41 05

fc af 40         Draw up-ropes

fd 27 30 02

fd 77 30 02

fc 83 40         Draw peanuts. Length should always be 01 for peanuts.

fd 04 06 01

fd 44 03 01

fd 58 03 01

fd 98 06 01

fd 04 15 01

fd 98 15 01

fd 24 25 01

fd 78 25 01

fd 04 52 01

fd 40 47 01

fd 5c 47 01

fd 98 52 01

ff                End of drawing table

Vertical girder stacks are rare but sometimes used (hotfoot and GP III): fe 00 03

Screenshot 2016-05-02 12.38.06.png

List of Objects

00 40 = girder (or ramp, same thing :)

2c 40 = ladder

af 40 = up rope

c0 40 = down rope

83 40 = peanut

16 40 = erase a girder (erasing is used in harvest tables)

56 40 = erase a ladder

d1 40 = erase an up or down rope (only used in harvest tables of Grand Puzzle I II and III)

99 40 = erase a peanut (only used on Runaway)

Obscure Objects

5f 2e = blue block that’s painted over everything on Mystery Maze levels

50 5b = used on intro screen only - inside the sign

6d 5b = used on intro screen only - dotted outer edge of the sign

        Mildly interesting: There’s overlap between these? To demonstrate, here’s the sign with the outer edge turned to down ropes and the inside turned to up ropes.

Screenshot 2016-05-03 16.08.15.png

0e 40 is interesting - a blue bar that Jumpman can stand on. I think this is the top layer of the girder graphic. Also 0a, same thing. Here’s something I invented with that: the climbable blue bar stack (fe 00 03 fc 0e 40). The Y can be 02-05 and still be climbable.

Screenshot 2016-05-02 13.27.25.png

It also works as a girder or ramp. Here’s fe 04 01 fc 0e 40

Screenshot 2016-05-02 13.37.39.png

Rarely there’s an odd-looking thing in Jumpman’s levels, done by breaking the normal placement rules. This base part of Freeze is made by stacking girders very close together: fd 3c 54 0a fd 38 55 0c (then placing the peanut on top after)

Screenshot 2016-05-04 17.55.48.png

Drawing an Object Code

An object code is actually a pointer to a small set of bytes that describe a command language to draw pixels. For example, here are the bytes describing the girder at 4000:

04 00 00 01 01 01 01 04 00 01 01 00 01 00 04 00 02 01 01 01 01 ff

And those describing the ladder at 402c:

02 00 00 02 02 02 06 00 02 02 02 00 01 02 02 02 06 01 02 02 08 00 02 02 02 02 02 02 02 02 02 02 00 03 02 02 02 06 03 02 02 ff

The bytes define a set of horizontal lines that describes the color of each pixel. Taken together, they describe a unit of the object. The level definition codes describe lines made up of these units, where the fe flag sets the spacing between units and the fd flag sets the position on screen and the number of units to draw.

Looking at the girder example and breaking it up the following way shows that there are 3 lines of 4 pixels each:

04 00 00   01 01 01 01

04 00 01   01 00 01 00

04 00 02   01 01 01 01

ff

The first 3 bytes of each line describe the number of pixels to draw horizontally, the x offset, and the y offset. The offsets are added to the X and Y values in the fd command to determine the location on screen.

The next 4 bytes (in this case, because the number of pixels is 4 in each of these lines) describe the color of each pixel. These pixel values are in the range of 0 to 3 because there are 4 colors available in graphics mode 7 (ANTIC mode d). This shows that the girders are drawn mostly in color 1, except the holes in the middle section of the girder are the background color 0.

This process is repeated for each line until reaching the ff flag in the data.

The ladder is more complicated, but breaking it down into the sets of lines makes it easier to follow:

02 00 00   02 02

02 06 00   02 02

02 00 01   02 02

02 06 01   02 02

08 00 02   02 02 02 02 02 02 02 02

02 00 03   02 02

02 06 03   02 02

ff

This shows that the ladder is 4 lines high and there are segments of the ladder that are 2 and 8 pixels wide. Taken all together, it is 4 pixels high and 8 pixels wide and drawn in color 2.

The codes to erase items use the same format, they just draw lots of color 0 pixels. For example, the codes at 4056 to erase a ladder are:

08 00 00   00 00 00 00 00 00 00 00

08 00 01   00 00 00 00 00 00 00 00

08 00 02   00 00 00 00 00 00 00 00

08 00 03   00 00 00 00 00 00 00 00

ff

The advantage of this pixel-level encoding is that there is no byte alignment requirement, so it’s not necessary that anything be lined up on 4 pixel boundaries like character graphics would be. The disadvantage is that it is slower than character graphics would be, but obviously it’s fast enough because the game works!

Harvest Table & Painting Table

After the ff that ends the level definition table, comes the Harvest Table. (Usually. Technically it can go almost anywhere in the level, pointed to by 284e,284f.) There’s an entry for every peanut, describing what objects to make appear or disappear what that peanut is taken; and vectors to subroutine to run when that peanut is taken.

ZZ — an encoded number. Further discussion on this below.

XX YY — x y of the peanut. These match the peanut positions laid out in the level layout. In the same order, too.

4B 28 — vector to subroutine to run when peanut is taken. 4B 28 if none required. If you jump to custom code that you’ve created within the level, end that code with RTS. See https://www.youtube.com/watch?v=_RRrynvRbZs 

4C 28 —vector to the part of the painting table that describes the item(s) to be removed or added when this peanut is taken.

4C 28 if nothing is removed when this peanut is taken.

If something is removed or added, it is the memory location of the item in the painting table

… repeats for every peanut …

ends with FF

Then comes the painting table, which looks a lot like the level definition table.

fe 04 00        what direction to paint

fc 00 40         what object to paint

fd x y len          position and length of the thing

{optional repeat of fd x y len}

{optional additional fe and fc entries}

ff                the end of painting for that particular peanut

Easy Does It

harvest table:

22 04 06 4b 28 54 2d

62 44 03 4b 28 4c 28

82 58 03 4b 28 4c 28

c2 98 06 4b 28 64 2d

24 04 15 4b 28 3e 2d

c4 98 15 4b 28 49 2d

46 24 25 4b 28 4c 28

a6 78 25 4b 28 4c 28

2c 04 52 4b 28 4c 28

6a 40 47 4b 28 4c 28

8a 5c 47 4b 28 4c 28

cc 98 52 4b 28 4c 28

ff

           

painting table:

2d3e: fe 00 04 fc 56 40 fd 0c 21 02 ff

2d49: fe 00 04 fc 56 40 fd 8c 21 02 ff

2d54: fc 16 40 fd 18 0a 01 fd 1c 0b 01 fd 20 0a 01 ff

2d64: fc 16 40 fd 7c 0a 01 fd 80 0b 01 fd 84 0a 01 ff

Robots I

42 18 05 07 2d 4c 28

62 38 02 07 2d 4c 28

82 64 02 07 2d 4c 28

a2 84 05 07 2d 4c 28

46 24 22 07 2d 4c 28

86 58 22 07 2d 4c 28

a6 80 22 07 2d 4c 28

68 44 32 07 2d 4c 28

a8 7b 32 07 2d 4c 28

4a 1e 42 07 2d 4c 28

8c 64 52 07 2d 4c 28

4c 18 52 07 2d 4c 28

ff

           

this harvest table shows the use of a function when the object is collected. It calls 2d07, which is a simple routine:

           

lda #$01

sta $2850

rts

           

which must be detected during the VBI processing.

Jumpman Coordinates

The following image shows jumpman in coordinates 34, c0:

jumpmanx34.png

Jumpman can hang off the edge, e.g. here with coordinates 2e, c0:

jumpmanx2e.png

Here’s poor ol’ dead jumpman at 2a, c6:

jumpmanx2a.png

And here he is at the top right of this level: c4, 20:

jumpmanxc4y20.png

Harvest Table Encoding

Due to the original design of Jumpman, bomb placement is limited. The screen is divided up into 64 grid squares, and only one bomb is allowed per grid square. (If you place more than one, the wrong one can disappear when taken and things get wonky quickly.)

The code uses Jumpman’s X and Y player/missile coordinates in 306a and 306f, then runs it through this equation to get a single checksum value for the bomb location (c.f. Code at $4b1a) The coordinates (c.f. Code at $49d5) which means the X range is the 30 - c6 and the Y range is 20 - c0. An X coordinate of 30 corresponds to the left edge of the playfield graphics. It seems a lot of the levels don’t start at the left edge but instead 4 pixels in, so a common X minimum is 34. So, the equation in Jumpman coords would be:

(($306a + $2846) & #$e0) + ((($306f + $2847) & #$e0)/$10)

It turns everything into an 8x8 grid, that means only 64 possible places that can be in the table.

For the level Easy Does It, the X offset in 2846 is 0 and the Y offset in 2847 is 6. Here’s a visualization, where the blue shaded areas are OK to place bombs, and the red shaded areas must not contain bombs:

harvest.png

The Jumpman level editor in Omnivore includes this grid when placing bombs on the screen.

Jumpman position of 34, 20, so way at the top left of the screen (the upper-most block in the image above) would produce a checksum of (($34 + 0) & $e0) + (($20 + $6) & $e0)/$11 = $22

So to create the checksum using the bomb location, I think you’ll have to add the value of $34 to the bomb X location and the value of $20 to twice the Y bomb location, then run it through the modified equation. Because the player/missile graphics use single line resolution and the graphics is in Graphics 7, Jumpman has twice as many vertical positions as bomb vertical coordinates. The machine language routine in the code that calculates the checksum is based on there is twice the resolution for the input coordinates to this checksum routine.

((bomb_x + $34 + $2846) & #$e0) + ((((bomb_y * 2) + $20 + $2847) & #$e0)/$10)

I’m not sure of the exact offset between the bomb coordinates and the jumpman coords. It might be off by a couple, so the bombs near the edges of each one of the cells in the 8x8 grid might be wrong. We’ll have to experiment.

Experiment with Play The Demo: harvest table = XX 2e 08 4b 28 4c 28 ff. This defines a single bomb at 2e, 08. The goal is to figure out what XX should be. $2846 is 0, $2847 is 6, so the checksum equation says:

Or, in parts:

the X part = ($2e + $34 + $00) & $e0 = $60

The Y part = ($08*2 + $20 + $06) & $e0 = $20,

Together: checksum = $60 + ($20 / $10) = $62

Here’s the code that calculates the checksum:

        .org $4b1a

        lda $284e    ;harvest table checksum calculation for bomb lookup

        sta $ba      ; STOPLN

        lda $284f

        sta $bb      ; STOPLN+1

        lda $306a

        clc

        adc $2846

        and #$e0

        sta $bc      ; TRAPLN

        lda $306f

        clc

        adc $2847

        and #$e0

        lsr a

        lsr a

        lsr a

        lsr a

        ora $bc      ; TRAPLN

        sta $bc      ; TRAPLN

Designing a Level

Use Omnivore to create a new level or modify an existing level.

Notes on The Original 30 Levels

Some levels have special features that can’t yet be created or modified in Omnivore. These include:

Easy Does It (level 1) has unused custom level code at $2900 and $2a00, plus unused graphics at $2b00. Those graphics are duplicated in all 3 Mystery Maze levels. What did Glover have in mind?

Screenshot 2016-08-19 22.01.24.png

Robots I contains a copy of graphics from The Roost. So do Vampire and Invasion.

Robots I also has some unused mystery code (which looks unusable) at 2b00. And unused data at 2a6a-2ab2.

The Robots unused mystery code is duplicated in Roll Me Over at 2b00.

Was a 5th prize being considered for Grand Puzzle II?

image07.png

There are unused graphics in Grand Puzzle I and III.

image02.png

Bombs Away uses two simultaneous falling bombs, but the level is set up to handle three. For some reason, the combined-missile Player is deactivated. Change $29c3 from $ff to $0 to activate it. Also change $29ab to c3 so it makes the falling sound.

Bombs away has unused data at 2ac0-2ad3.

Hand coding

Now that Omnivore is functional as a level editor, hand-coding is not recommended. However, here are some examples for posterity:

Girder on bottom, as low and long as possible: fc 00 40 fd 04 55 26

Girder along the top: fc 00 40 fd 04 01 26

        Jumpman can stand on top of that.

        (Using 00 for the y seems to work, but things go bad when the game ends/the level collapses filling the screen with blue instead of black, and that goes on for a laughably long time)

Climbable girder stack on the left: fc 00 40 fe 00 03 fd 04 01 21

On the right: fc 00 40 fe 00 03 fd 98 01 21

The Peanut Harvest Error Screen

This error screen will appear when a peanut is placed in one of the red shaded areas and is collected by Jumpman. Thanks to Omnivore’s editor, you’ll probably never see this screen.

00BC CHECKSUM: XX  <-- this is the value it computed as the entry into the harvest table using the 4 params below

284E OFFSET X: XX <-- this is the X shift value for the peanut mask, the hx value in my lookup table

284F OFFSET Y: XX <-- the Y shift value for the peanut mask, the hy value in my lookup table

306A JUMPMAN X: XX <-- the player position for Jumpman

306F JUMPMAN Y: XX

PEANUT TABLE: XXXX <-- the address of the harvest table in the level. I really should rename this as HARVEST TABLE

subsequent lines are the checksum values in the harvest table. It will print out a list of all the checksum values until it hits FF

Music

The music driver at 32b0 looks for an available voice and uses the first available slot it finds. Slots are defined by the 4 bytes at 3030, 3032, 3034, 3036. If there is a positive number there, something is using that voice.

The procedure to start playing something seems to be load 3040 & 3041 with the address of your music list, load the accumulator with a number that represents the length of each note: $01 is long and $ff is very short. (This doesn’t change the tempo, just note length.) Then, JSR to 32b0 to submit the music list to the player which plays during the vertical blank.

Here’s a routine at $3760 that plays something:

        lda #$4e

        sta $3040

        lda #$37

        sta $3041

        lda #$04

        jsr $32b0

where the music list at 374e is “01 81 04 01 00”. Here’s another example is at $3ae3:

        lda #$ca

        sta $3040

        lda #$3b

        sta $3041

        lda #$08

        jsr $32b0

Where the music list at 3bca is “01 a5 79 04 60 04 51 04 3c 04 51 04 60 04 79 04 00” . (That’s the Jump sound)

Music List

Music lists are for a single voice. For a more voices, you must make successive calls to 32b0 with separate music lists.

The list is processed two or three bytes at a time: the first byte may be a special flag byte of 1, 2 or 3. If it reads one of these flags, it processes it as a command and looks at the next one or two bytes:

1: next byte gets stored in AUDCx register

2: next byte gets stored in AUDCTL

3: take next 2 bytes and JSR there (a user defined subroutine)

If the byte is not 1, 2, or 3, it is considered a note: the byte is stored in the AUDFx register. The following byte is a priority byte of 1-4. Priority 4 notes take priority over everything else, and will override any priority 1-3 notes if all 4 channels are in use. Priority 1 notes are good for background sounds etc, as they will be overirdden by more important noises like the jumping sound, bang of bullets, etc.

The music list is zero-terminated.

So: 01 a5 79 04 60 04 51 04 3c 04 51 04 60 04 79 04 00

01 next byte gets stored in AUDCx register

a5 this goes in AUDCx register

79 a note

04 high priority

60 a note

04 high priority

00 end of sound data

Custom Code

Custom code on a level allows you to add things like Player enemies (eg Robots), custom behaviors of non-enemy objects (Ladder Challenge), and special actions upon taking peanuts (eg getting extra points and a lovely on-screen “500” for certain peanuts on Grand Puzzles.)

There are four vectors where the game can access custom code:

Kay made a short video at https://www.youtube.com/watch?v=_RRrynvRbZs that shows in the quickest possible way how to create custom code with each of the three typical vectors.

Main game loop code

The vector pointed to by 283b-283c is typically 2880 or 2860. This code is run after the level scrolls into view: it’s responsible for doling out Jumpmen as long as there are lives left, and ending the level when you’ve gotten all the peanuts, or ending the game when you’ve run out of lives.

It makes Jumpman appear and plays the appearance sound. Then a little loop waits until you’ve either taken all the peanuts or die. If you die, it gives you another life if you have one, or ends the game, crumble crumble crumble.)

It may be a little underwhelming, but this is the main game loop. (In reality, most of the work goes on behind the scenes during the VBI.)

You might be tempted to stuff all your level initialization stuff here for P/M graphics and whatnot, but I don’t think you should. More often than not, Glover used exactly the same basic code here on every level (see Ladder Challenge:2880 for this typical code), and does any initialization routines as part of the “run constantly” code.

Well, sometimes he does do P/M graphics setup and other initialization here. My gut is that you only should do that here if you need things to reset to a known state every time Jumpman (re)appears on the level.

I reserve the right to change my mind on this later as we figure things out, but right now my feeling is that although you can use this opportunity for custom code, you probably shouldn’t.

Here’s the basic code used on several levels:

$2880   JSR $49d0    ;Play appear tone, fade Jumpman into view

$2883   JSR $4b00    ;Maintain peanut harvest

        LDA $283e    ;are there 0 peanuts left to take?

        CMP #$00

        BEQ $289e    ;completed the level!

        LDA $30be    ;Is Jumpman super dead yet?

        CMP #$08

        BCC $2883    ;if it's <8, loop back, just keep

watching the peanut count and if he's dead

        LDA $30f0    ;Too bad, he's dead. # lives left?

        CMP #$ff     ;end game if it’s -1

        BNE $2880

        JMP $283f    ;jump to lost last life vector, crumble crumble

$2893   JMP ($2844)  ;jump to successful completion of level vector

Code to run when a particular peanut is taken

This code runs once, when a particular peanut is taken. Put the code somewhere in the free space in the level. End with RTS. Point to the start of your code from the Harvest Table entry for that peanut (See “vector to subroutine to run when peanut is taken”, above.) If you’re using the Jumpman level editor in Omnivore (which I hope you are, for your own sanity) right-click on the Peanut and choose Set Trigger Function to point it to the memory location of your code.

Code to run with any/every peanut is taken

This code runs once each time any peanut is taken. E.g. The robots in Robots I move only when you take a peanut. Put the code somewhere in the free space in the level. End with RTS. Point to the start of your code from 2849-284a. (See “subroutine within the level to jump to, every time a peanut is taken” above). If that functionality isn’t needed, the default is at 2849-284a 4b28, which is always RTS.

Code to run constantly during the level

Sometimes you want your custom code to run constantly during the level: e.g. the robots in Robots 2 are always moving along their path. The ladder in Ladder Challenge is always moving back and forth.

Put the code somewhere in the free space in the level. Typically Glover put it at $2900. Always end the code with JMP $311b (not RTS.) Point to your code from one (or possibly more) of the vectors at 2802-2809 (see “2802-2809”, above.) These 8 bytes comprise a jump table of 4 addresses that are called on a constant basis during the game.

So if your code is at $2900, you can set $2802 to #$00 and $2803 to #$29. There’s currently no way to set this within the Omnivore Jumpman level editor, but you can do it from its hex editor.

Note that there can be multiple entry points for your code, which are run in turn. Ladder Challenge, for instance, has entry points at $2900 (which handles initialization and ladder movement) and $29b8 (which handles collisions with the ladder and barriers.)

The best way to see how this code works is to see how Glover did it. Kay has documented the code for two levels: The Roost and Ladder Challenge. Both use Players, but in rather different ways. You can get the documented code here: http://atariage.com/forums/topic/255262-jumpman-level-design-contest/?p=3573981 

Useful Code

Here’s how to kill Jumpman: e.g. put him into “fall to the bottom of the screen” mode. This is all you have to do, the game takes care of the rest.

LDA #1

STA $30bd

Here’s how to add to the score manually. Assuming 50 points here ($32)

ad f5 30 18 69 32 8d f5 30 ad f6 30 69 00 8d f6 30 ad f7 30 69 00 8d f7 30 20 e9 46

LDA $30F5

CLC

ADC #$32

STA $30F5

LDA $30F6

ADC #$00

STA $30F6

LDA $30F7

ADC #$00

STA $30F7

STX $2fff optional: save the X register somewhere: the score display routine clobbers it

JSR $46E9 force score display to update

LDX $2fff reload X register

Add 400 points (e.g. took a 500-point peanut. 100 regular + 400)

LDA $30F5

CLC

ADC #$90

STA $30F5

LDA $30F6

ADC #$01

STA $30F6

LDA $30F7

ADC #$00

STA $30F7

STX $2fff optional: save the X register somewhere: the score display routine clobbers it

JSR $46E9 force score display to update

LDX $2fff reload X register

Appendix 1: Notes On Our Reference Disk Image

The reference disk image that we have been using is the image available on AtariMania, cracked by an unknown hacker. The original disk was copy protected but at this time we do not have access to an original disk or an ATX image of the original protected disk.

There is an image available on atarionline.pl that has a different sector map (i.e. the levels are in different places than listed in the sector map above).

Referring to our working disk image, there are several places where seemingly random data is encoded into the level data. For example, Jumping Blocks at $2a13 through $2aff has what looks like some raw assembly source:

jumping_blocks_asm.png

as does Builder. In fact, Builder has more of what looks to be the same assembly source. It starts at $2900 and continues through $2aff, where the part at $2a13 - $2aff is exactly the same as the code in Jumping Blocks.

My initial assumption was that this code is code left over from the hacker and not Glover’s code, but examining the version found at atarionline.pl shows that this same code exists in the same places in that version as well. Rob has imaged the pirated copy he obtained in 1984 and also contains this same code. The provenance of these 3 versions is unknown, so despite their similarity they could have all descended from the same original hack.

Circumstantially, though, it is quite possible that this is left over from Glover’s compilation process. The bytes in Builder assemble to the level data at $2d53 through $2dbd. The snippet in Jumping Blocks is a subset of the same assembly text, so it doesn’t assemble to the level data in Jumping Blocks, it assembles to a smaller chunk of the level data in Builder.