Jumpman Reverse Engineering Notes

 (for the Atari 8-bit version)

Our descriptions below are: Copyright © 2016-2026, 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

Introduction

A Note on Terminology

Sector Map

Level Layout

Boot Process

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

7fff: top of memory for original code

Memory Map Refresher

Level Definition Table Examples

Easy Does It

List of Objects

Obscure Objects

Drawing an Object Code

Title screen custom graphics

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 in the vertical blank interval

Useful Code

Appendix 1: Notes On Our Reference Disk Image

Appendix 2: Level Breakdown

Robots 1

Assembly code: gameloop @ 2880

Assembly code: vbi1 @ 2900

Assembly code: harvest table trigger @ 2d07

The Roost

Assembly code: vbi1 @ 2900

Ladder Challenge

Assembly code: vbi1 @ 2900

Assembly code: vbi2 @ 2900

Appendix 3: Reverse Engineering the Title Screen

Appendix 4: Level Load Process

Appendix 5: Level Storage Version 2

Creating Easy Does It v2 by hand

Appendix 6: Creating the Level Builder Test Image

Level Storage Version 1 (Classic Jumpman levels)

Level Storage Version 2

Introduction

Jumpman is one of the standout games on the Atari 8-bit platform, and a favorite (if not the favorite) of Kay and Rob.

Kay has created a web-based level editor at https://www.savetz.com/jumpman/. It replaces the level editor in Omnivore, which has been retired.

A Note on Terminology

Apart from sector numbers which are traditionally shown in decimal, all values in this document are in hex unless either super-obvious or otherwise noted.

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

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

Sector                Description

1                boot sector @ 700

2-5                boot loader @ 800

5-16                blank, not loaded

17-529                levels, loaded on demand during game

530-547        blank, not loaded

548-559        game code, 0a00-0fff

560-575        game code, 2000-27ff

576-591        high score management, 2800-2fff

592-693        game code, 3000-62ff

694-703        blank (but loaded anyway), 6300-67ff

704-719        game code, 6800-6fff

Level Layout

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 I

417        425        mystery maze II

433        441        mystery maze III

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)

Boot Process

The first 3 sectors are loaded at $700, and the code is initialized by a JMP to $706. The boot loader then loads sectors 2-5 at $800-$9ff (even though sectors 2 and 3 were already loaded at $700-87f) and 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.

Jumpman Memory Map

0000-0600: Low Memory

00be: addr used as target to write to screen memory for hex to decimal screen rendering

00cb-00d1: scratch area available for gameloop use. Not saved between calls to VBI, though, so is clobbered every VBI

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, continues via JMP to 261a

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

0f00: new level init, calls 5290 to hide players then jumps to 4460 if 0bfc is zero, otherwise swallows a JSR return value for some reason, sets 0bfc to zero, and exits with jmp to 4c7a

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

22f8: play level complete music, random 0-f in A, $20 in Y

23eb: attract mode 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 a DLI?

261a: zeros bonus score at 4cfd-4cff, continues to 4d00

2640: blank screen with no-op display list and

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 command-line 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 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 status divider/background color

$280b status PF0 / unused

$280c "L=" color

$280d "SCORE" / "BONUS" text color

$280e remaining-life Jumpmen color

$280f status-area background color

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: kill_check addr: always vector to 30e0, which does CLC, RTS. 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.

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: ladder_columns: 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 downrope_columns: 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 P0 / Jumpman init color, but changing it doesnt do much. It is always $0f because Jumpman is normally white. We had trouble making Jumpman color editable, so the game may overwrite or refresh P0 color elsewhere, but structurally this byte belongs there.

Here’s how to actually change Jumpman’s color

    lda #$xx

    sta $02c0   ; P0 / Jumpman color shadow

    sta $d012   ; P0 / Jumpman hardware color now

Do it from VBI or another routine that runs repeatedly.

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 playfield 4 and combined player 4/bullet 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: level_definition: 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,2843: next_level_sector: Sector number of start of next level, copied to 30ee,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: harvest_table: 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 seems to be various uses

2b00: VBI code

2bea: gets stored in HSCROL at 454d for some reason

        0: most levels

4: Vampire, Builder, Runaway, Robots II, Dragon Slayer, Grand Puzzle II, Ride Around, The Roost, Figureit, Follow The Leader, Gunfighter, Going Down

2beb: MUST be $ff or the level won’t work

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

  3000: 4150 vbi_dli_driver -> vbi_counters -> vbi_collision

  3002: 3155 vbi_joystick

  3004: 311b used as vbi1 after level start

  3006: 311b …vbi2

  3008: 311b …vbi3

  300a: 3870 check and update jumpman status flags 30bd and 30be

  300c: 4800 bullet_logic

  300e: 34d0 player_driver

  3010: 4a60 bonus_driver

  3012: 3690 bullet_drawing

  3014: 41c8 vbi_atract

  3016: e462 XITVBV, signal that VBI is over.

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: sound_flag0 music flag for voice #0

3032: sound_flag1 music flag for voice #1

3034: sound_flag2 music flag for voice #2

3036: sound_flag3 music flag for voice #3

3038,3039: sound_list0 music list for voice #0

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

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

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

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

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

3044,3045: fc param: address of object code

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)

3087-308f extra vbi routines? Set in 5ff4 end-of-game routine. 3088, 8a, 8c, 8e map to 3004, 6, 8, a. 3088 = $ff seems to be a trigger to shut off level vbis, 3088=0 do nothing, 3088 = anything else, replace the corresponding 3004 vbi routine, and handled by vbi_collision, which is vbi #0

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

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 OR of D000-D003. See code at vbi_collision

30a1: Combined Missile Player to Player collision byte.  This is an OR of D008-D00B. See code as above

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: jm_alive 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: jm_status 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: ??

30c3-30ca: copies of 2810-2817

        The setup copy loop copies $2810-$2817 into $30c3-$30ca; $30c2 is deliberately left as working state and zeroed separately.

  30c3: 2810 dead_begin

  30c5: 2812 dead_at_bottom

  30c7: 2814 dead_falling

  30c9: 2816 kill_check

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.

30e2,30e3: addr: display list interrupt trigger. Put address into Will be stuffed into DLI pointer by VBI Vector #0 at 4150, then reset to zero. If doing this outside a VBI, set low byte first, then high byte

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

30f9: color score trigger?

30fa: color score index for score area color changing

30fb: flag used in score driver

30fc:

30fe: speed 0-7 corresponding to keypress 1-8

30ff: printable character 1-8 corresponding to keypress 1-8

   

3100-6fff: Code

3100: vbi_init subroutine: initialize vertical blank counters

311b: vbi_driver vertical blank interrupt driver. Loops and calls all VBI vectors in 3000 - 3017. Final vector in 3016 is XITVBV, terminating the VBI.

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

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

3180: joystick processing

31ec: vbi_sound_player VBI music player

32b0: play_sound_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_sound_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 move players off screen & 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: player_driver: VBI vector handles updating player position and image frame

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: vbi_collision: inside VBI #1, copies collision registers

3690: bullet_drawing: masking and drawing of bullets when position changes

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

3800: Clear 4k of memory at 7000, including the jumpman playfield memory at 7000-76xx

3820: subroutine: more level initialization: set gameplay display list, call init_pm and clear_pm, subroutine at 3300, sets up gameplay DLI using the 30e2 vector, sets gameplay character set.

3870: jumpman_movement: VBI vector #5: checks jm_status, if 8 (super dead) skips stuff. Then checks for player control and interaction with environment

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?

3c65: gameplay_dli: DLI #1, occurs at scan line 207 (the DLI on mode D line at 3c5c): sets background color to divider color value set in 30b7, sets DLI handler to DLI #2 at 3c7b

3c7b: DLI #2: occurs at scan line 209 (on the mode D line at 3c5d): sets scoreboard colors to values in 30b8-30bc; sets DLI handler to DLI #3 at 3c9a

3c9a: DLI #3: occurs at scan line 226 (on the mode E line at 3c60): sets background color to divider color (again), value from 30b7; sets DLI handler to DLI #1 in preparation for next frame

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_dli_driver: VBI vector #0: checks for new DLI and calls 313b to increment timers and read joystick

41bc: byte, something to do with jumpman dead processing

41c8: vbi_atract: check for activity & reset ATRACT; handle console switches

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

466b: convert number to decimal and display

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: bullet_driver: all the bullet logic and bang

4974: sound_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: fade_in_jumpman: 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: bonus_driver: VBI vector to handle bonus value decreasing and playing the chime

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

4c00: level_complete: entry point that adds remaining bonus points, starts level complete music,

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 lives, install keyboardhandler

4d2a: keyboard interrupt handler

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

5049: copy screen down one line?

50d8: play level start music

5180: check for bonus life

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: hide players offscreen, turns off combined missile player and p2 and p3

52b8: subroutine:

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

5400: subroutine: draw generalized graphics, used for EPYX Presents and Jumpman logo inside marquee

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. Stops the 4 level VBIs by setting new VBIs through the 3087-308f vectors to 60c9, 311b, 311b, and 49a0.

6000 - 67ff: gameplay player/missile memory (single line resolution, so definitions start at 6300, code still exists from 6000-62ff) 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)

        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

7fff: top of memory for original code

