--- title: "3D Cube Animation" id: 57441 type: "computer_media" slug: "unknown-program" url: "http://localhost/computer_media/unknown-program/" markdown_url: "http://localhost/computer_media/unknown-program.md" published_at: "2024-10-05T13:46:51+00:00" modified_at: "2026-04-03T07:58:29+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A 3D wireframe cube is drawn vertex-by-vertex in BASIC, then a hex-encoded Z80 machine code routine takes over for the finale — all in under 100 lines." 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 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Demo" slug: "demo" taxonomy: "genre" url: "http://localhost/type/demo/" media_contents: - id: 56736 title: "Timex Sinclair Public Domain Library Tape 1005" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1005/" media_type: "Program" mediadate: "198x" media_type_tags: "Demo" --- # 3D Cube Animation This program draws a wireframe 3D cube on screen using BASIC line-drawing routines, then pokes Z80 machine code into memory to animate or manipulate the display. The X and Y coordinate arrays (each 24 elements) store the projected vertices of the cube at multiple rotation steps, with pairs of points defining edges drawn via a slope-intercept line algorithm in the subroutine at line 2500. Vertical edges are handled separately at line 3000 using a simple Y-loop PLOT. Machine code is loaded at line 4010/4060 via a hex-string POKEing routine (lines 4070–4130) that decodes two-character hex pairs using CODE arithmetic, with the entry point address decoded from the string “7530” (hex for address 30000). After the BASIC animation loop completes, execution transfers to the machine code via RAND USR 30000 at line 1040. *** ## Program Analysis ### Program Structure The program is divided into several functional blocks: 1. **Initialisation (lines 20–130):** Clears screen, enters FAST mode, dimensions two 24-element arrays, and zeroes several string variables. 2. **Coordinate data (lines 140–600):** Hard-codes projected X and Y screen coordinates for 24 points — the vertices of a cube at multiple viewing angles or rotation frames, stored in `X()` and `Y()`. 3. **Machine code loader (line 605):** Calls `GOSUB 4010` to poke the Z80 routine before the animation loop begins. 4. **Main animation loop (lines 610–820):** Iterates `J` from 1 to 21 in steps of 4, drawing each frame of the cube via subroutines at 1500, then rebuilding `A$` as a hex dump of screen memory before clearing and reloading the machine code hex string. 5. **Line drawing — horizontal edges (lines 2500–2550):** Slope-intercept algorithm plotting points along a line between two vertices. 6. **Line drawing — vertical edges (lines 3000–3030):** Simple Y-loop PLOTs, plus a fixed vertical line at X=30. 7. **Machine code POKEing (lines 4010–4130):** Decodes and writes two hex-string segments into memory. 8. **Address decoder (lines 5000–5010):** Converts a 4-character hex string into a numeric address used as the POKE destination `Z`. 9. **Termination (lines 1010–1060):** Restores SLOW mode and executes the machine code with `RAND USR 30000`. ### Coordinate Data and Cube Geometry The 24 coordinate pairs in `X(1)–X(24)` and `Y(1)–Y(24)` represent 6 groups of 4 points each. The main loop steps through these in groups of 4 (`J=1,5,9,13,17,21`), drawing edges between consecutive pairs. This encodes a perspective or parallel projection of a rotating or multi-view cube directly as lookup tables, avoiding any trigonometric calculation at runtime. The symmetry in the data is notable: X values for indices 9–12 match those for 17–20, and Y values are mirrored, suggesting a front-to-back rotation where intermediate frames reuse reflected coordinates. ### Line Drawing Algorithm The subroutine at line 2500 draws a line between two points using the classic slope-intercept form. Given two points `(X(I), Y(I))` and `(X(I+1), Y(I+1))`: - `M` = gradient = `(Y(I)-Y(I+1))/(X(I)-X(I+1))` - `C` = Y-intercept = `Y(I) - M*X(I)` - Points are plotted for each integer X from `X(I)` to `X(I+1)` This approach works for edges that are not too steep (low gradient), but will produce sparse plots for near-vertical lines. The separate vertical-edge subroutine at line 3000 compensates by iterating over Y instead of X. Note that lines 3024–3028 unconditionally draw a fixed vertical line at X=30 from Y=22 to Y=43, which appears to be the central axis spine of the cube used as a reference or construction line. ### Machine Code Loading Mechanism The hex-string POKEing idiom (lines 4070–4130) is a common technique for embedding Z80 machine code in BASIC programs. Two characters of the hex string are consumed per byte: - `16*CODE A$ + CODE A$(2) - 476` converts two ASCII hex digits to a byte value. Since `CODE "0"` = 28 on the ZX81, the bias `476 = 16*28 + 28` correctly removes the character offset. - `Z` is incremented after each POKE, advancing through memory. - `A$` is sliced with `A$(3 TO)` to consume the processed pair. - The loop exits when `A$=""` (line 4070). The address decoder at line 5000 handles a 4-character hex string, computing `Z = 4096*C1 + 256*C2 + 16*C3 + C4 - 122332`. The bias `122332 = 4096*28 + 256*28 + 16*28 + 28` again removes the ZX81 character code offset, yielding the target address. The string `"7530"` decodes to hex 0x7530 = decimal 30000, confirming the machine code is loaded at address 30000. ### Screen Memory Snapshot (lines 660–790) After drawing each frame, the program reads 364 bytes of screen memory starting at the display file address obtained from system variables at addresses 16396–16397 (`D_FILE`). Each byte is split into high and low nibbles, converted to ZX81 character codes by adding 28, and appended to `A$`. This hex dump of the display is built but then discarded when `A$` is overwritten at line 4060 with the machine code hex string. This suggests the screen capture code may be a remnant of a development or debugging phase, or is intended to be used differently in a more complete version. ### Frame Counter and Display Variable `P` counts rendered frames. At line 650, when `P=6` a pound sign character is printed at position `AT 10,30` — one column to the left of the standard position — suggesting a two-character display that changes at the halfway point of the 6-frame animation. ### Key Variables | Variable | Purpose | | --- | --- | | `X(1..24)` | X screen coordinates for cube vertices | | `Y(1..24)` | Y screen coordinates for cube vertices | | `J` | Outer loop index, selects vertex group (step 4) | | `I` | Inner loop index, selects individual vertex pair | | `M`, `C` | Gradient and intercept for line drawing | | `P` | Frame counter | | `Z` | Current POKE destination address | | `A$` | Dual-use: screen hex dump, then machine code hex string | | `A` | Display file base address (from D_FILE system variable) | ### Notable Anomalies - Line 4010 is referenced by `GOSUB 4010` at line 605 but does not exist as a line number; the program falls through to line 4020, which is a well-known BASIC technique for entering a subroutine at an offset. - The screen memory dump into `A$` (lines 660–790) is immediately overwritten at line 4060, making it effectively dead code in its current form. - Lines 5020–5050 (`STOP`, `SAVE`, `RUN`) are unreachable in normal execution and serve as utility lines for saving the program. - The fixed PLOT loop at lines 3024–3028 (X=30, Y=22 to 43) runs unconditionally every time a vertical edge is drawn, potentially overdrawing and creating a persistent visual artifact. ## Source Code ``` 20 CLS 40 FAST 50 DIM X(24) 60 DIM Y(24) 70 LET P=0 80 LET B$="" 90 LET C$="" 100 LET D$="" 110 LET E$="" 120 LET F$="" 130 LET G$="" 140 LET X(1)=20 150 LET X(2)=40 160 LET X(3)=20 170 LET X(4)=40 180 LET X(5)=23 190 LET X(6)=37 200 LET X(7)=23 210 LET X(8)=37 220 LET X(9)=26 230 LET X(10)=34 240 LET X(11)=26 250 LET X(12)=34 260 LET X(13)=31 270 LET X(14)=30 280 LET X(15)=31 290 LET X(16)=30 300 LET X(17)=26 310 LET X(18)=34 320 LET X(19)=26 330 LET X(20)=34 340 LET X(21)=23 345 LET X(22)=37 350 LET X(23)=23 360 LET X(24)=37 370 LET Y(1)=39 380 LET Y(2)=39 390 LET Y(3)=26 400 LET Y(4)=26 410 LET Y(5)=42 420 LET Y(6)=36 430 LET Y(7)=29 440 LET Y(8)=23 450 LET Y(9)=43 460 LET Y(10)=35 470 LET Y(11)=30 480 LET Y(12)=22 490 LET Y(13)=43 500 LET Y(14)=35 510 LET Y(15)=30 520 LET Y(16)=22 530 LET Y(17)=35 540 LET Y(18)=43 550 LET Y(19)=22 560 LET Y(20)=30 570 LET Y(21)=36 580 LET Y(22)=42 590 LET Y(23)=23 600 LET Y(24)=29 605 GOSUB 4010 610 FOR J=1 TO 21 STEP 4 620 GOSUB 1500 630 PRINT AT 10,31;"£" 640 LET P=P+1 650 IF P=6 THEN PRINT AT 10,30;"£" 660 LET A=PEEK 16396+256*PEEK 16397 665 LET A$="" 670 FOR B=0 TO 363 680 LET S=PEEK (A+B) 690 LET H=INT (S/16) 700 LET L=(S/16-H)*16 710 LET L$=CHR$ (L+28) 720 LET H$=CHR$ (H+28) 730 LET A$=A$+H$+L$ 790 NEXT B 800 CLS 810 GOSUB 4070 820 NEXT J 1010 CLS 1020 SLOW 1040 RAND USR 30000 1060 STOP 1500 FOR I=J TO J+3 STEP 2 1510 GOSUB 2500 1520 NEXT I 1530 FOR I=J TO J+1 1540 GOSUB 3000 1550 NEXT I 1560 RETURN 2500 LET M=(Y(I)-(Y(I+1)))/(X(I)-(X(I+1))) 2510 LET C=Y(I)-(M*X(I)) 2520 FOR X=X(I) TO X(I+1) 2530 PLOT X,(M*X)+C 2540 NEXT X 2550 RETURN 3000 FOR Y=Y(I+2) TO Y(I) 3010 PLOT X(I),Y 3020 NEXT Y 3024 FOR Y=22 TO 43 3026 PLOT 30,Y 3028 NEXT Y 3030 RETURN 4020 LET A$="7530" 4030 GOSUB 5000 4060 LET A$="1EFF1D7BFE00C82A0C40015A750A7723030AFE0C20F8030AFE0C28E61602157AFE0020FA2A0C400A18E4" 4070 IF A$="" THEN RETURN 4100 POKE Z,16*CODE A$+CODE A$(2)-476 4110 LET Z=Z+1 4120 LET A$=A$(3 TO ) 4130 GOTO 4070 5000 LET Z=4096*CODE A$+256*CODE A$(2)+16*CODE A$(3)+CODE A$(4)-122332 5010 RETURN 5020 STOP 5040 SAVE "1024%2" 5050 RUN ```