1982 · Commodore and HAL Laboratory

Jupiter Lander

This minisite was curated by air.

Five kilobytes of code, an unexpected difficulty curve, and surprising rules for scoring. Let's play around with the values.

01 · The screen

One frame, rebuilt

This is not a screenshot. Every pixel below is computed from three pieces of memory: the 1000 bytes of screen, the 1000 nibbles of colour, and the character set the game installs at $3800. The lander over it is sprite 1, drawn from its own 63 bytes.

rock, colour 2 gauge, colour 5 safe speed, colour 7 lander sprite, colour 3

320×200 · 40 columns by 25 rows of 8×8 characters. The terrain painter writes 38 of those columns. The last two belong to the speed gauge, and the bottom two rows to the score and the fuel bar.

02 · The cast

Sixteen sprite shapes

The sixteen sprite shapes are stored in the second half of the 2KB character set block, addressed through the sprite pointers.

Click any shape to see it large. Slots $FC to $FF are empty; they are the tail of the 2 KB block.

The explosion animation

Hitting rock does not swap the ship for an explosion. It adds one to the sprite pointer, seven times, and cycles the sprite colour between frames.

$E46F lda #$07 $E471 sta boom_frame $E473 inc $07F9 ; the sprite pointer $E481 jsr flash_explosion_colour $E484 dec boom_frame $E486 bne $E473

Thrusters: compose three sprites

The flames are not drawn into the hull. They are sprites 2 and 3, sitting at exactly the same coordinates as sprite 1 and switched on only while a thruster is held. Sprite 2 alternates between two shapes on every update, which is what makes the main engine flicker.

Sprite 1 is cyan, sprite 2 red, sprite 3 orange, and none of those colours is ever changed except during an explosion. Double size is the whole of the game's zoom: $D017 and $D01D both get $0E.

03 · Controls

Three keys is all you need

The instruction screen prints LEFT-A and D-RIGHT. Those labels name the thruster that fires, not the direction you travel. Firing the left thruster pushes you right.

A
F1
D
joystick 1: left  ·  fire  ·  right
Hold a key, or use the arrow keys and space.

The arrow shows the acceleration the held key applies to the ship this pass. Gravity is left out here; it is added separately and always.

One bit for two devices

F1 and the port 1 fire button are the same bit of the same register. Keyboard row 0 is selected by writing $FE to $DC00, and F1 then reads as bit 4 of $DC01 . That bit is also where the joystick's fire button lands. The game never has to choose between them.

$EB9E lda #$fe $EBA0 sta $dc00 ; select keyboard row 0 $EBA3 lda $dc01 ; read it back $EBA6 cmp $dc01 ; twice, until two reads agree $EBA9 bne $EBA3 $EBAB and #$10 ; bit 4: F1, and fire

The joystick is read first, with every keyboard row driven high so no key can show through. Only if the stick is completely idle does the keyboard get scanned, and then only for A, D and F1. D is tested last, so holding A and D together thrusts right.

04 · The world

Four hand-drawn screens

Fly near a landing site and the view snaps to a close-up. Nothing about the terrain is scaled. The game holds four separate pictures and flying between zones swaps which one is painted. The only thing that changes size is the lander, doubled by the video chip.

Every view is a run-length stream of count-and-character pairs, laid out 38 cells to a row and stopping on a count of zero. 924 bytes for 3496 cells.

The badge in the rock

All four streams carry the same four glyphs, $0C to $0F, arranged as a two-by-two block. It is a Commodore badge cut into the cliff, and it is terrain data rather than an overlay: the painter has no idea it is drawing a logo.

Where the pads are

A pad is one line in the world. The test for standing on one compares both bytes of the ship's Y position for equality, and the X range is an ordinary bounds check.

PadWorld YWorld XMultiplier
Left$016A$00B0 – $00E05
Middle plateau$009A$0160 – $01902
Right$017A$01E0 – $020010

Descend faster than one world unit per pass and the ship's Y can step over that line without ever equalling it. The game never sees a landing at all, and the ship dies on the rock a moment later.

All three numbers are placed to the pixel. Put the lander on a pad's line and run the same sprite-against-character test the video chip runs, and it is clear by a hair. Move it one world unit lower and the legs are inside the rock, on all three pads.

World Y relative to the padx5 padx2 padx10 pad
4 belowin rockin rockin rock
1 belowin rockin rockin rock
the pad's own lineclearclearclear
1 aboveclearclearclear

Run in this page against the decoded terrain and the sprite bitmap, not measured in the emulator. The pad line is the lowest position at which the lander does not touch.

05 · The flight model

Let's fly!

The physics is a handful of additions. What follows is the same arithmetic at the same rate with the same constants, over the terrain decoded from the game and against the same sprite-and-character collision test the video chip performs. Land inside a pad with a downward speed under 72 and it scores. Starting where the game starts you is as hard here as it is there, so three buttons will drop you in above a pad instead.

