Saddle Cosine 1

Developer(s): James Jones
Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Demo, Graphics

This program renders a 3D “saddle-cosine” surface using a perspective projection, iterating over a parameter k to produce a family of related surfaces. It implements a full 3D rotation pipeline in lines 20–30, converting world coordinates through rotation angles theta and phi into screen coordinates via a perspective divide. Two 12-byte machine code routines are POKEd into RAM at addresses 32000 and 32050; these use the Z80 LDIR instruction (opcode ED B0) to perform fast block memory copies, saving and restoring the Spectrum’s display file to/from pages in RAM above 26000. The surface function is defined with DEF FN as COS(0.1*(x²+y²)) + (x²-y²)/k, and k loops from 10 to 40 to animate a morphing shape. The program saves both the machine code block and itself to tape via line 9000 onwards.


Program Structure

The program divides into four logical sections:

  1. Initialisation (lines 1–8): Sets CLEAR 31999 to protect machine code space, POKEs two 12-byte ML routines at addresses s=32000 and r=32050, and initialises page counter p=0.
  2. 3D plotting loop (lines 35–140): Iterates k from 10 to 40, sets up projection parameters, and plots the surface by sweeping x and y.
  3. Page-save section (lines 1000–1050): After each surface is plotted, calls the LDIR save routine via USR s to copy the display file to a RAM page.
  4. Page-recall slideshow (lines 2000–2060): Loops q from −3 to 4, recalls stored display pages via USR r with brief PAUSE 1 delays, looping indefinitely.

Machine Code Routines

Two 12-byte Z80 routines are loaded via READ/POKE from DATA statements at lines 6–7:

AddressBytes (decimal)Z80 MnemonicPurpose
32000 (save)17,232,128 → LD DE,32744(?)LD DE,nnDestination address (patched at runtime)
+333,0,64 → LD HL,16384LD HL,nnSource = display file base
+61,0,27 → LD BC,6912LD BC,nnByte count = full display file
+9237,176 → LDIRLDIRBlock copy display→RAM page
+11201 → RETRETReturn to BASIC
32050 (restore)17,0,64 → LD DE,16384LD DE,nnDestination = display file
+333,232,128 → LD HL,33000(?)LD HL,nnSource address (patched at runtime)
+61,0,27 → LD BC,6912LD BC,nnByte count = full display file
+9237,176 → LDIRLDIRBlock copy RAM page→display
+11201 → RETRETReturn to BASIC

The destination/source addresses in these routines are patched at runtime: lines 1030–1031 overwrite bytes at offsets +1/+2 of the save routine (POKE 32001,a1: POKE 32002,a2), and lines 2030–2031 patch the restore routine similarly. This allows the same LDIR stub to copy the 6912-byte display file to/from any calculated RAM address.

Page addresses are computed as a = p*7000 + 26000, giving pages starting at 26000, 33000, 40000, 47000 for p=0–3. However, page 4 (a=54000) would overlap with the machine code at 32000–32061 for some pages, and addresses above ~49151 are in ROM on an unexpanded Spectrum — this scheme likely assumes a 128 KB or RAM-expanded machine, or the page count is intended to stay within safe bounds.

3D Projection Pipeline

Lines 20–30 implement a classic perspective projection with rotation. Given world coordinates (x, y, z), the routine computes rotated eye-space coordinates using angles t (theta, azimuth) and phi (elevation), with precomputed sines/cosines s1, c1, s2, c2. The perspective divide uses focal length d=350 and centre (cx,cy)=(127,87):

  • xe = -x*s1 + y*c1 — rotated X
  • ye = -x*c1*c2 - y*s1*c2 + z*s2 — rotated Y
  • ze = -x*s2*c1 - y*s2*s1 - z*c2 + rho — depth (with viewer distance rho=50)
  • sx = d*xe/ze + cx, sy = cy + d*ye/ze — screen coordinates

Surface Function and Animation

The surface is defined at line 50: DEF FN z(x) = COS(0.1*(x*x+y*y)) + (x*x-y*y)/k. The first term is a radially symmetric cosine ripple; the second is a hyperbolic paraboloid (saddle) term scaled by 1/k. As k increases from 10 to 40 in the outer loop (line 35), the saddle contribution diminishes, morphing the shape. The variable y is used as a free variable inside FN z, relying on its current value from the enclosing FOR y loop — a standard but implicit BASIC closure technique.

Plotting and Clipping

Line 110 clips points outside the screen bounds (0–255 horizontally, 0–175 vertically) and resets the fl (flag) variable to 0 on out-of-bounds points. Line 120 uses IF fl=0 THEN LET l=1: PLOT sx,sy — the assignment LET l=1 here appears vestigial (a remnant of a DRAW-based approach visible in commented-out lines 125–130). The variable l is set but never subsequently used in the active code path.