Jumpman was designed to run in 32K of RAM, so page $80 and above are not used by the original code. Jumpman II requires 48k, we use the 16K of memory from page $80 to page $BF.

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!

Title screen custom graphics

The custom drawing objects are:

5b50: a 4x4 block of color 1

04 00 00 01 01 01 01

04 00 01 01 01 01 01

04 00 02 01 01 01 01

04 00 03 01 01 01 01

FF

5b6d: a 2x1 group of color 3

02 00 00 03 03 FF

The drawing definition is located in the level definition table at 5815, and starting at 583c draws the gray box:

FE 04 00                sets spacing to x=4, y=0

FC 50 5B                sets type to 5b50

FD 2E 2F 11                draw at x=2e, y=2f, len=11 (i.e. 17 groups of 4x4 color 1 pixels)

FD 2E 33 11                y=33

FD 2E 37 11                y=37

FD 2E 3B 11                y=3b

And 5852 draws the flushing border:

FC 6D 5B                sets type to 5b6d (note spacing is still x=4, y=0 from above)

FD 2E 2F 11                draw at x=2e, y=2f, len=11 (i.e. top line)

FD 30 3E 11                x=30, y=3e (bottom line)

FE 00 02                sets spacing to x=0, y=2

FD 2E 2F 08                x=2e, y=2f, len=8 (left edge)

FD 70 30 07                x=70, y=30, len=7 (right edge)

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

lda $284f

sta $bb

lda $306a

clc

adc $2846

and #$e0

sta $bc

lda $306f

clc

adc $2847

and #$e0

lsr a

lsr a

lsr a

lsr a

ora $bc

sta $bc

Designing a Level

Use https://www.savetz.com/jumpman/ 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 the level editor. 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 we have a level editor, hand-coding the level definition table 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 the checks built in to the level 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

$289e   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 level editor, select the peanut and choose the trigger callback in the right-hand menu to select the address of the routine to run when this peanut is taken.

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 in the vertical blank interval

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. These routines are run during the vertical blank interval at the end of every TV frame, so they are triggered at a constant rate during gameplay, 60 times per second (50 if on a PAL system).

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.

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 Jumpman level editor can handle all 4 of the vertical blank routines.

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 also see the original post announcing Kay’s documentation 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.

Appendix 2: Level Breakdown

Robots 1

2800-2801: level_number: 3032 (02 in atascii)

2802-2803: vbi1: 2900
2804-2805:
vbi2: 311b

2806-2807: vbi3: 311b

2808-2809: vbi4: 490a (which points to the default vbi4)

280a: color_border: c6

280b: color_unused: 94

280c: color_lives_prefix: aa

280d color_words: 78

280e color_lives: 0f

280f color_score_background: 00

2810,2811: dead_begin: 4200 (points to the standard call)

2812,2813: dead_at_bottom: 4580 (points to the standard call)

2814,2815: dead_falling: 311b (points to the standard routine)

2816-2817: kill_check: 30e0 (points to the standard routine)

2818-2823: ladder_columns: 34 4c 68 7c 90 a0 c4 00 00 00 00 00

2824-2829: downrope_columns: 00 00 00 00 00 00

282a: color_jumpman: 0f

282b: color_shadow: 00

282c: color_p2: 28

282d: color_p3: c6

282e: color_girder_uprope: 96

282f: color_ladder_downrope: 18

2830: color_peanut: 5a

2831: color_bullet_p4: ca

2832: color_background: 00

2833,2834: points_peanut: 64 (decimal 100)

2835,2836: points_bonus: 05dc (decimal 1500)

2837,2838: level_def_table: 2c00

2839: jumpman_start_x: bc

283a: jumpman_start_y: c0

283b,283c: gameloop: 2880

283d: num_bullets: 0

283e: num_peanuts: 0c

283f: jmp_target_out_of_lives: 4c

2840-2841: out_of_lives: 4ffd

2842,2843: sector_next_level: 0031

2844-2845: level_complete: 4c00

2846: harvest_x: 00

2847: harvest_y: 06

2848: 20 (always a JSR)

2849-284a: collect_callback: 284b

284b: 60 (always RTS)

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

284e,284f: harvest_addr: 2cb2

Assembly code: gameloop @ 2880

This is the standard game loop that fades in jumpman, checks the number of lives left, and either moves on to the next level or ends the game if the player runs out of lives. All the work moving the robots when peanuts are collected happens is the vbi1 routine, documented below.

Assembly code: vbi1 @ 2900

2900       lda L30bd      ; VBI entry point.

2903       cmp #$02       ; if Jumpman is super dead

2905       bne L2912

2907 L2907 jmp L311b      ; exit the custom level stuff

290a L290a lda #$00       ; after the 2x loop

290c       sta L2850      ; clear peanut taken flag

290f       jmp L311b      ; exit

2912 L2912 lda L2a5c      ; $2a5c is Are Players initialized?

2915       cmp #$00

2917       bne L291c

2919       jmp L2a0c      ; Go initialize Players.

291c L291c jsr L41e0      ; Players are already initialized.

291f       bne L2907      ; exit 1/4 of the time.  Why?

2921       inc L2a65

2924       lda L2a65      ; alternates between 0 and 1 for animation

2927       and #$01

2929       sta L2a65      ; alternate it

292c       ldx #$ff

292e L292e inx            ; this loop is done twice, as 0 and 1

292f       cpx #$02

2931       beq L290a      ; exit the loop

2933       lda L2a5a,x    ; is robot already on the move to a destination?

2936       cmp #$00

2938       bne L2990      ; If so, JMP away

293a       lda L2850      ; Robot is not on the move.

293d       cmp #$00       ; Has a peanut just been taken?

293f       bne L2955      ; If so, JMP away

2941       lda L2a66,x    ; Nope. No robots are moving, so just animate in place. 2a66 Counts 1-4

2944       and #$03       ; set the image for each robot

2946       sta L2a66,x    ; to one of the first 4 graphics

2949       inc L2a66,x    ; rotating in order

294c       lda L2a66,x    ; their graphics move in unison

294f       sta L3076,x    ; Set image data for Player 2/3

2952       jmp L292e      ; next loop

2955 L2955 jsr L29da      ; Oh boy a peanut has been taken

2958       lda #$01       ; reminder: we're in a loop where X will be 0 or 1

295a       sta L2a5a,x    ; Set this robot is on the move

295d       lda L2a61,x    ; get pointer to next 3-tuple of movement data, LB

2960       sta L00cb      ; cb-d1 are scratch 0-page addresses

2962       lda L2a63,x    ; starts by loading cb-cc with 2e00 when X=0 and 2f00 when X=1

2965       sta L00cc

2967       ldy #$00

2969       lda (L00cb),y  ; load 2e00, e.g. This is the robot's X change.

296b       sta L2a5d,x    ; remember robot's X change

296e       iny

296f       lda (L00cb),y  ; load 2e01, e.g.

2971       sta L2a5f,x    ; remember robot's Y change

2974       iny

2975       lda (L00cb),y  ; load 2e02, e.g.

2977       sta L2a68,x    ; remember robot move counter

297a       lda L2a61,x    ; pointer to next 3-tuple of movement data

297d       clc

297e       adc #$03       ; set up pointer to read next set

2980       sta L2a61,x

2983       lda L2a68,x    ; get robot move counter

2986       cmp #$00

2988       bne L292e      ; if >0, iterate X to next robot

298a       sta L2a5a,x    ; robot's not to its destination yet, store counter in the On The Move? byte

298d       jmp L292e      ; iterate X to next robot

2990 L2990 dec L2a68,x    ; Robot is not to its destination. Move a step. Decrement robot move countdown.

2993       beq L2955      ; if its 0, jump to oh boy a peanut has been taken

2995       lda L306c,x    ; get Horizontal position of robot

2998       clc

2999       adc L2a5d,x    ; add robot X change

299c       sta L306c,x    ; change robot's X

299f       clc

29a0       lda L3071,x    ; get Vertical position of robot

29a3       adc L2a5f,x    ; add robot Y change

29a6       sta L3071,x    ; change robot's Y

29a9       lda L2a5d,x    ; get robot X change again

29ac       cmp #$02       ; Show graphic for robot facing the right way, and animate. is it going 02 (right)?

29ae       beq L29b9

29b0       cmp #$fe       ; is it fe (left)?

29b2       beq L29be

29b4       lda #$05       ; robot's X is not changing (it's on a ladder)

29b6       jmp L29c3

29b9 L29b9 lda #$01       ; it's 02 right, so use image 1 (or 2)

29bb       jmp L29c3

29be L29be lda #$03       ; its fe left, so use image 3 (or 4)

29c0       jmp L29c3      ; this JMP is silly.

29c3 L29c3 clc            ; A is loaded with a pointer to a different image based on X movement

29c4       adc L2a65      ; add 0 or 1 to animate movement

29c7       sta L3076,x    ; Set image data for robot

29ca       jmp L292e      ; end of loop; NEXT X

29cd       lda #$00       ; THIS CODE IS UNREACHABLE

29cf       sta L2a5a,x    ; UNREACHABLE:Store that robot has reached its destination, no longer moving

29d2       lda #$01       ; UNREACHABLE:

29d4       sta L2a66,x    ; UNREACHABLE:reset 1-4 counter back to 1 for standing still animation

