Saturn Lander

Type: Program
Platform(s): TS 2068
Tags: Arcade, Game

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:

  1. Lines 3–16: REM header block with title and attribution.
  2. Lines 17–32: Scene drawing — Saturn’s surface terrain via PLOT/DRAW, and the ring/atmosphere belt.
  3. Lines 35–55: UDG-based planet and ring graphics printed at fixed screen positions.
  4. Lines 60–200: Main game loop — lander physics, sprite drawing, collision detection, and input handling.
  5. Lines 1000–1060: Crash sequence with flashing explosion, score display, and restart.
  6. Lines 2000–2030: Successful landing sequence with score calculation.
  7. Lines 8900–9070: Initialization routine — prints controls, loads UDG pixel data from DATA statements.
  8. Lines 9502–9700: Machine code loader — decodes hex strings from DATA and POKEs bytes into memory.
  9. 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:

AddressCalled atPurpose (inferred)
32000Line 35 (RANDOMIZE loop)Seed/timing utility
32100Secondary utility
32200Line 75Called each game loop iteration (likely delay/sync)
32300Line 35Called 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 VV to 0 if SY > 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,-1 mid-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 value DATUM held at the end of the preceding READ loop (the last value read in line 9010). This is likely unintentional but produces a consistent result since DATUM is 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 STOP between the landing success routine and the initialization block, acting as a guard to prevent falling through into the setup code during normal play.

Content

Appears On

Library tape of the Indiana Sinclair Timex User’s Group.
One of a series of library tapes. Programs on these tapes were renamed to a number series. This tape contained

Related Products

Related Articles

Related Content

Image Gallery

Saturn Lander

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.

People

No people associated with this content.

Scroll to Top