Notable Anomalies and Remnants

  • Line 9 (GO TO 2000) followed by line 10 (GO TO 35) means line 10 is dead code on first run; the REM notes it should be removed for initial plotting.
  • Lines 2 and 8 contain initialisation that is bypassed after the first run via the GO TO 2000 at line 9 — the program enters the recall slideshow directly on subsequent runs if the display pages are already populated.
  • The recall loop at line 2000 uses q from −3 to 4 and computes p=ABS q, skipping p=0 and q=1 via line 2017. This effectively recalls pages 3, 2, 1, skips 0, skips q=1 (p=1), then recalls 2, 3, 4 — an unusual asymmetric sequence likely producing a non-obvious display order.
  • Line 2 is a REM but contains executable-looking code (BORDER 0: PAPER 0: INK 7: CLS); it is never executed and serves as a comment-placeholder for setup commands.
  • Several FOR loops and DEF FN alternatives are present as REMs (lines 52–78), suggesting experimentation with torus/Lissajous figures.

Save/Load Sequence

Lines 9000–9010 save the machine code block (30000 bytes from address 32000) and then the BASIC program itself with LINE 1 autostart. The large byte count of 30000 for the CODE block is almost certainly intentional overkill — it captures the ML routines plus any data pages stored above them, providing a complete snapshot for tape distribution.

Content

Appears On

Capital Area Timex Sinclair User Group’s Library Tape.

Related Products

Related Articles

Related Content

Image Gallery

Source Code

    1 REM  this poqram 6.4
    2 REM BORDER 0: PAPER 0: INK 7: CLS : CLS 
    3 CLEAR 31999: LET s=32000: LET r=32050
    4 RESTORE : FOR j=0 TO 11: READ a: POKE s+j,a: NEXT j
    5 FOR j=0 TO 11: READ a: POKE r+j,a: NEXT j
    6 DATA 17,232,128,33,0,64,1,0,27,237,176,201
    7 DATA 17,0,64,33,232,128,1,0,27,237,176,201
    8 LET p=0
    9 GO TO 2000: REM TAKE OUT TO PLOT PICTURES FOR THE FIRST TIME
   10 GO TO 35
   12 REM SADDLECOS
   15 REM by                              James N. Jones                  2242 Locust                     Amarillo, Texas 79109
   20 LET xe=-x*s1+y*c1: LET ye=-x*c1*c2-y*s1*c2+z*s2: LET ze=-x*s2*c1-y*s2*s1-z*c2+rho
   30 LET sx=d*xe/ze+cx: LET sy=cy+d*ye/ze: RETURN 
   35 FOR k=10 TO 40
   38 LET t=PI/4
   40 LET rho=50: LET d=350: LET phi=PI/4: LET cx=127: LET cy=87: LET s1=SIN (t): LET s2=SIN (phi): LET c1=COS (t): LET c2=COS (phi)
   50 DEF FN z(x)=COS (.1*(x*x+y*y))+(x*x-y*y)/(k)
   52 REM LET size=8
   55 REM DEF FN z(l)=4*SIN l
   60 REM color
   70 FOR x=10 TO -10 STEP -.05
   75 REM FOR m=0 TO PI*2 STEP .05
   76 REM LET x=size*COS (1*m): REM LET x=size*SIN l
   77 REM LET z=size*1*COS (2*m)
   78 REM LET y=size*1*SIN (3*m)
   80 LET fl=0
   90 FOR y=-10 TO 10
  100 LET z=FN z(x): GO SUB 20
  105 REM GO SUB 20
  110 IF sx<0 OR sx>255 OR sy<0 OR sy>175 THEN LET fl=0: GO TO 140
  120 IF fl=0 THEN LET l=1: PLOT sx,sy
  123 REM PLOT 127,87: DRAW 127-sx,87-sy
  125 REM LET sx1=sx: LET sy1=sy
  130 REM DRAW (sx1-sx),(sy1-sy)
  140 NEXT y: NEXT x
 1000 LET p=p+1: REM INPUT "Stor as page no. (1-4) ";p
 1010 LET p=INT p: REM IF p<1 OR p>4 THEN GO TO 1000
 1020 LET a=p*7000+26000: LET a2=INT (a/256): LET a1=a-a2*256
 1030 POKE 32001,a1: POKE 32002,a2
 1040 LET n=USR s
 1045 IF p=4 THEN GO TO 2000
 1046 CLS 
 1050 NEXT k
 2000 FOR q=-3 TO 4: REM INPUT "Recall page no. (1-4) ";p
 2010 LET p=INT q: REM IF p<1 OR p>4 THEN GO TO 2050
 2015 LET p=ABS p
 2017 IF p=0 OR q=1 THEN GO TO 2050
 2020 LET a=p*7000+26000: LET a2=INT (a/256): LET a1=a-a2*256
 2030 POKE 32054,a1: POKE 32055,a2
 2040 LET n=USR r
 2045 PAUSE 1
 2050 NEXT q: REM RETURN 
 2060 GO TO 2000
 9000 SAVE "saddlecos1"CODE 32000,30000
 9005 BEEP 1,22
 9006 SAVE "saddlecos1" LINE 1
 9010 BEEP 2,33

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

Scroll to Top