A and D for the side jets, space or F for the main engine. Or use the buttons.

Each pass: a side jet moves the X velocity by 6 and costs 30 fuel; the main engine moves the Y velocity by −12 and costs 111; then gravity is added whatever you did. Velocities are in 256ths of a world unit.

$E328 lda #$0c ; main thruster: 12 $E32A pha $E32B asl a ; x2 $E32C asl a ; x4 $E32D asl a ; x8 = 96 $E32E adc #$0f ; + 15 = 111, the fuel cost $E330 sta scratch $E332 lda fuel_lo ; tank -= 111 $E33F pla ; back to 12 $E342 lda ship_vy_lo ; velocity -= 12

The fuel cost and the thrust come out of the same constant. Twelve is shifted three places to make ninety-six, fifteen is added, and the original twelve is pulled back off the stack to use as the acceleration.

06 · The gauge

Secret: the landing gauge lets you go over - by one pixel

The instructions say the vertical velocity has to be inside the yellow area of the scale. There is one yellow cell. It covers downward speeds 0 to 63. The landing test accepts everything up to 71, and accepts every upward speed there is.

the scale the yellow cell lands SORRY NO BONUS

Sixteen character rows, eight needle positions inside each: 128 over the whole scale, one for every eight units of velocity. The needle is a black line cut out of the bar, glyph $18 plus the bottom three bits of the position, and the cell keeps the colour of its row, green or yellow, because the routine never writes colour memory. The strip beside the scale is not on the C64's screen: it is the landing test's verdict at every needle position. The inset is rows 11 to 13 at twelve times the size.

Set the velocity to 63, then 64. The needle leaves the yellow cell, and the landing still works: 64 through 71 all land. Then 71, then 72. The needle moves one pixel, inside the same cell, and the strip beside it turns from lands to SORRY NO BONUS. That pixel is the line between nine hundred points and a destroyed ship, and nothing on the scale marks it.

07 · Scoring

Secret: it's possible to land while going UP

One pad has open air beside it, which means it's possible to sideslip onto it and even push up into a negative speed.

The x2 close-up, view 2. The amber line is world Y $009A, the one line on which the pad test can pass, and the amber bracket is the range of X it accepts, $0160 to $0190. The whole manoeuvre is eight world units of sideways drift, so the inset shows the lander's feet and the pad's edge at eight times the size. Every pass shown here was replayed in the emulator with the game's control read redirected to this list of inputs; the positions matched to the byte, and the screen printed what the last step shows.

Each pass of the flight loop does four things in a fixed order: it asks the video chip whether the lander has touched rock, it tests the pad, it reads the controls and it moves the ship. The pad test is an exact match on the ship's world Y and a range check on its X, so the velocity that touchdown sees is the one that carried the ship onto the line in the previous pass. Arriving from above, that velocity is positive; a climbing arrival means the ship was below the line the pass before, which on the x5 and x10 pads is inside rock, and the collision test runs first. The x2 plateau is the exception: its flat top is 98 pixels wide with a 45° slope on either side, so a ship hovering on the pad's line just left of it is in clear air, and can drift right onto the pad with any velocity at all.

From above, the smallest speed you can land at is not one either. The main thruster changes the velocity by −12 and gravity by +4 on the first landing, so every velocity the ship can have is a multiple of 4 and the softest arrival is 4, which pays 760, shown as 7600. On the landings where gravity is 5 or 7 the steps are coprime with 12 and a speed of 1 is possible, worth 790. Where gravity is 6 the softest arrival is 6 and the best is 740. The 800 that a perfect stop would pay exists only on the x2 pad, sideways, at zero speed, where the multiplier turns it into 1600.

The bonus line the game draws on screen row 2, rendered from the same character set. Everything on screen is ten times the number the game holds, because the status line has a 0 painted on the end of it. The doubling for the x2 and x10 pads is eight-bit too and loses its carry, which is why a speed of −48 on the x2 pad pays nothing at all.

Every point counted out also puts 70 units back in the tank, one point and 70 units at a time, at about 21 milliseconds each. A good landing on the x10 pad therefore takes roughly seventeen seconds to pay out, and you watch the fuel bar grow the whole time.

08 · Progression

An uneven difficulty curve

Nothing else changes as the game goes on. The pads stay where they are, the fuel costs stay the same, the landing limit stays at 72. The only thing that gets harder is how fast you fall, and it is a sixteen-byte table.

Gravity at $E0DE, in 256ths of a world unit per pass per pass. The counter stops at sixteen, so the sixteenth landing is as hard as the game ever gets, and a pilot who survives fifty is flying the same game as one who survived sixteen.

The table does not climb steadily. It goes 4, 5, 4, 5, 6, 5, so the second landing is harder than the third, and the eleventh is harder than the twelfth. The difficulty steps back four times on its way up.