29d7       jmp L292e      ; UNREACHABLE:iterate loop; NEXT X

29da L29da lda #$f1       ; sound subroutine

29dc       sta L3040

29df       lda #$29       ; load address of sound

29e1       sta L3041

29e4       stx L29f0      ; temp. storage of X register. Basically, a PHX

29e7       lda #$07

29e9       jsr L32b0      ; submit the sound list to the player for robot sound

29ec       ldx L29f0      ; reset X register after JSR. PLX equiv.

29ef       rts            ; end sound subroutine

29f0 L29f0 .byte 00       ; Temporary storage of X in the sound routine.

29f1       .byte 01a21401f0013c01 ; Robot sound data

29f9       .byte c8016401a0018c01

2a01       .byte 78017d0173018201

2a09       .byte 6e0100

2a0c L2a0c lda #$00       ; PM Initialization

2a0e       sta L3080      ; old Y position of Player 2

2a11       sta L3081      ; old Y position of Player 3

2a14       lda #$6a

2a16       sta L305d      ; image data LB for Player 2

2a19       sta L305e      ; image data LB for Player 3

2a1c       lda #$2a

2a1e       sta L3062      ; image data HB for Player 3

2a21       sta L3063      ; image data HB for Player 3

2a24       lda #$0c

2a26       sta L3067      ; image data bytes per image for Player 2

2a29       sta L3068      ; image data bytes per image for Player 3

2a2c       lda #$01

2a2e       sta L3076      ; Set image data for Player 2

2a31       sta L3077      ; Set image data for Player 3

2a34       lda L2a5d

2a37       sta L306c      ; Horizontal position of player 2

2a3a       lda L2a5e

2a3d       sta L306d      ; Horizontal position of player 3

2a40       lda L2a5f

2a43       sta L3071      ; Y position of Player 2

2a46       lda L2a60

2a49       sta L3072      ; Y position of Player 3

2a4c       lda #$01

2a4e       sta L3058      ; P2 active

2a51       sta L3059      ; P3 active

2a54       inc L2a5c      ; Mark players as initialized

2a57       jmp L311b      ; exit

2a5a L2a5a .byte 00       ; Player 2 on the move?  0=reached destination

2a5b       .byte 00       ; Player 3 on the move?  0=reached destination

2a5c L2a5c .byte 00       ; Are Players initialized? 1=yes

2a5d L2a5d .byte 98       ; Player 2 robot X change

2a5e L2a5e .byte 44       ; Player 3 robot X change

2a5f L2a5f .byte be       ; Player 2 robot Y change

2a60 L2a60 .byte 32       ; Player 3 robot Y change

2a61 L2a61 .byte 00       ; Player 2 pointer to next 3-tuple of movement data, LB

2a62       .byte 00       ; Player 3 pointer to next 3-tuple of movement data, LB

2a63 L2a63 .byte 2e       ; Player 2 pointer to 3-tuples of movement data, HB (this never changes)

2a64       .byte 2f       ; Player 3 pointer to 3-tuples of movement data, HB (this never changes)

2a65 L2a65 .byte 00       ; alternates between 0 and 1 (for animation)

2a66 L2a66 .byte 01       ; Counts 1-4 for Player 2 robot animation

2a67       .byte 01       ; Counts 1-4 for Player 3 robot animation

2a68 L2a68 .byte 00       ; Player 2 robot move countdown

2a69       .byte 00       ; Player 3 robot move countdown

Assembly code: harvest table trigger @ 2d07

Every time a peanut is taken, this code is called using the trigger function in the harvest table. This simple routine sets a flag at location $2850 that is checked in the vbi1 code (at address $293a in the code above).

2d07       lda #$01       ; Trigger: any peanut taken

2d09       sta L2850      ; $2850 is "a peanut has been taken"

2d0c       rts

The Roost

This level uses the standard gameloop and vbi1.

Assembly code: vbi1 @ 2900

2900       lda L30bd

2903       cmp #$02       ; if Jumpman is dead at the bottom of the screen, skip all this

2905       bne L290d

2907       jmp L311b

290a L290a jmp L2a2d      ; deal with enemy color changing and attack sounds 1/4 of the time

290d L290d jsr L41e0      ; this returns 1 every fourth time.

2910       bne L290a

2912       inc L2ace

2915       lda L2ace

2918       and #$01

291a       sta L2ace      ; 2ace alternates 0 and 1

291d       ldx #$ff

291f L291f inx            ; iterate through Player memory: Missiles, 2, and 3

2920       cpx L2acd      ; 2acd is always 5. So iterate through until X=5

2923       beq L290a

2925       cpx #$01       ; skip P0 ("zone" 1 in player memory map)

2927       beq L291f

2929       cpx #$02       ; skip P1 ("zone" 2)

292b       beq L291f

292d       lda L2a80,x    ; get byte of graphics

2930       cmp #$00       ; if it's first time, initialize

2932       bne L2976

2934       lda #$85       ; initialize

2936       sta L305a,x    ; image data LB

2939       lda #$2a

293b       sta L305f,x    ; image data HB

293e       lda #$08

2940       sta L3064,x    ; image data bytes per image

2943       lda #$01

2945       sta L3073,x    ; start with graphic #1

2948       sta L3055,x    ; activate all players in turn

294b       sta L2a80,x    ; remember this Player has been initialzied

294e       lda #$c2

2950       sta L306e,x    ; set Y of enemy Player

2953       lda #$7c

2955       sta L3069,x    ; set X of ememy Player

2958       cpx #$00       ; except we don't really want them

295a       beq L2966      ; all starting in the same X

295c       cpx #$03       ; so space them nicely

295e       beq L296e

2960       sta L3069,x    ; for Player 3

2963       jmp L291f

2966 L2966 lda #$54       ; for the combined-Missile Player...

2968       sta L3069,x    ; change its starting X

296b       jmp L291f

296e L296e lda #$a4       ; for Player 2

2970       sta L3069,x    ; change its starting X

2973       jmp L291f

2976 L2976 lda L306e,x    ; get Y of emeny

2979       sec

297a       sbc #$02       ; it ascends

297c       cmp L306f      ; compare enemy Y to Jumpman Y

297f       beq L29e9      ; if they're equal, go to attack mode

2981       cpx #$00       ; For the Combined Missile Player...

2983       bne L298b

2985       lda L30a0      ; get Combined Missile-to Playfield collision byte

2988       jmp L298e

298b L298b lda L3093,x    ; but for the other enemy Players (2 and 3) get their collission register

298e L298e cmp #$00       ; If that Player is colliding with the playfield...

2990       bne L29d8      ; ...jump to 29d8 to put it in floating up mode

2992       lda L306e,x    ; So Player is in X adjust mode, wants to move up and move its X towards Jumpman's X

2995       sec

2996       sbc #$02       ; move Player up

2998       cmp #$05

299a       bcc L29cd      ; if too far up, de-initialize that Player. It'll be re-initialized at the screen bottom later.

299c       sta L306e,x    ; save that new Player Y

299f       lda L3069,x    ; Get Horizontal position of this Player

29a2       cmp L306a      ; compare to jumpman X

29a5       bcs L29b5

29a7       clc

29a8       lda L3069,x    ; Jumpman is to Player's right

29ab       adc #$02       ; move right

29ad       sta L3069,x    ; Save new Player X

29b0       lda #$06       ; save this 6 for later. This will be the Graphic number (6 or 7)

29b2       jmp L29c0

29b5 L29b5 lda L3069,x    ; Jumpman is to Player's left

29b8       sec

29b9       sbc #$02       ; move left

29bb       sta L3069,x    ; Save new Player X

29be       lda #$08       ; save this 8 for later. This wil be the Graphic number (8 or 9)

29c0 L29c0 pha

29c1       lda L2acf      ; 2acf is this level's What Sound To Make byte. Bit 1 means some emeny is in X adjust mode.

29c4       ora #$01

29c6       sta L2acf

29c9       pla            ; Retrieve that saved number...

29ca       jmp L2a23      ; To be continued...

29cd L29cd lda #$00       ; routine to un-initialize a Player

29cf       sta L2a80,x    ; un-initalize the Player

29d2       sta L3055,x    ; deactivate the Player

29d5       jmp L291f

29d8 L29d8 lda #$01       ; No collision with playfield, continued

29da       sta L3073,x    ; graphic 1 (floating up)

29dd       lda L306e,x    ; get Y of emeny

29e0       sec

29e1       sbc #$01       ; ascend a line

29e3       sta L306e,x    ; save new enemy Y

29e6       jmp L291f

29e9 L29e9 lda L2acf      ; start attack mode

29ec       ora #$02

29ee       sta L2acf      ; Remember that some enemy is in attack mode.

29f1       lda L3069,x    ; get X of enemy

29f4       cmp L306a      ; jumpman X

29f7       bcs L2a0e

29f9       lda L3069,x    ; jumpman is to enemy's right

29fc       clc

29fd       adc #$02       ; so move the enemy right

29ff       sta L3069,x    ; save new enemy X

2a02       lda #$02

2a04       clc

2a05       adc L2ace      ; 2ace is 0 or 1, so this=2 or 3

2a08       sta L3073,x    ; use graphic 2 or 3

2a0b       jmp L291f

2a0e L2a0e lda L3069,x    ; jumpman is to enemy's left

2a11       sec

