Saturn Lander is a lunar-lander-style game set above a procedurally drawn Saturn landscape, where the player must guide a spacecraft to a safe touchdown using horizontal and vertical thrust controls. The terrain is rendered using a series of PLOT and DRAW commands that build a jagged mountain silhouette across the bottom of the screen, while a ring system and planet body are drawn in UDG characters loaded from DATA statements. Machine code routines are POKEd into memory starting at address 32000 via a hex-string decoder in lines 9502–9590, which converts ASCII hex pairs (with letters offset by 7) into raw bytes. The lander sprite is drawn and erased using OVER 1 mode, allowing flicker-free movement without explicit background restoration. Landing success is determined by checking POINT values beneath the craft’s feet and testing that horizontal velocity is within 0.25 and the altitude is low enough (SY≤5).
Program Analysis
Program Structure
The program is divided into several functional regions:
- Lines 3–16: REM header block with title and attribution.
- Lines 17–32: Scene drawing — Saturn’s surface terrain via PLOT/DRAW, and the ring/atmosphere belt.
- Lines 35–55: UDG-based planet and ring graphics printed at fixed screen positions.
- Lines 60–200: Main game loop — lander physics, sprite drawing, collision detection, and input handling.
- Lines 1000–1060: Crash sequence with flashing explosion, score display, and restart.
- Lines 2000–2030: Successful landing sequence with score calculation.
- Lines 8900–9070: Initialization routine — prints controls, loads UDG pixel data from DATA statements.
- Lines 9502–9700: Machine code loader — decodes hex strings from DATA and POKEs bytes into memory.
- Lines 9998–9999: SAVE and VERIFY the program with auto-run from line 8900.
Machine Code Usage
Four machine code routines are loaded into high memory via a hex-string decoder (lines 9502–9590). The DATA lines at 9610–9640 contain the start address followed by a hex string, terminated by "N" (next block) or "S" (stop). Each two-character hex pair is decoded: if a character’s CODE is above 64 (i.e., it is a letter A–F), 7 is subtracted to bridge the gap between ASCII digits and uppercase letters, yielding the correct nibble value. The formula 16*CODE A$ + CODE A$(2) - 816 reconstructs each byte.
The routines are placed at:
| Address | Called at | Purpose (inferred) |
|---|---|---|
| 32000 | Line 35 (RANDOMIZE loop) | Seed/timing utility |
| 32100 | — | Secondary utility |
| 32200 | Line 75 | Called each game loop iteration (likely delay/sync) |
| 32300 | Line 35 | Called in RND seed loop |
Line 35 uses LET L=USR 32300 inside a FOR/NEXT loop with a random iteration count to introduce timing randomness at startup. Line 75 calls USR 32200 each game loop cycle, likely as a frame-rate limiter or display sync routine.
UDG Graphics
Lines 9000–9070 load 128 bytes of pixel data into the UDG area starting at USR "A", defining 16 custom characters (UDGs \a through \p). These are used in line 51 to build the string r$ which paints Saturn’s rings and cloud bands across the screen at rows 4, 8, 12, 15, and 17. The ring pattern string is 60 characters wide — wider than the 32-column display — and a rotation trick in line 45 (R$(15 TO )+R$( TO 14)) shifts the string by 14 characters before printing the second copy, creating a visual offset between ring rows for a curved perspective effect.
Note that line 50 defines R$ using plain ASCII letters, which is immediately overwritten by line 51 using the actual UDG escape sequences. Line 50 appears to be a commented-out or leftover draft that has no runtime effect since line 51 redefines the same variable.
Sprite Drawing and Collision Detection
The lander sprite is drawn by subroutine at line 18, which uses PLOT to set six pixels forming a small spacecraft shape. The program uses OVER 1 (XOR mode) throughout: drawing the sprite a second time at the same position erases it. This is the standard ZX Spectrum flicker-free sprite technique — line 70 calls the subroutine to erase the old position, then lines 71 onward update coordinates and redraw.
Collision detection uses POINT to sample eight pixels around the lander’s bounding area (line 80). If any of these return 1 (lit pixel), the craft has hit an obstacle and the crash routine at line 1000 is triggered. Landing detection (line 65) checks three specific pixels just below the landing legs; if all three are set, the craft is over the landing pad region.
Safe landing conditions (line 2000) additionally require ABS HU <= 0.25 (near-zero horizontal velocity) and SY <= 5 (close to ground level).
Physics Model
The game uses a simple Euler-integration physics model. Each loop iteration:
HU(horizontal velocity) is incremented by ±0.25 per keypress of “1” or “3”, clamped to ±3.VV(vertical velocity) increases by 0.25 each frame (gravity), reduced by 0.5 when “2” is held (upward thrust net = −0.25).- Position is updated:
SX = SX + HU,SY = SY - VV. - A ceiling check on line 68 resets
VVto 0 ifSY > 165.
Velocities are displayed each frame via PRINT #1 (the lower status line), truncated to two decimal places using the idiom INT(x*100)/100.
Score Mechanism
The score variable S is incremented each game loop by 1 - (INKEY$ <> ""): it counts up every frame that no key is pressed, and does not increment when thrust is applied. This penalizes fuel use. On a successful landing, the displayed score is 200 - S, rewarding quick, efficient landings. On a crash, raw S is shown with no inversion.
Crash Animation
Lines 1000–1040 implement a 30-frame explosion: alternating OVER states (even/odd frame) flash an X-pattern drawn with PLOT/DRAW in INK 2 (red). Simultaneously, OUT 254 drives the speaker border port with value 55 on even frames and 0 on odd frames, producing a buzzing crash sound without BEEP.
Notable Anomalies
- Line 50 defines
R$with plain ASCII letters; line 51 immediately redefines it with UDG characters. Line 50 is dead code at runtime. - Line 24 contains
DRAW OVER 0;16,-1mid-sequence, locally switching OVER mode off for one segment of the terrain outline — this is syntactically valid as an inline attribute change within a DRAW statement. - The DATA at line 9050 references the variable
DATUM(as a BASIC expression, not a literal), meaning one byte of the UDG data is taken from whatever valueDATUMheld at the end of the preceding READ loop (the last value read in line 9010). This is likely unintentional but produces a consistent result sinceDATUMis deterministic after the loop. - Line 8905 describes controls as “1-left, 2-right, 0-up” but the actual game logic in lines 76–77 uses “1”, “3”, and “2” for left, right, and up respectively. The instructions at line 8905 are incorrect.
- Line 8000 contains a bare
STOPbetween the landing success routine and the initialization block, acting as a guard to prevent falling through into the setup code during normal play.
Content
Source Code
3 REM
4 REM
7 REM SATURN LANDER
8 REM \''\''\''\''\''\''\''\''\''\''\''\''\''
9 REM
10 REM FROM "YOUR COMPUTER" MAGAZINE MAY 1983
11 REM
12 REM Entered by
13 REM G.F.Chambers
14 REM
15 REM
16 REM
17 GO TO 20
18 PLOT SX,SY: PLOT SX,SY+1: DRAW 3,0: PLOT SX+3,SY: PLOT SX+1,SY+2: PLOT SX+2,SY+2
19 RETURN
20 PAPER 0: INK 7: CLS : OVER 1
21 PLOT 0,0: DRAW 10,20: DRAW 9,-10: DRAW 8,6: DRAW 14,-10: DRAW 6,4
22 DRAW 4,-4: DRAW 8,6: DRAW 14,-10: DRAW 16,0
24 DRAW 0,1: DRAW -16,0: DRAW OVER 0;16,-1
26 DRAW 14,6: DRAW 4,-4: DRAW 18,8: DRAW 6,-2: DRAW 8,4
28 DRAW 10,-10: DRAW 10,6: DRAW 8,12: DRAW 10,-8: DRAW 8,4
30 DRAW 16,-10: DRAW 24,14: DRAW 8,-10: DRAW 4,2: DRAW 8,-10
32 DRAW 10,-4
35 RANDOMIZE : FOR S=1 TO RND*100: LET L=USR 32300: NEXT S
40 LET r$="\a\b \c\d \a\b \c\d \a\b \c\d \c\d \a\b \c\d"
45 PRINT AT 12,0;R$;AT 15,0;R$: LET R$=R$(15 TO )+R$( TO 14): PRINT AT 17,0;R$
50 LET R$="EFG KLM KLM EFG EFG KLM HIJ NOP NOP HIJ HIJ NOP "
51 LET r$="\e\f\g \k\l\m \k\l\m \e\f\g \e\f\g \k\l\m \h\i\j \n\o\p \n\o\p \h\i\j \h\i\j \n\o\p"
55 PRINT INK 5;AT 4,0;R$;AT 8,0;R$
60 LET SX=RND*100+75: LET SY=165
61 LET HU=0: LET VV=0
62 LET S=0
64 GO SUB 18
65 IF POINT (SX,SY-1)+POINT (SX+1,SY-1)+POINT (SX+2,SY-1)=3 THEN GO TO 2000
66 LET OSX=SX: LET OSY=SY
67 LET SX=SX+HU
68 LET SY=SY-VV: IF SY>165 THEN LET VV=0
69 LET NSX=SX: LET NSY=SY: LET SX=OSX: LET SY=OSY
70 GO SUB 18
71 LET SX=NSX: LET SY=NSY
72 LET S=S+1-(INKEY$<>"")
75 LET L=USR 32200
76 LET HU=HU+.25*((INKEY$="3" AND SX<250 AND HU<=3)-(INKEY$="1" AND SX>2 AND HU>=-3))
77 LET VV=VV+.25-.5*(INKEY$="2")
78 INPUT "": PRINT #1;"VERT.VEL=";INT (VV*100)/100,"HOR.VEL=";INT (HU*100)/100
80 IF POINT (SX,SY)+POINT (SX,SY+1)+POINT (SX+1,SY+1)+POINT (SX+1,SY+2)+POINT (SX+2,SY+1)+POINT (SX+2,SY+2)+POINT (SX+3,SY)+POINT (SX+3,SY+1)<>0 THEN GO TO 1000
200 GO TO 64
1000 FOR F=0 TO 30: OVER (F/2=INT (F/2)): INK 2: PLOT SX,SY: DRAW 3,2
1010 PLOT SX,SY+2: DRAW 3,-2
1020 OUT 254,55*(F/2=INT (F/2))
1030 NEXT F
1035 INK 7
1040 OUT 254,0
1050 PRINT OVER 1;AT 10,11; FLASH 1;"GAME OVER"
1055 PRINT TAB 11;"SCORE=";S
1056 PRINT AT 18,2; FLASH 1;"PRESS ANY KEY TO PLAY AGAIN"
1060 PAUSE 1000: RUN
2000 IF ABS HU>.25 OR SY>5 THEN GO TO 1000
2010 FOR X=10 TO 50 STEP 5: BEEP .1,X: NEXT X
2015 INK 7
2020 PRINT AT 10,12; FLASH 1;"WELL DONE"
2030 PRINT TAB 11;"SCORE=";200-S
8000 STOP
8900 CLEAR 31999: PRINT AT 5,0;"Controls are:"
8905 PRINT " 1-accelerate to left 2-accelerate to the right 0-accelerate upwards"
8910 PRINT ''"Wait for data to load"
9000 RESTORE 9000: FOR U=0 TO 127
9010 READ DATUM: POKE USR "A"+U,DATUM: NEXT U
9020 DATA 0,15,31,31,63,63,7,0,0,192,224,248,252,252,192,0
9030 DATA 0,1,15,31,127,63,31,0,0,224,252,254,254,248,240,0
9040 DATA 0,0,0,3,15,31,63,61,0,0,0,248,254,255,255,255,0,0,0,0,0,0,128,192
9050 DATA 126,127,127,127,127,63,15,0,247,15,255,255,255,255,255,255,224,240,BIN 10110000,BIN 10111000,BIN 01111000,DATUM,240,0
9060 DATA 0,0,0,0,0,1,3,15,0,0,0,0,127,255,255,255,0,0,0,0,128,224,240,248
9070 DATA BIN 00110111,111,111,BIN 00110111,31,3,0,0,251,247,239,223,255,255,0,0,248,248,240,240,128,0,0,0
9502 RESTORE 9610
9505 READ ADD
9510 LET A$=""
9520 IF A$="" THEN READ A$
9525 IF A$="N" THEN GO TO 9505
9526 IF A$="S" THEN GO TO 10
9540 IF CODE A$(1)>64 THEN LET A$(1)=CHR$ (CODE A$(1)-7)
9550 IF CODE A$(2)>64 THEN LET A$(2)=CHR$ (CODE A$(2)-7)
9560 POKE ADD,16*CODE A$+CODE A$(2)-816
9570 LET ADD=ADD+1
9580 LET A$=A$(3 TO )
9590 GO TO 9520
9610 DATA 32000,"C5D5E5F52B1120000608C5237ECB172BF519F10620CB162B10FB24C110ECF1E1D1C1C9N"
9620 DATA 32100,"C5D5E5F51120000608C52B197ECB1FF5A7ED5223F10620CB1E2310FBA7ED5224C110E6F1E1D1C1C9N"
9630 DATA 32200,"218040CD007D21A040CD007D210048CD647D212048CD647D218048CD007D21E048CD647D212050CD007DC9N"
9640 DATA 32300,"216050CD647D218050CD647D21A050CD647DC9N"
9700 DATA 0,"S"
9998 SAVE "Saturn" LINE 8900
9999 PRINT "Rewind to Verify": VERIFY "Saturn"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
