This program renders a three-dimensional surface plot of the function z = 3·e^(−0.1·(x²+y+y)) using a perspective projection, then stores successive views to memory pages and plays them back as a simple animation. The projection routine at lines 20–30 applies a full rotation matrix using precomputed sines and cosines of azimuth angle theta and elevation angle phi, projecting 3-D coordinates to a 2-D screen position centered at (cx, cy). 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 block-copy the screen buffer (6912 bytes from address 16384) to and from off-screen memory pages, enabling multi-frame storage and retrieval. The program iterates theta in steps of PI/4 from PI/8 to PI, drawing four rotated views and cycling through them in a continuous loop via the recall routine.
Program Analysis
Program Structure
The program is organized into several functional blocks:
- Initialization (lines 1–8): Clears memory above 31999, defines base addresses for two machine code routines, and POKEs them into RAM.
- Projection subroutine (lines 20–30): Applies a 3-D rotation and perspective transform.
- Main drawing loop (lines 35–140): Iterates theta, draws the surface for each viewing angle, and saves the screen to a memory page.
- Page-save logic (lines 1000–1050): Patches the save machine code routine with the destination address and calls it.
- Playback loop (lines 2000–2060): Recalls saved screen pages in sequence with a short pause between frames, then repeats indefinitely.
- Save lines (9000–9050): SAVE statements for backing up the BASIC program and the machine code block.
Machine Code Routines
Two 12-byte Z80 routines are installed at s=32000 (save) and r=32050 (recall). Both use the Z80 block-move instruction LDIR (mnemonic opcodes ED B0) to copy 6912 bytes (a full Spectrum display file). The DATA statements decode as follows:
| Address | Bytes | Z80 Mnemonics | Purpose |
|---|---|---|---|
| 32000 | 17, a1, a2 | LD DE, <dest> | Destination address (patched at runtime) |
| 32003 | 33, 0, 64 | LD HL, 16384 | Source = screen start |
| 32006 | 1, 0, 27 | LD BC, 6912 | Byte count (27×256 = 6912) |
| 32009 | 237, 176 | LDIR | Block copy |
| 32011 | 201 | RET | Return to BASIC |
The recall routine at 32050 mirrors this with source and destination swapped: DE is loaded with 16384 (the screen) and HL with the page address, so the off-screen data is written back to the display. Addresses are patched dynamically by poking the low and high bytes into 32001/32002 (save) and 32054/32055 (recall) before each USR call.
Perspective Projection
The subroutine at lines 20–30 implements a standard perspective camera transform. The 3-D point (x, y, z) is first rotated using precomputed values s1=SIN(t), c1=COS(t), s2=SIN(phi), c2=COS(phi), producing camera-space coordinates xe, ye, ze. The offset rho=50 shifts the scene along the camera’s z-axis. Screen coordinates are then computed as sx = d*xe/ze + cx and sy = cy + d*ye/ze with focal length d=350 and screen center (cx, cy) = (127, 87).
Surface Function and Drawing
The surface is defined at line 50 as DEF FN z(x) = 3*EXP(-.1*(x*x+y+y)). Note that the exponent argument contains y+y (i.e., 2y) rather than the expected y*y, which is almost certainly a typo — the intended function is the Gaussian bell surface z = 3·e^(−0.1·(x²+y²)). The outer loop iterates x from 10 to −10 in steps of −0.1 and the inner loop iterates integer y from −10 to 10. The variable fl acts as a “pen down” flag: when a valid screen point is found after an out-of-bounds gap, PLOT is used to begin a new stroke; otherwise DRAW connects to the previous point using the stored values sx1, sy1. However, lines 125–130 overwrite sx/sy with themselves before DRAW, meaning the DRAW always has delta arguments of zero — this is a bug; the previous screen coordinates should have been saved before recalculating sx and sy for the new point.
Memory Page Addressing
Each of the four screen pages is stored at address a = p*7000 + 26000, giving addresses 33000, 40000, 47000, and 54000 for pages 1–4. These regions lie well above the BASIC/display area but must fit within a 64 KB address space; page 4 at 54000 + 6912 = 60912 is within bounds. The low and high byte decomposition uses integer arithmetic: a2 = INT(a/256), a1 = a - a2*256, avoiding the BASIC INT/MOD combination.
Notable Techniques and Anomalies
- Commented-out
INPUTandIFstatements at lines 1000–2050 suggest the program was originally interactive but was simplified to run automatically withpauto-incrementing andqdriven by a FOR loop. LET l=1at line 120 appears to set a variable that is never subsequently used — likely a remnant of a color or attribute selection feature.- The
CLEAR 31999at line 3 sets RAMTOP just below the machine code, protecting it from BASIC’s memory manager. - The playback loop (lines 2000–2060) runs indefinitely via
GO TO 2000after exhausting the FOR loop, creating a continuous slideshow. - The REM at the end of line 2050 contains
RETURN, suggesting an earlier design where the recall block was a subroutine. - Two separate
SAVE "droppages" LINE 0statements exist at lines 9000 and 9050, which is redundant.
Content
Source Code
1 REM this poqram 6.4
2 REM draws a surface z=f(x,y)
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/8 TO PI+.1 STEP PI/4
38 REM t=theta
40 LET rho=50: LET d=350: LET phi=1: 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)=3*EXP (-.1*(x*x+y+y))
60 REM color
70 FOR x=10 TO -10 STEP -.1
80 LET fl=0
90 FOR y=-10 TO 10
100 LET z=FN z(x): 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
125 LET sx1=sx: LET sy1=sy
130 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 p: REM IF p<1 OR p>4 THEN GO TO 2000
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 22
2050 NEXT q: REM RETURN
2060 GO TO 2000
9000 SAVE "droppages" LINE 0
9005 STOP
9010 SAVE "droppages"CODE 32000,30000
9050 SAVE "droppages" LINE 0
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
