--- title: "Saturn Lander" id: 53809 type: "computer_media" slug: "saturn" url: "http://localhost/computer_media/saturn/" markdown_url: "http://localhost/computer_media/saturn.md" published_at: "2024-05-13T18:56:53+00:00" modified_at: "2026-03-30T21:45:15+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/05/Saturn-Lander.png" excerpt: "Guide a spacecraft down through Saturn's rings and land safely on the rocky surface — this BASIC lander game packs machine code routines, UDG graphics, and pixel-perfect collision detection." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Arcade" slug: "arcade" taxonomy: "genre" url: "http://localhost/type/arcade/" - name: "Game" slug: "game" taxonomy: "genre" url: "http://localhost/type/game/" media_contents: - id: 53877 title: "SINCUS Exchange Tape 101 – Entertainment" type: "computer_media" url: "http://localhost/computer_media/sincus-exchange-tape-101-entertainment/" - id: 55430 title: "Timex Sinclair Public Domain Library Tape 2004" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-2004/" - id: 55711 title: "ISTUG Public Domain Library 8" type: "computer_media" url: "http://localhost/computer_media/istug-public-domain-library-8/" media_type: "Program" images: - url: "http://localhost/wp-content/uploads/2024/05/Saturn-Lander.png" media_type_tags: "Arcade, Game" --- # Saturn Lander 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: | 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 `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. ## 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" ```