--- title: "Saddle Cosine" id: 64606 type: "computer_media" slug: "saddle-cosine" url: "http://localhost/computer_media/saddle-cosine/" markdown_url: "http://localhost/computer_media/saddle-cosine.md" published_at: "2026-03-04T19:49:31+00:00" modified_at: "2026-03-30T21:45:14+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "Watch a 3D saddle-shaped surface animate across four stored screen pages, driven by Z80 machine code block-copy routines and perspective projection math." 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/" indiv: - name: "James Jones" slug: "james-jones" taxonomy: "indiv" url: "http://localhost/indiv/james-jones/" genre: - name: "Graphics" slug: "graphics" taxonomy: "genre" url: "http://localhost/type/graphics/" media_contents: - id: 64507 title: "CATS Library Tape 9" type: "computer_media" url: "http://localhost/computer_media/cats-library-tape-9/" media_type: "Program" programmers: - name: "James Jones" slug: "james-jones" taxonomy: "indiv" url: "http://localhost/indiv/james-jones/" download_url: "https://archive.org/download/timex-sinclair-software-archive/Saddle%20COS%20%28198x%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" media_type_tags: "Graphics" --- # Saddle Cosine 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`). | Address | Bytes | Z80 Assembly | Purpose | | --- | --- | --- | --- | | 32000 (save) | `17,232,128,33,0,64,1,0,27,237,176,201` | `LD DE,nn; LD HL,16384; LD BC,6912; LDIR; RET` | Copy screen to destination address | | 32050 (restore) | `17,0,64,33,232,128,1,0,27,237,176,201` | `LD DE,16384; LD HL,nn; LD BC,6912; LDIR; RET` | Copy 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 | | --- | --- | | 1 | 33000 | | 2 | 40000 | | 3 | 47000 | | 4 | 54000 | 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. ## 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 ```