2a12       sbc #$02       ; so move enemy left

2a14       sta L3069,x    ; save new enemy X

2a17       lda #$04

2a19       clc

2a1a       adc L2ace      ; 2ace is 0 or 1 so this is 4 or 5

2a1d       sta L3073,x    ; use graphic 4/5

2a20       jmp L291f

2a23 L2a23 clc            ; ...X adjust mode, continued.

2a24       adc L2ace      ; add our saved number to 0 or 1

2a27       sta L3073,x    ; And that's our new graphic for the Player

2a2a       jmp L291f

2a2d L2a2d lda L30b8      ; Get Color 0 in Score area. This is constantly changing.

2a30       sta COLOR3     ; Combined Missile Player Color

2a33       sta PCOLR2     ; Player 2 Color

2a36       sta PCOLR3     ; Player 3 Color

2a39       lda L2ace      ; This switches between 0 and 1

2a3c       cmp #$01

2a3e       beq L2a68      ; ...So Half The Time...

2a40       lda L2acf      ; Should we be making a sound based on enemy movements?

2a43       cmp #$00       ; if its 0, we're done here

2a45       beq L2a53

2a47       cmp #$01       ; if its 1, X adjust mode sound

2a49       beq L2a5b

2a4b       cmp #$02       ; if its 2, attack mode sound

2a4d       beq L2a73

2a4f       cmp #$03       ; if bits 1 and 2, attack mode sound takes preceince over X adjust sound

2a51       beq L2a73

2a53 L2a53 lda #$00       ; nah, it's 0

2a55       sta L2acf      ; reset 2acf to 0

2a58       jmp L311b

2a5b L2a5b lda #$81       ; its 1, so...

2a5d       sta AUDC2      ; AUDC2 - make a noise

2a60       lda #$20

2a62       sta AUDF2      ; AUDF2 - make a noise

2a65       jmp L2a53

2a68 L2a68 lda #$00       ; ...The Other Half of the Time, quiet...

2a6a       sta AUDC2      ; AUDC2 quiet audio channel 2

2a6d       sta AUDF2      ; AUDF2 quiet audio channel 2

2a70       jmp L2a53

2a73 L2a73 lda #$84       ; it's 2 or 3, so...

2a75       sta AUDC2      ; AUDC2 - make sound

2a78       lda #$10

2a7a       sta AUDF2      ; AUDF2 - make sound

2a7d       jmp L2a53

Ladder Challenge

This level uses the standard game loop and two VBIs, vbi1 and vbi2.

Assembly code: vbi1 @ 2900

2900       lda L30bd      ; Jumpman alive flag

2903       cmp #$02       ; if he's dead at the bottom of the screen, exit this routine

2905       bne L290a

2907 L2907 jmp L311b      ; VBI driver

290a L290a jsr L41e0      ; ?

290d       bne L2907      ; ?loop until ?

290f       lda L29b7      ; check initialized? byte

2912       cmp #$00

2914       beq L293d      ; 1st time, go initialize

2916       lda L29b6      ; get ladder X change - 02 or FE

2919       sta L29b5      ; save as old ladder X change

291c       clc

291d       adc L306d      ; add that to current ladder X position

2920       sta L306d      ; and move the ladder to new X

2923       sta L30d2      ; make it a valid ladder position

2926       cmp #$62       ; < ladder L extreme?

2928       beq L2931

292a       cmp #$96       ; > ladder R extreme?

292c       beq L2931

292e       jmp L2907

2931 L2931 lda #$00       ; reverse ladder direction

2933       sec

2934       sbc L29b6

2937       sta L29b6

293a       jmp L2907

293d L293d lda #$01       ; initialization

293f       sta L3067      ; image data bytes per image for Player 2

2942       sta L3068      ; image data bytes per image for Player 3

2945       sta L3080      ; old Y position of Player 2 - forces Player do be drawn the 1st time

2948       sta L3071      ; Y position of Player 2

294b       sta L3081      ; old Y position of Player 3 - forces Player to be drawn the first time

294e       sta L3072      ; Y position of Player 3

2951       sta L3085      ; old image data for Player 2 - forces Player to be drawn the first time

2954       sta L3086      ; old image data for Player 3 - forces player to be drawn the first time

2957       sta L3076      ; Set image data for Player 2

295a       sta L3077      ; Set image data for Player 3

295d       lda #$7c

295f       sta L306d      ; P3 (ladder) starting X

2962       lda #$70

2964       sta L306c      ; P2 (barriers) X position

2967       lda #$06       ; Barriers are in $6600 zone, so are Player 2

2969       sta L6648      ; top barrier grafx

296c       sta L6649      ; second line for thickness

296f       lda #$18

2971       sta L666c      ; middle barrier grafx

2974       sta L666d      ; second line for thickness

2977       lda #$60

2979       sta L6692      ; bottom barrier grafx

297c       sta L6693      ; second line for thickness

297f       lda #$0d       ; #ladder segments to copy

2981       sta L00cb

2983       ldy #$42

2985 L2985 ldx #$00

2987 L2987 lda L29ad,x    ; copy ladder to PM

298a       sta L6700,y    ; Ladder is in $6700 zone, so Player 3

298d       iny

298e       inx

298f       cpx #$08

2991       bne L2987

2993       dec L00cb

2995       bne L2985      ; end copy ladder

2997       lda #$01

2999       sta L3058      ; barrier PM2 on

299c       sta L3059      ; ladder PM3 on

299f       inc L29b7      ; remember initialization has been done

29a2       lda #$03

29a4       sta L3085      ; Old version of image data for Player 2

29a7       sta SIZEP2     ; SIZEP2 quadruple

29aa       jmp L311b

Assembly code: vbi2 @ 2900

29b8       lda L3098      ; Entry point #2. Copy of d008, M0PL

29bb       ora L3099      ; copy of d009, M1PL

29be       ora L309a      ; copy of d00a, M2PL

29c1       ora L309b      ; copy of d00b, M3PL

29c4       ora L309e      ; copy of d00e, P2PL

29c7       and #$01       ; did P2 or any missle hit P1

29c9       cmp #$00

29cb       beq L29d3

29cd       sta L30bd      ; gonna die - setting this to 1 puts Jumpman in "fall to his death" mode

29d0 L29d0 jmp L311b

29d3 L29d3 jsr L41c0      ; this returns 1 one-fourth of the time

29d6       bne L29f5

29d8       lda L30be      ; Is jumpman super dead?

29db       cmp #$08

29dd       bcs L29d0      ; If so, loop around, wait for new Jumpman to reappear.

29df       lda L309f      ; copy of d00f, P3PL

29e2       and #$01       ; does the ladder touch Player 0 (Jumpman)?

29e4       cmp #$00

29e6       beq L2a12

29e8       clc            ; Jumpman is touching P3 (ladder)

29e9       lda L306a      ; Jumpman X

29ec       adc L29b5

29ef       sta L306a      ; change Jumpman X based on ladder direction

29f2       jmp L2a09

29f5 L29f5 lda L309f      ; copy of d00f - P3PL

29f8       and #$01       ; does P3 touch P1 (jumpman)?

29fa       cmp #$00

29fc       beq L29d0

29fe L29fe lda L3094      ; copy of d004 - P0PF

2a01       ora #$02       ; Force Color 2 bit. Hmm.

2a03       sta L3094      ; save it back. Hm.

2a06       jmp L29d0

2a09 L2a09 lda L30b8      ; borrow color 0 in score area (the flashing numbers)

2a0c       sta PCOLR2     ; PCOL2 - changing barrier color

2a0f       jmp L29fe

2a12 L2a12 lda #$0f       ; jumpman not touching ladder...

2a14       sta PCOLR2     ; PCOL2 - make barriers white

2a17       jmp L29d0

Appendix 3: Reverse Engineering the Title Screen

The routine at 5400 controls a specialized drawing routine that is responsible for the “EPYX presents” logo and the flashing Jumpman name on the title screen marquee. It is designed for mode D graphics, but it’s unclear how it works, so this is a log of my attempt to understand it.

It appears that the graphics data used for this is stored in an array at 5470:

5470 L5470 .byte 4010040180200802

5478       .byte c0300c0300000000

5480       .byte 0400000000000000

5488       .byte 0c00000000000000

5490       .byte 0c00000000000000

5498       .byte 0c447bc7c7bc7c7c

54a0       .byte 0ccccccccccc0ccc

54a8       .byte 0ccccccccccc7ccc

54b0       .byte 0ccccccccccccccc

54b8       .byte 0ccccccccccccccc

54c0       .byte 0cf8888f8888f888

54c8       .byte 4c00000c00000000

54d0       .byte cc00000c00000000

54d8       .byte f800000800000000

54e0       .byte fe7e30cc60c06330

54e8       .byte cc60c06330cc60c0

54f0       .byte 6330c6c0fe6330c3

54f8       .byte 80c063198380c063

5500       .byte 0f06c0c063060c60

5508       .byte fe7e0c0c60006018

5510       .byte 00600060300060ff

5518       .byte ffffffe044245940

5520       .byte 851111493c47d100

5528       .byte 04925123c4245138

5530       .byte 8511114944441100

5538       .byte 8492512444245104

5540       .byte 84e08e4938238e00

5548       .byte 78924e2382239138

5550       .byte 0000000004000000

