Saddle Cosine

Developer(s): James Jones
Date: 198x
Type: Program
Platform(s): TS 2068

This program renders a 3D saddle surface defined by the function z = cos(0.1*(x²+y²)) + (x²-y²)/(10*p+10) using a perspective projection onto the screen. It generates four “pages” of the plot at different viewing angles (theta stepping from PI/9 to PI+0.1 in increments of PI/4), storing each rendered frame into a separate memory region via two machine code routines. The two 12-byte Z80 routines, POKEd into addresses 32000 and 32050, perform LDIR-based block memory copies: the first saves the screen (address 16384, length 6912) to a calculated destination, and the second restores it back. The program cycles through all four stored pages in a loop with PAUSE 31 delays to produce a rudimentary animation effect. Saving is handled by two separate SAVE commands — one for the BASIC program with auto-run and one for the 30000-byte machine code and data block starting at 32000.


Program Analysis

Program Structure

The program is organized into several functional blocks:

  1. Initialization (lines 1–8): Sets memory boundaries, POKEs two machine code routines, and initializes the page counter p=0.
  2. Entry point redirect (line 9): GO TO 2000 jumps directly to the playback loop on first run, bypassing the drawing loop — this means the program plays back before generating, which appears intentional for demo/review purposes.
  3. Projection subroutine (lines 20–30): Computes 3D-to-2D perspective projection; called via GO SUB 20.
  4. Drawing loop (lines 35–140): Iterates over viewing angles t and plots the saddle surface point-by-point.
  5. Page save routine (lines 1000–1050): Calculates destination address for each page, patches the machine code, and calls the save routine via USR s.
  6. Playback loop (lines 2000–2060): Restores each stored screen page in sequence with a short pause, then loops indefinitely.
  7. Save/export (lines 9000–9020): Saves both BASIC and the machine code block to tape.

Machine Code Routines

Two 12-byte Z80 routines are POKEd into RAM at addresses s=32000 and r=32050. Both use the LDIR instruction (opcode ED B0) for fast block memory transfer, terminated by RET (C9).

AddressBytesZ80 AssemblyPurpose
32000 (save)17,232,128,33,0,64,1,0,27,237,176,201LD DE,nn; LD HL,16384; LD BC,6912; LDIR; RETCopy screen to destination address
32050 (restore)17,0,64,33,232,128,1,0,27,237,176,201LD DE,16384; LD HL,nn; LD BC,6912; LDIR; RETCopy from source address back to screen

The destination/source address (nn) is a two-byte little-endian value at offsets +1 and +2 from the routine start. The BASIC code at lines 1020–1030 and 2020–2030 calculates the page address as q*7000+26000 and patches bytes at 32001/32002 (save) or 32054/32055 (restore) accordingly. The value 6912 (0x1B00) covers the full pixel and attribute areas of the display. Note that 6912 = 0x1B00, stored as the 16-bit little-endian pair 1,27 (i.e., 00 1B), which matches bytes at positions 8–9 in each DATA sequence.

3D Projection Mathematics

Lines 20–30 implement a combined rotation and perspective projection. The transformation in line 20 applies a rotation matrix parameterized by angles t (theta, azimuth) and phi (elevation), with rho acting as a z-axis translation (viewer distance offset). Line 30 performs the perspective divide: sx = d*xe/ze + cx and sy = cy + d*ye/ze, where d=350 is the focal length and (cx,cy)=(127,87) is the screen center.

Line 45 reassigns phi=t after line 40 sets phi=PI/4, meaning the elevation angle actually tracks the loop variable — both angles change together each frame.

Surface Function

The plotted surface is defined by:

DEF FN z(x) = COS(0.1*(x²+y²)) + (x²-y²)/(10*p+10)

This combines a radially symmetric ripple (cos term) with a hyperbolic saddle component ((x²-y²) term). The parameter p (page number, 1–4) modifies the saddle curvature, so each of the four stored frames differs both in viewing angle and surface shape. The outer variable y is used as a free variable within the FN z(x) definition, which is valid but relies on y being set in the enclosing loop scope.

Page Memory Layout

Four screen pages are stored at addresses computed as q*7000+26000:

Page (q)Address
133000
240000
347000
454000

Page 4 at address 54000 approaches the upper limit of the 64KB address space (65536), and pages 3 and 4 would overlap with ROM or other mapped regions on some configurations. This could cause issues depending on available RAM expansion.

Key BASIC Idioms and Notable Techniques

  • Self-modifying machine code: the BASIC patches the DE register value inside the LDIR routine before calling it, effectively parameterizing the copy destination without needing separate routines for each page.
  • The GO SUB 20 at line 100 is routed through the trampoline at line 10 (GO TO 35 bypasses it after initialization), which is a flow-control quirk — line 20 is directly a LET statement serving double duty as both a subroutine body and a location jumped over by line 10.
  • Numerous REM-commented lines (75–78, 105, 123, 125, 130, 1000–1010, 2000, 2010, 2050) preserve alternative plotting approaches (Lissajous curves, DRAW-based line plotting) and interactive page selection, indicating iterative development.
  • The IF fl=0 THEN LET l=1: PLOT sx,sy at line 120 uses a flag variable fl to control whether a point is plotted, but fl is never set to anything other than 0 (it is reset to 0 at line 80 and line 110), so every valid point is always plotted — the flag mechanism is vestigial from the commented-out DRAW-based approach.
  • The FOR x loop steps in increments of -0.05 (line 70) while the inner FOR y loop steps by integer 1 (line 90), resulting in a much denser sampling in the x-direction than y.

Bugs and Anomalies

  • Line 9 (GO TO 2000) causes the program to skip straight to playback on startup. Since no frames have been generated or saved at that point, USR r will copy uninitialized memory to the screen. This is only harmless if previously saved CODE has been loaded.
  • The variable l is set to 1 at line 120 but never used meaningfully; it appears to be a leftover from a color or draw-length variable in an earlier version.
  • Lines 9005 and 9010 both attempt to save starting at address 32000 for 30000 bytes; the second SAVE ... LINE 1 at line 9010 duplicates line 9000, and the intervening BEEP at line 9008 separates them unnecessarily.

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
   10 GO TO 35
   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 t=PI/9 TO PI+.1 STEP PI/4
   38 REM t=theta
   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)
   45 LET phi=t
   50 DEF FN z(x)=COS (.1*(x*x+y*y))+(x*x-y*y)/(10*p+10)
   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 t
 2000 FOR q=1 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
 2020 LET a=q*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 31
 2050 NEXT q: REM RETURN 
 2060 GO TO 2000
 9000 SAVE "saddle cos" LINE 1
 9005 SAVE "saddle cos"CODE 32000,30000
 9008 BEEP 1,22
 9010 SAVE "saddle cos" LINE 1
 9020 BEEP 2,33

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

Scroll to Top