09 · Sound

Music: three tunes, stored as chip registers

There is no music driver. Each tune is a list of raw SID frequency words and a fixed gap between them, played by a loop that writes two registers and counts. The gate is opened once at the start and closed at the end, so a repeated note runs into the one before it instead of being struck again.

Frequency in hertz against time, from the stored words. A SID frequency of n is n × 985248 / 16777216 hertz on a PAL machine. Zero is a rest.

The thrusters and the explosion do not use this path at all. They gate voices 2 and 3 to noise directly, and the only difference between the main engine and a crash is the frequency: $0900 for the engine, $0700 for the bang.

10 · Quirks

Things a pilot would not know

Nothing is synchronised to the screen

No raster interrupt is enabled, and the timer interrupt is masked for the whole of a game. The game times itself by counting down a register ten times two hundred, which is about 10.3 milliseconds, and waits three of those per update. On an NTSC machine, with a faster clock, the whole game runs about four per cent quicker.

The interrupt exists to flash one line of text

It runs only during the attract sequence. Every twentieth tick it steps the colour of eighteen cells at $DBCA, which is exactly the eighteen characters of PUSH F1 TO START, and on every tick it checks whether F1 is down. That is all it does.

The last 255 units of fuel are unspendable

The tank is called empty when its high byte reaches zero, and that same test clears both thrust flags. Up to 255 units are still sitting in the low byte, and no thruster will ever touch them. It also means the counter can never underflow, which is presumably the point.

Running dry does not end the game

OUT OF FUEL appears, the engines stop, and the ship keeps falling. The game ends at the next impact, when the crash handler finds the tank at zero and there is nothing to charge the crash to.

A crash costs fuel, and the bill is capped

The impact speed shifted right by four comes off the fuel high byte. Above a speed of $01FF the calculation is clamped, so every very fast crash costs the same 7936 units. Hitting the ground at speed 512 and at speed 768 cost exactly the same.

What the counter actually holds

Four packed BCD digits, printed followed by a 0 that is part of the static template. The counter tops out at 9999, which reads as 99990, and a single point is worth ten on screen.

The perfect stop cannot happen

A pad is one world line reached from above, so the velocity the bonus subtracts is never zero: the softest arrival is 4 on the first landing and 1 where gravity is 5 or 7, for 7600 and 7900 on the x10 pad. The only way to be paid for zero, or for a climb, is sideways onto the x2 plateau, where the eight-bit arithmetic pays up to 2540. Section 07 steps through it.

The whole game is 49 bytes of state

Everything the game knows about itself lives in $02 to $32: two positions, two velocities, a tank, a score, a high score, a view number and a handful of flags. Nothing else in memory changes while you fly.

11 · The screens

Everything it can put in front of you

The thruster flames in two of these were forced on by patching two bytes. They are otherwise almost impossible to catch, because they are only lit while a key is down.

Title screen

The title, unpacked from a bitmap of two screen cells per byte. The wordmark under it is 21 one-off glyphs rather than letters.

Instruction screen

The instruction screen. All 382 characters of it sit in one block at $E874.

Wide view

The pulled-back view, with all three pads and the badge in the rock.

Close-up of the middle pad

The middle plateau at full size, with both flames lit and the sprite doubled.

Landing bonus

A perfect landing on the x10 pad. 800 points, shown as 8000.

A climbing landing on the x2 pad

The sideways landing of section 07, as the emulator printed it: a base of 127 whose tens digit is glyph $3C, because the printer expects one tens digit, times 2 in eight bits, 2540 on screen.

Out of fuel

The tank empty. The engines are already dead; the flight ends at the next impact.

Attract control demo

The attract sequence lighting each thruster in turn beside its label.

Game over

Game over, with a score of 3600 poked in as four BCD digits and a painted zero.

12 · Where it lives

Five kilobytes under the KERNAL

Reset switches the ROMs out through the processor port and never switches them back, so the game's own code occupies the addresses the KERNAL used to. All three hardware vectors point into it.

RangeBytesWhat
$0002 – $003249every variable the game has
$0400 – $07E71000screen, sprite pointers at $07F8
$3800 – $3BFF1024128 character glyphs
$3C00 – $3FFF102416 sprite shapes
$E000 – $E03655the video and sound chips' opening state
$E037 – $E8732109flight, scoring, explosion, attract
$E874 – $E9F1382the instruction screen, as text
$E9F2 – $EEB71222attract demo, interrupt, input, the panel, OUT OF FUEL
$EEB8 – $F253924four views, run-length encoded
$F254 – $F450509noise, and the title screen
$FFFA – $FFFF6NMI $E037, RESET $E037, IRQ $EB39

Addresses are from the Remember release of the cartridge, with both trainer options declined. The symbol map, the verified facts and the feature checklist sit beside this page.