5558       .byte 00000400f0ce38e5

5560       .byte 9f38891141164440

5568       .byte 8a1f39f444388a10

5570       .byte 05044404f20e38e4

5578       .byte 4238800000000000

5580       .byte 80

5581       .byte 15*00

but it does not seem to be related to any other drawing code.

The first time it’s called is early on in the routine at 5600, the title screen subroutine. It appears to use several variables in the zero page, e0-ef, and some are apparently required to be initialized outside the subroutine. For instance, in this first call to 5400, these variables are initialized:

5638       lda #$e0

563a       sta L00e3

563c       lda #$54

563e       sta L00e4

5640       lda #$78

5642       sta L00eb

5644       lda #$0f

5646       sta L00e0

5648       sta L00e2

564a       lda #$70

564c       sta L00e1

564e       lda #$0c

5650       sta L00e9

5652       lda #$05

5654       sta L00e6

5656       jsr L55f8

55f8 L55f8 lda #$03

55fa       sta L00e2

55fc       jsr L5400

It’s unclear what any of this means at this point. It does write to the screen in the standard location, which is at 7000-7800, so it’s possible that the $70 stored in e1 and the $78 stored into $eb has something to do with the screen, but at this point that’s just a guess.

It seems the input values are:

E0        screen?

E1        screen?

E2        ?

E3        ?

E4        ?

E6        ?

E9        ?

EB        screen?

Here’s the routine at 5400 in all its opaque glory, showing that several variables are copied: e6 -> e8, e2 -> ea, and eb is used for self-modifying code at 5465. This is the low byte of an ORA instruction, so eb is not directly related to the screen memory.

5400 L5400 lda L00e6

5402       sta L00e8

5404       lda L00e2

5406       sta L00ea

5408       lda L00eb

540a       sta L5465

540d L540d jsr L5430

5410       lda #$28

5412       sec

5413       sbc L00e8

5415       sec

5416       sbc L00e8

5418       clc

5419       adc L00e0

541b       sta L00e0

541d       lda L00e1

541f       adc #$00

5421       sta L00e1

5423       lda L00e8

5425       sta L00e6

5427       lda L00ea

5429       sta L00e2

542b       dec L00e9

542d       bne L540d

542f       rts

5430 L5430 lda #$08

5432       sta L00e5

5434       ldy #$00

5436       lda (L00e3),y

5438       pha

5439 L5439 pla

543a       asl a

543b       pha

543c       bcs L5460

543e L543e inc L00e2

5440       lda L00e2

5442       and #$03

5444       sta L00e2

5446       cmp #$00

5448       bne L5450

544a       inc L00e0

544c       bne L5450

544e       inc L00e1

5450 L5450 dec L00e5

5452       bne L5439

5454       pla

5455       inc L00e3

5457       bne L545b

5459       inc L00e4

545b L545b dec L00e6

545d       bne L5430

545f       rts

5460 L5460 ldx L00e2

5462       lda (L00e0),y

5464 L5464 ora L5470,x

5467       sta (L00e0),y

5469       jmp L543e

The address 5470 is now looking like pixel data for each of the colors, since it is or’d with what is probably the screen data referenced by ($e0).

5470 L5470 .byte 40100401 ; pixel data for color 1

5474       .byte 80200802 ; pixel data for color 2

5478       .byte c0300c03 ; pixel data for color 3

547c       .byte 00000000 ; pixel data for color 0 (for completeness?)

So, e0 is the destination for storing pixel data. It’s initially set to $700f back in the setup routine at 5644. The ORA at 5464 uses the X register as an index, using the value in e2. This value is initially set to 3 at the calling code at 55fa, but every time through the loop at 543e it increments this value but caps it at 3 – when it exceeds 3 it increments the e0 destination address.

E0        dest screen lo byte

E1        screen hi byte

E2        pixel x location in byte?

E3        source bitmap lo

E4        source bitmap hi

E6        ?

E9        ?

EB        flag to select color of pixel; pointer to self-modifying code pixel array

OOH, is this a run length encoding algorithm???

It seems that the code at 543e is moving in the positive X direction by first changing the pixel within the byte, then advancing the byte.

543e L543e inc L00e2      ; move a pixel to the right

5440       lda L00e2      ; make sure pixel stays within byte

5442       and #$03

5444       sta L00e2

5446       cmp #$00       ; check if we need to advance to the next byte

5448       bne L5450      ; nope, still same byte

544a       inc L00e0      ; yep, advance screen pointer

544c       bne L5450

544e       inc L00e1

5450 L5450 dec L00e5

5452       bne L5439

5454       pla

5455       inc L00e3

5457       bne L545b

5459       inc L00e4

545b L545b dec L00e6

545d       bne L5430

545f       rts

This does lead immediately to the end condition, so decrementing e5 until it’s zero falls through the check at 5452. But then we’re incrementing the 2 byte value at e3 and decrementing e6… but I don’t understand what those do yet. The code just before this at 5439 (that gets called when the decrement at 5450 is greater than zero) is what calls the drawing code:

5439 L5439 pla

543a       asl a          ; shift bit to be drawn

543b       pha

543c       bcs L5460      ; draw pixel if carry set

It calls the drawing code when the carry is set, meaning when the shift of the A register results in a number greater than 255. So this is just drawing or not drawing depending on if a bit is set or not.

Ok, e3 is the source address for the pixel data, and e6 looks like the number of bytes to use from the source address? The code immediately before 5439 looks like it always sets e5 to 8, so we’re drawing 8 pixels – which makes sense if we’re turning each bit of a byte into a pixel. So e5 is just a loop counter on the number of pixels that we’ve drawn.

5430 L5430 lda #$08       ; loop through each bit of the source byte

5432       sta L00e5

5434       ldy #$00

5436       lda (L00e3),y  ; load the image byte

5438       pha

Now the remainder of the drawing code is more clear. It’s drawing the number of pixels of a source line of bytes. Here’s the fully commented section of code that draws a line of pixels. This makes it clear that e6 contains the number of bytes that will be drawn in a line.

5430 L5430 lda #$08       ; loop through each bit of the source byte

5432       sta L00e5

5434       ldy #$00

5436       lda (L00e3),y  ; load the image byte

5438       pha

5439 L5439 pla

543a       asl a          ; shift bit to be drawn

543b       pha

543c       bcs L5460      ; draw pixel if carry set

543e L543e inc L00e2      ; move a pixel to the right

5440       lda L00e2      ; make sure pixel stays within byte

5442       and #$03

5444       sta L00e2

5446       cmp #$00       ; check if we need to advance to the next byte

5448       bne L5450      ; nope, still same byte

544a       inc L00e0      ; yep, advance screen pointer

544c       bne L5450

544e       inc L00e1

5450 L5450 dec L00e5      ; number of bits remaining

5452       bne L5439

5454       pla            ; clean up stack

5455       inc L00e3      ; advance image source address

5457       bne L545b

5459       inc L00e4

545b L545b dec L00e6      ; repeat for specified number of columns

545d       bne L5430

545f       rts

5460 L5460 ldx L00e2      ; load mask for pixel position

5462       lda (L00e0),y  ; or the pixel to the screen

5464 L5464 ora L5470,x

5467       sta (L00e0),y

5469       jmp L543e      ; continue to next pixel

Now it’s just the setup code to figure out.

5400 L5400 lda L00e6

5402       sta L00e8

5404       lda L00e2

5406       sta L00ea

5408       lda L00eb

540a       sta L5465

540d L540d jsr L5430

5410       lda #$28

5412       sec

5413       sbc L00e8

5415       sec

5416       sbc L00e8

5418       clc

5419       adc L00e0

541b       sta L00e0

541d       lda L00e1

541f       adc #$00

5421       sta L00e1

5423       lda L00e8

5425       sta L00e6

5427       lda L00ea

5429       sta L00e2

542b       dec L00e9

542d       bne L540d

542f       rts

It’s still not clear to me how the height of the image is specified. The width in bytes is specified in e6, so perhaps e9 is the height as it’s the only value that’s decremented, and it seems like that’s the only way the subroutine returns, through the BNE check at 542d.

Let’s see, if we make that assumption, the e9 is the height, we should be able to look at the data and visualize the bitmap pattern. In the call to 5400 at the 5638, it sets the source address to 54e0, the width in e6 to 5 and the height in e9 to c. And sure enough, we can see the data:

Therefore, e9 is the height we know all the input parameters:

E0        dest screen lo byte

E1        screen hi byte

E2        pixel x location within screen byte

E3        source bitmap lo

E4        source bitmap hi

E6        width of pixmap in bytes

E9        height of pixmap in lines

EB        flag to select color of pixel; pointer to self-modifying code pixel array

The final fully commented code:

5400 L5400 lda L00e6      ; width of image in bytes

5402       sta L00e8      ; save for later

5404       lda L00e2      ; pixel start within byte, 0-3

5406       sta L00ea

5408       lda L00eb      ; color flag: $70 for color 1, $74 for color 2, $78 for color 3

540a       sta L5465

540d L540d jsr L5430      ; draw first line

5410       lda #$28       ; each byte draws 8 pixels, so 2 bytes

5412       sec            ; .. of screen ram for each byte

5413       sbc L00e8      ; .. of source pixels

5415       sec

5416       sbc L00e8

5418       clc            ; add 40 - 2xsource width to find next line

5419       adc L00e0

541b       sta L00e0

541d       lda L00e1

541f       adc #$00

5421       sta L00e1

5423       lda L00e8      ; reload counter with source width in bytes

5425       sta L00e6

5427       lda L00ea      ; reload pixel start within byte

5429       sta L00e2

542b       dec L00e9      ; number of lines in image

542d       bne L540d

542f       rts

5430 L5430 lda #$08       ; loop through each bit of the source byte

5432       sta L00e5

5434       ldy #$00

5436       lda (L00e3),y  ; load the image byte

5438       pha            ; save it on the stack rather than temp var

5439 L5439 pla            ; get current source byte

543a       asl a          ; shift bit to be drawn

543b       pha            ; save result for next time through loop

543c       bcs L5460      ; draw pixel if carry set

543e L543e inc L00e2      ; move a pixel to the right

5440       lda L00e2      ; make sure pixel stays within byte

5442       and #$03

5444       sta L00e2

5446       cmp #$00       ; check if we need to advance to the next byte

5448       bne L5450      ; nope, still same byte

544a       inc L00e0      ; yep, advance screen pointer

544c       bne L5450

544e       inc L00e1

5450 L5450 dec L00e5      ; number of bits remaining

5452       bne L5439

5454       pla            ; clean up stack

5455       inc L00e3      ; advance image source address

5457       bne L545b

5459       inc L00e4

545b L545b dec L00e6      ; repeat for specified number of columns

545d       bne L5430

545f       rts

5460 L5460 ldx L00e2      ; load mask for pixel position

5462       lda (L00e0),y  ; or the pixel to the screen

5464 L5464 ora L5470,x    ; self-modifying code lo byte of pixel within byte

5467       sta (L00e0),y

5469       jmp L543e      ; continue to next pixel

This first call to 5400 from 5638 prints EPYX. The next call to 5400 is at 5659 that prints “presents”, which is 5 bytes wide and 9 lines tall:

The next call is at 5bb3, and uses a JMP so that the return from 5400 is used as the return to 5bb3. It’s really called from the title screen setup at

57bf       lda #$b4       ; get Jumpman graphic drawn in marquee

57c1       sta L00e0      ; screen at 77b4

57c3       lda #$77

57c5       sta L00e1

57c7       lda #$08       ; 8 bytes wide

57c9       sta L00e6

57cb       lda #$80       ; source pixmap at 5480

57cd       sta L00e3

57cf       lda #$54

57d1       sta L00e4

57d3       lda #$0c       ; 12 lines high

57d5       sta L00e9

57d7       lda #$01       ; x offset of 1

57d9       sta L00e2

57db       jsr L5ba0

Where 5ba0 is a small routine that copies the copyright notice to the bottom of the screen and then JMPs to 5400:

5ba0 L5ba0 ldx #$28       ; display copyright message

5ba2 L5ba2 lda L26d7,x

5ba5       clc

5ba6       adc #$20

5ba8       sta L7de7,x

5bab       dex

5bac       bne L5ba2

5bae       lda #$02       ; change display list line in score area

5bb0       sta L3c5e      ;  to mode 2

5bb3       jmp L5400      ; draw the jumpman image on the marquee

The Jumpman data is at location 5480 and its bitmap looks like this:

Appendix 4: Level Load Process

The process from the main menu selection to the user playing a level begins once Start is pressed. The code at 24c9 begins the level load process by using the value of 2603 as an index into the sector start table, initializing a few variables, and jumping to a00.

24dd       ldy L2603

24e0       lda L2507,y

24e3       sta L30ee

24e6       lda L250d,y

24e9       sta L30ef

24ec       lda #$00

24ee       sta L51c9

24f1       sta L4106

24f4       jsr L3780

24f7       jsr L2640

24fa       lda #$10

24fc       sta L51c7

24ff       lda #$27

2501       sta L51c8

2504       jmp L0a00

The code at a00 displays the “Number of players?” screen and waits for a keypress. A strange value of $34 is stored in the Jumpman speed parameter, perhaps used as a flag later (we’ll see), and JMPs to 261a.

0a00       jsr L0fd5

0fd5 L0fd5 lda #$00

0fd7       sta COLOR4

0fda       sta L4cfc

0fdd       jmp L0df6

0df6 L0df6 lda #$01

0df8       sta L4106

0dfb       jsr L3100

3100 L3100 ldy #$1b       ; set gameplay VBI

3102       ldx #$31

3104       lda #$07

3106       jsr SETVBV

3109       lda #$00

310b       sta L3018      ; set VBI index

310e       sta L301c      ; counter 4

3111       sta L301d      ; counter 5

3114       sta L301e      ; counter 6

3117       sta L301f      ; inside VBI flag?

311a       rts

0dfe       rts

0a03       lda #$aa

0a05       sta COLOR0

0a08       lda #$d0

0a0a       sta SDLSTL

0a0d       lda #$0d

0a0f       sta SDLSTH

0a12       sta L30ff

0a15 L0a15 lda L30ff      ; wait for keypress

0a18       cmp #$04

0a1a       bcs L0a15

0a1c       sta L0ed0

0a1f       lda #$03

0a21       sta L30ff

0a24       lda #$00

0a26       sta COLOR0

0a29       sta COLPF0

0a2c       nop

0a2d       nop

0a2e       lda #$34

0a30       sta L30fe

0a33       jmp L261a

261a zeros the bonus values, and saves the game option from 2603 into 55f0, disables the START key, and jumps to 4d00.

261a       lda #$00

261c       sta L4cfd

261f       sta L4cfe

2622       sta L4cff

2625       sta L0041

2627       lda L2603

262a       sta L5ff0

262d       lda #$11

262f       sta L4632

2632       lda L0ed0

2635       sta L0ed2

2638       lda #$00

263a       sta L4106

263d       jmp L4d00

4d00 initializes the number of lives left, zeros out the score, calls a subroutine at 22d4 (which looks like it copies 11 bytes from 55d8 to ed4, 4 times (one time for each player?)), sets the keyboard handler to 4d2a, and continues on with a JMP to 4d70.

4d00       lda #$06       ; initialize number of lives left

4d02       sta L30f0

4d05       ldx #$08

4d07       lda #$00

4d09 L4d09 sta L30f4,x    ; clear score stuff

4d0c       dex

4d0d       bne L4d09

4d0f       lda #$02

4d11       sta L30b6

4d14       jsr L22d4

22d4       lda #$04

22d6       sta L05ff

22d9       ldx #$00

22db L22db ldy #$00

22dd L22dd lda L55d8,y

22e0       sta L0ed4,x

22e3       inx

22e4       iny

22e5       cpy #$0b

22e7       bne L22dd

22e9       dec L05ff

22ec       bne L22db

22ee       rts

4d17       lda #$2a

4d19       sta VKEYBD

4d1c       lda #$4d

4d1e       sta VKEYBD+1

4d21       lda #$07

4d23       sta L30f9

4d26       jmp L4d70

4d70 does … something, then JMPs to 4460

4d70       lda #$00

4d72       sta L0ed1

4d75       sta L0bfe

4d78       lda #$11

4d7a       sta L0ebc

4d7d       jmp L4460

4460 sets lm_alive to 2 (super dead), restores the vertical blank routines (?) and calls the subroutine fe0:

4460       lda #$02

4462       sta L30bd

4465       ldx #$e4

4467       ldy #$62

4469       lda #$07d

446b       jsr SETVBV

446e       lda #$00

4470       sta L30f4

4473       jsr L0fe0

0fe0       jsr L4540

The code at fe0 calls 4540

4540       jsr L4400

4540 calls 4400, which is a straightforward routine using the sector numbers in 30ee and 30ef to call the SIO handler to load the 16 sectors into memory. An error causes the routine to reset the stack and start the arcade attract mode via a JMP to 23eb.

Returning from 4540 means the level has loaded successfully. Copies of 2850 and 2851 are stored at ffe and fff, the uses of which are unknown at this point. Some levels like Gunfighter start their game loop at 2850, others have zeros at this location, still others have ff 27 or ff 00 at these locations.

0fe3       lda L2850

0fe6       sta L0ffe

0fe9       lda L2851

0fec       sta L0fff

0fef       rts

The code continues following this subroutine at 4543 performing what looks like a sanity check on the data, where the level load is aborted and the user is sent back to the start screen if the byte at 2beb is not $ff. Perhaps this was a test to make sure the Jumpman disk was still inserted? If this check passes, the level name is copied into screen ram. Unsure what the byte 2bea is used for the HSCROL offset.

4543       lda L2beb

4546       cmp #$ff

4548       bne L455c

454a       lda L2bea

454d       sta HSCROL

4550       ldx #$14

4552 L4552 lda L2beb,x

4555       sta L7dfd,x

4558       dex

4559       bne L4552

455b       rts

455c L455c ldx #$ff

455e       txs

455f       jmp (L4102)

The routine at 4460 continues at 4476:

4476       ldx #$06

4478       jsr L47b0

initializing the vertical blank routines from the level by calling the subroutine at 47b0, but maybe only the first 3 VBI routines, 2802, 2804, and 2806.

47b0       lda L2801,x

47b3       sta L3086,x

47b6       lda L2809,x

47b9       sta L30b6,x

47bc       dex

47bd       bne L47b0

47bf       lda L2831

47c2       sta L0700

47c5       rts

Back to the routine at 4460, the remainder of this routine initializes page 30 with level data from the 2800-2850 range.

447b       lda RANDOM     ; random level title color

447e       and #$f0

4480       ora #$08       ; minimum luminance of 8

4482       sta L2831      ; store in color3, inverse lowercase color

4485       nop

4486       nop

4487       ldx #$08

4489 L4489 lda L280f,x

448c       sta L30c2,x

448f       lda #$00

4491       sta L4967,x

4494       dex

4495       bne L4489

4497       ldx #$12

4499 L4499 lda L2817,x

449c       sta L30cd,x

449f       dex

44a0       bne L4499

44a2       ldx #$09

44a4 L44a4 lda L2829,x

44a7       sta L30e4,x

44aa       dex

44ab       bne L44a4

44ad       lda #$00

44af       sta AUDCTL

44b2       sta L30c0

44b5       sta L30c1

44b8       sta L30c2

44bb       sta L30cd

44be       sta L30f8

44c1       lda #$08

44c3       sta L30be

44c6       lda #$01

44c8       sta L30cb

44cb       sta L3056

44ce       sta L3057

44d1       sta L3074      ; Jumpman image

44d4       sta L3075

44d7       jsr L3820

3820 L3820 lda #$00       ; Set gameplay display list

3822       sta L0058

3824       sta SDLSTL

3827       lda #$3c

3829       sta SDLSTH

382c       lda #$70

382e       sta L0059

3830       lda #$07

3832       sta L0057

3834       jsr L3410

3410 sets PMBASE, SDMCTL, GRACTL, GPRIOR, and SIZEMx and SIZEPx registers from the values on page 30.

3837       jsr L3451

3451 is a general purpose player/missle memory clear routine using some self-modifying code for destination addresses and boundary checks. It moves all players and missiles offscreen (x coord set to zero) and then clears PMBASE: 5 x 256 bytes for single line resolution, or 5 x 128 bytes for double line resolution. It returns execution to 383a.

383a       jsr L3300

3300 L3300 ldx #$10

3302 L3302 lda L330b,x

3305       sta L034f,x

3308       dex

3309       bne L3302

330b L330b rts

330c       .byte 09010b0100000000

3314       .byte 0000080000000000

383d       nop

383e       nop

383f       nop

3840       lda #$65        ; set DLI trigger

3842       sta L30e2       ; for score area color changes

3845       lda #$3c

3847       sta L30e3

384a       nop

384b       nop

384c       nop

384d       nop

384e       nop

384f       nop

3850       nop

3851       nop

3852       nop

3853       nop

3854       nop

3855       nop

3856       ldx #$09

3858 L3858 lda L30e4,x    ; copy color registers from page 30

385b       sta L02ff,x

385e       dex

385f       bne L3858

3861       lda #$3e       ; set character set to 3e00

3863       sta CHBAS

3866       rts

44da       lda L2837      ; load level definition table

44dd       sta L3042

44e0       lda L2838

44e3       sta L3043

44e6       jsr L0be0

Be0 is something strange. Bff seems to be zero during normal operation, so jumps to fc0. If it’s not, it pulls the return address off the stack and jumps back into the continuation of the subroutine at 44e9, essentially skipping over the JSR to 5590.

0be0       lda L0bff

0be3       cmp #$00

0be5       bne L0bea

0be7       jmp L0fc0

0bea L0bea lda #$00

0bec       sta L0bff

0bef       pla

0bf0       pla

0bf1       jmp L44ec

Normal operation continues with fc0, loading the level number into screen memory at 4636 and continuing to 4ca0. This section initializes some unknown variables and sets what appears to be a temporary DLI that operates during the level scroll. It then calls 3820 again – it’s already been called once before in this initialization process. Not sure why it’s called again, but that means it sets the gameplay display list, DLI trigger for gameplay, clears PMBASE areas, sets GRACTL & related stuff, and copies color registers from the level area.

0fc0 L0fc0 lda L2800

0fc3       sta L4636

0fc6       lda L2801

0fc9       sta L4637

0fcc       jmp L4ca0

4ca0       lda #$cd

4ca2       sta L3c03

4ca5       lda #$4d

4ca7       sta L4cf7

4caa       lda #$03

4cac       sta L00f0

4cae       lda #$3c

4cb0       sta L00f1

4cb2       lda #$cd       ; sets temporary DLI

4cb4       sta VDSLST

4cb7       lda #$4c

4cb9       sta L0201

4cbc       lda #$c0

4cbe       sta NMIEN

4cc1       jsr L3820      ; Not sure why this is called here

4cc4       lda #$02

4cc6       sta L30bd

4cc9       jsr L50d8      ; call level start music

The level start music is a straight-forward subroutine that loads two music lists and starts them each with calls to 32b0.

50d8 L50d8 lda #$00       ; play level start music

50da       sta AUDCTL

50dd       sta L3040

50e0       lda #$03

50e2       sta SSKCTL

50e5       lda #$51

50e7       sta L3041

50ea       lda #$10

50ec       jsr L32b0

50ef       lda #$4b

50f1       sta L3040

50f4       lda #$51

50f6       sta L3041

50f9       lda #$10

50fb       jsr L32b0

50fe       rts

4ccc       rts

44e9       jsr L5590

5590 L5590 jsr L5000

5000 L5000 lda #$10

5002       sta L0059

5004       sta L3806

5007       jsr L3800

Storing $10 into 3806 and the subsequent call to 3800 clears 4k of memory at 1000, so 1000-1fff are cleared. This range is used as working memory by the level construction code before it’s scrolled into view.

500a       jsr L331c

331c is the level construction, using the level definition table pointed to by the address at 3042 (mirrors of the level address at 2837) to build the structure of ladders, girders, ropes, etc. When this subroutine returns, the level is constructed in memory 1000-1fff and ready to be scrolled into view. I may reverse engineer that routine in a later appendix, but for now we’ll leave it as a black box.

5000 L5000 lda #$10       ; temporarily sets size of

5002       sta L0059      ;  page clear routine

5004       sta L3806      ; self-modifying code at 3806

5007       jsr L3800

500a       jsr L331c

500d       lda #$58

500f       sta L00e0      ; number of lines to scroll

5011       lda #$98       ; 1d98 is addr of lowest line of

5013       sta L00e1      ; constructed screen

5015       lda #$1d

5017       sta L00e2

5019       ldx #$50       ; sets VBI to 50cb, used during

501b       ldy #$cb       ; level scroll to implement

501d       lda #$07       ; a counter and play music

501f       jsr L516c

516c L516c jsr SETVBV

516f       lda #$c0

5171       sta NMIEN

5174       rts

5022 L5022 jsr L503c

5025       lda L00e0

5027       cmp #$00

5029       bne L5022

503c is the routine that scrolls the constructed level onto the main display memory one line at a time. It does not use hardware scrolling, it copies the screen down line by line, then inserts a new line from the working memory onto the top of the screen. $e0 is used as the counter, when it reaches zero, the level has been scrolled into place.

502b       ldx #$e4       ; reset system VBI

502d       ldy #$62

502f       lda #$07

5031       jsr SETVBV

5034       lda #$70       ; resets page clear routine size

5036       sta L0059

5038       sta L3806

503b       rts

Returning back to 5593, this copies the loaded level from 2800-2fff to 1800-1fff, saving the level data so the level can be rebuilt in a multi-player game.

5593       lda #$18

5595       sta L55a6

5598       lda #$28

559a       sta L55a3

559d       ldx #$08

559f L559f ldy #$00

55a1 L55a1 lda Lff00,y

55a4 L55a4 sta Lff00,y

55a7       iny

55a8       bne L55a1

55aa       inc L55a6

55ad       inc L55a3

55b0       dex

55b1       bne L559f

55b3       lda L0700

55b6       sta L1831

55b9       jmp L55e4

55e4 L55e4 lda L0ed0

55e7       cmp #$00

55e9       bne L55f1

55eb       lda #$31

55ed       sta L4632

55f0       rts

If there’s only one player, the code returns to continue executing at 44ec, otherwise it hits this JMP:

55f1 L55f1 jmp L0ad8

Which handles reloading the level for multiple players. We’re going to continue the trace as if this is a single player run, and therefore the code continues execution at 44ec.

44ec       lda #$00

44ee       sta L30fc

44f1       sta L30fb

44f4       nop

44f5       sta SDLSTL

44f8       lda #$3c

44fa       sta SDLSTH

44fd       jsr L47c6

47c6 L47c6 lda L0700

47c9       sta L30ec

47cc       sta COLOR3

47cf       jsr L4600

4600 L4600 ldx #$28

4602 L4602 jmp L4570

4570 L4570 lda #$00

4572       sta HSCROL

4575       lda L4630,x

4578       jmp L4605

4605 L4605 sec

4606       sbc #$20

4608       sta L7de7,x

460b       dex

460c       bne L4602

460e       ldx L30f0

4611       ldy #$15

4613       inx

4614 L4614 dex

4615       beq L462a

4617       lda #$c1

4619       sta L7dea,y

461c       iny

461d       cpy #$1b

461f       bne L4614

4621       cpx #$01

4623       beq L462a

4625       lda #$cb

4627       sta L7dea,y

462a L462a jsr L46e9

46e9 L46e9 lda L30f5      ; display score

46ec       sta L30f1

46ef       lda L30f6

46f2       sta L30f2

46f5       lda L30f7

46f8       sta L30f3      ; 30f1-30f3 holds 3 byte number

46fb       lda #$f5

46fd       sta L00be

46ff       lda #$7d

4701       sta L00bf      ; $(be) holds screen addr

4703       jsr L5180

The routine at 5180 checks for an extra life and displays the score.

4706       rts

462d       jsr L4707

The routine at 4707 displays the bonus value.

4630 L4630 rts

47d2       rts

4500       lda L2800

4503       sta L4636

4506       lda L2801

4509       sta L4637

450c       lda L2808

450f       sta L308e

4512       lda L2809

4515       sta L308f

4518       lda #$03

451a       sta SKCTL

451d       lda L2842

4520       sta L30ee

4523       lda L2843

4526       sta L30ef

4529       lda L283b      ; load address of game loop

452c       sta L00b0

452e       lda L283c

4531       sta L00b1

4533       jmp (L00b0)    ; start level!

Appendix 5: Level Storage Version 2

Level storage in the original Jumpman consisted of 16 sectors on disk, loaded into memory at 2800-2fff. Most levels used less than half of the 2 kilobytes of data, while some seemed crammed into the space and could have used more. There was both wasted space and space limitations.

Level storage version 2 addresses both of these problems. There is no fixed size, and the maximum allowed size is increased to 4 kilobytes (and beyond when compression is implemented). Levels can now occupy as few as 2 sectors on disk, or as many as 33.

This new design splits the level into 2 storage areas: the header and the block data. The block data is further split into Block A and Block B. The header is 1 sector (128 bytes), and the block data may use up to 32 additional sectors (4 kilobytes). The header is loaded at 2800 in memory, and (after processing) Block A is placed at 2880-2fff and Block B at a800-bfff.

The loading process begins by reading the sector containing the header at 2800 in memory. The block data is not loaded until after the header is parsed. The first two bytes of the header have been modified to indicate the new version, but the rest of the data from 2802-284f is unchanged from the original. The header contains some new information about how to parse the remaining sectors, and the level title has been moved into the header so it does not split the memory in Block A. The header sector is always uncompressed; in future revisions the block data may be compressed.

The sectors making up the block data are loaded at the scratch area of 1000-1fff and then distributed to the two blocks. The number of bytes stored in Block A is indicated by the word at 2860 (length_a) and the number of bytes stored in Block B is indicated at 2862 (length_b). Block B’s destination address is pointed to by the word at 2864, but note that for version 2 this address must be set at a800. The data for Block A starts at 1000, and Block B data starts immediately after Block A data, i.e. 1000 + length_a. If compression is used, length_a and length_b indicate the lengths of the compressed data.

Block A is the smaller of the two blocks and is designed to hold the level definition table and harvest table. Because Block A must fit into the range 2880-2fff, it is at most $780 bytes in length. This means that it has a maximum size of 15 sectors (uncompressed), but may be shorter.

Block B supplies a larger contiguous range of memory, and as such is designed for custom code and graphics. It must fit in the range a800-bfff: $1800 bytes or 6k of memory.

The block data must fit in a maximum of 32 sectors, meaning Block B’s maximum size is dependent on the amount used in Block A. Note that Block A does not have to end on a sector boundary. Uncompressed, Block B will be less than 4k; using compression can expand the uncompressed data toward the 6K limit. (6k is the equivalent of 48 uncompressed sectors, or 3 times the size of a level in Jumpman 1.)

Compression is reserved for future use, and currently compression_a and compression_b must be zero.

2800:                $80 indicates version 2

2801:                level_num_bcd: Level number in BCD

2802-284f:        as original

2850-285f:        reserved

2860,2861:        length_a: number of bytes in Block A (to load at 2880), low & high bytes

2862,2863:        length_b: number of bytes in Block B (to load at destination address), low & high bytes

2864,2865:        dest_b: destination address (always 00 a8 for level tester image)

2866:                compression_a: compression type for Block A (0 = uncompressed)

2867:                compression_b: compression type for Block B (0 = uncompressed)

2868:                num_data_sectors: number of sectors for Block A and Block B data

2869:                reserved

286a:                0 (placeholder for 2bea, in some levels is 4)

286b:                ff (level storage magic byte, formerly at 2beb)

288c-287f:        level title

2880-2fff:        level definition and harvest table

A800-bfff:        custom code

Creating Easy Does It v2 by hand

Creating the initial test level required hex editing. Easy Does It was chosen because there’s no custom code and the gameloop does not have any absolute addresses within its code.

Item

Orgiinal Address

New Address

Level Definition Table

2c00

2880

Harvest Table

2ce9

2969

Paint Target Start

2d3e

29be

Paint Target 1

2d54

29d4

Paint Target 2

2d64

29e4

Paint Target 3

2d3e

29be

Paint Target 4

2d49

29c9

Paint Target End

2d73

29f3

Gameloop Start

2880

29f4

Gameloop End

28a0

2a15

The level definition table was moved to 2880, the harvest table immediately afterward resulting in the address 2969, and the paint target table immediately after that at 29be. Pointers within the harvest table had to be adjusted to point to the relocated paint target table entries. The gameloop starts immediately after the paint target at 29f4. The changes to the header are:

2837,2838:        80 28        level_definition

283b,283c:        00 a8        gameloop

284e,284f:        69 29        harvest_table

2860,2861:        f4 01        length_a

2862,2863:        21 00        length_b

2864,2865:        00 a8        dest_b (must be a800)

2866:                00        compression_a (not used)

2867:                00        compression_b (not used)

2868:                04        num_data_sectors: (4 sectors for combined Block A and Block B raw data)

Appendix 6: Creating the Level Builder Test Image

It is useful to have a minimal bootable image to test a level design in an emulator without going through the time and extra steps of selecting a particular level from the main menu. This level tester image sends the user directly to playtesting the level as soon as this disk is booted. Additionally, pressing OPTION during play sends the user to a replay menu that allows speed selection and number of lives. This allows multiple gameplay tests without having to reboot the emulator.

The disk image itself uses all of the Jumpman code, plus custom code needed to bypass the menu, plus the level data. The 4 blocks of code from the game (from the sector map above) are:

Sector Range

Contents

Address Range

548-559

Game code

0a00-0fff

560-575

Game code

2000-27ff

592-693

Game code

3000-62ff

704-719

Game code

6800-6fff


along with the level to be tested. The level tester image can work with both versions of the level storage format, the original that stores the level at 2800-2fff, and
Version 2 that also has storage at a800-bfff. Note that for replay ability to work with levels that are using Version 2, the size of Block B is limited to 4k, meaning the loaded data must be restricted to use a800-b7ff. Further note that the original Jumpman runs in 32k, so the top 16k of RAM is untouched by the original code and is thus a convenient place to store any enhancements.

The layout of the reference images are (note, all numbers are in hexadecimal):

Load addr in memory

End addr

Num bytes

Offset in XEX

Offset in ATR

Description

-

-

-

-

0

ATR Header

700

880

180

-

10

3 sector boot loader

2800

2fff

800

6

196

Level storage v1 or

Header + Block A for v2

a00

aff

600

80a

99a

Code at 0a00

2000

2800

800

e0e

f9e

Code at 2000

3000

6300

3300

1612

17a2

Code at 3000

6800

7000

800

4916

4aa6

Code at 6800

02e0

02e1

2

511a

52aa

Run address

8000

8fff

1000

5120

52b0

Level tester code

a800

b7ff

1000

6124

62b4

Block B data

The level builder test image distributed when using https://www.savetz.com/jumpman is the ATR version. The XEX version doesn’t have a loading screen, so it is repackaged into the ATR image that does provide this. The ATR is just a 16 byte header and a 3 sector boot loader in front of the XEX. The boot loader parses the XEX structure, loads each segment into the appropriate area of memory, and runs the code.

Level Storage Version 1 (Classic Jumpman levels)

The level tester understands both the original level storage format and the new version 2. If the original format is used, the level editor will place the level data at offset $196 in the ATR image.

Level Storage Version 2

If version 2 is used, the new header format must be used for the data stored in Block A. There are two cases: Block A only, and Block A plus Block B. There is no case where only Block B is used. Block A will store at least the level definition table and the harvest table.

For code that uses the version 2 header but only Block A, the level data is again placed at offset $196 in the ATR image, and the header must be set to:

2800:                $80 (indicates version 2)

2801:                xx (encode the level number in BCD)

2802-284f:        as original classic Jumpman header

2850-285f:        all zeros (reserved for future expansion)

2860,2861:        low & high bytes of length of data used starting at 2880

2862,2863:        0

2864,2865:        0

2866:                0

2867:                0

2868:                0

2869:                0

286a:                0 (placeholder for 2bea, in some levels is 4)

286b:                ff (level storage magic byte, formerly at 2beb)

288c-287f:        level title

For code that uses the version 2 header and both Block A and Block B, the data for Block A is placed at offset $196 and the data for Block B is placed at offset $62b4 in the ATR image. The following changes to the above header are also needed:

2862,2863:        low & high bytes of length of data used starting at a800

2864,2865:        00 a8 (specifying destination address of a800)