--- title: "Metagraphics" id: 51735 type: "computer_media" slug: "metagraphics" url: "http://localhost/computer_media/metagraphics/" markdown_url: "http://localhost/computer_media/metagraphics.md" published_at: "2023-08-13T23:14:29+00:00" modified_at: "2026-04-04T20:18:05+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A character-based freehand drawing tool with eight-direction movement, inverse-video toggling, random pattern fills, and machine code speed control built right in." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Calliope Software" slug: "calliope-software" taxonomy: "post_tag" url: "http://localhost/tag/calliope-software/" - 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: "Art" slug: "art" taxonomy: "genre" url: "http://localhost/type/art/" media_type: "Cassette" download_url: "https://archive.org/download/timex-sinclair-software-archive/Metagraphics (1983)(Calliope Software)(US)(TS1000)(Cassette).zip" mediadate: "1983" producer_company: - id: 10718 title: "Calliope Software" type: "company" url: "http://localhost/company/calliope-software/" images: - url: "http://localhost/wp-content/uploads/2023/08/SCR-20260404-eftb.png" related_products: - id: 13297 title: "Metagraphics" type: "product" url: "http://localhost/product/metagraphics/" media_type_tags: "Art" --- # Metagraphics Metagraphics is an interactive freehand drawing program that lets the user place and move a cursor character around a 24×32 text display, leaving graphical characters as “paint” on the screen. The program uses a short machine code routine embedded in REM line 1 (bytes `3E 1E ED 47 C9`) to control display speed via POKE/USR calls at lines 910–999. Cursor movement is handled by subroutines at lines 401–489, covering all eight cardinal and diagonal directions with key-held repeat loops. An inverse-video toggle (line 1050), a random-pattern fill function (line 1100), a stamp/flood fill using string concatenation (lines 310–350), and a cycle-through-characters mechanism (lines 810–870) round out the feature set. *** ## Program Analysis ### Program Structure The program is divided into two main phases. Lines 9000–9330 form the initialization and title-screen section, which displays a splash screen in inverse video, waits for a keypress, saves the program, then jumps to `RUN` to restart from line 1. Lines 1–199 form the main loop: setup (lines 35–95), key scanning and dispatch (lines 110–195), cursor display (lines 197–198), and loop-back (line 199 — note the loop target is line 100, which does not exist; execution falls through to line 110, a common ZX81 technique). Subroutines are grouped in the 200s–1100s ranges by function. ### Machine Code Usage Line 1 contains a REM statement whose body is a five-byte machine code routine: `3E 1E` (`LD A,1E`), `ED 47` (`LD I,A`), `C9` (`RET`). This loads the value from address 16515 (the byte following the REM opcode byte, accessed via `POKE 16515,I`) into the Z80 I register before returning. The routine is invoked with `LET ZZZ=USR 16514`, where 16514 points to the first byte of the REM data. This is used by the speed-control subroutines (lines 910–999 and 1000–1049) to adjust display timing by varying `I` in steps of 2 between 0 and 30. ### Key Dispatch Table The main loop scans `INKEY$` and dispatches via computed `GOSUB` or direct comparison: | Key Code | Key | Action | Subroutine | | --- | --- | --- | --- | | 33–36 | Cursor keys | Cardinal movement | 400+(K-33)*10 | | 112–116 | Diagonal keys | Diagonal movement | 450+(K-112)*10 | | 46 | . | Inverse toggle | 1050 | | 28 | Shift+4 (£?) | Stamp fill | 315 | | 119 | W | Wipe/reset cursor char | 250 | | 42 | * | Swap A$/B$ | 700 | | 40 | ( | Advance character | 800 | | 15 | Enter | Decrement character | 850 | | 50 | 2 | Increase speed | 900 | | 18 | R | Decrease speed | 950 | | 53 | 5 | Reset speed/exit | 200 | | 41 | ) | Show palette | 500 | | 56 | 8 | Save program | (inline SAVE) | | 43 | + | Random fill | 1100 | | 61 | = | Reset speed | 1000 | | 39 | ‘ | Stamp screen | 300 | ### Movement Subroutines Eight subroutines handle cursor movement (lines 401–489). Each subroutine updates row `R` and/or column `C` using a bounded Boolean expression (e.g., `LET C=C-(C>0)`), prints the cursor graphic `C$` at the new position, immediately overwrites it with the drawing character `A$` — effectively “painting” — and then loops on `INKEY$<>""` for key-held autorepeat. The boundary checks using Boolean arithmetic prevent the cursor from leaving the 24×32 screen. ### Character Cycling Subroutine 800 (advance) and 850 (decrement) step through the character set stored in `A$`. The advance routine at line 810 increments the character code, wrapping from code 64 (which would be `@`) back to a space (code 32), and from code 192 (inverse space) to `"% "` (an inverse-video space), bridging the normal and inverse video halves of the character set. This allows the user to cycle through all drawable characters including inverse graphics. ### Inverse Video Toggle Subroutine 1050 toggles between a normal and inverse version of the current drawing character. Line 1055 checks if `CODE A$>65`; if so, it assumes the character is already inverse and strips 128 from its code (line 1080), reverting to normal. Otherwise it adds 128 to the code (line 1065) and sets the cursor indicator `C$` to `"%I"` (inverse I) to show inverse mode is active. Returning to normal mode resets `C$` to `"\@@"` (the default cursor glyph, a block graphic). ### Screen Fill and Stamp Subroutine 300/315 (lines 310–399) fills the screen by building a string `X$` that starts as a copy of `B$` (the saved character), then doubles itself eight times in a loop (`FOR A=1 TO 8`), producing a 256-character string. Three copies are printed at row 0 column 0, flooding the screen. Subroutine 500 (line 510) similarly constructs `D$` as three copies of `A$` and displays it in rows 20–22 as a palette preview strip, then waits for a keypress and restores using `B$`. ### Random Pattern Fill Subroutine 1100 (lines 1100–1230) generates five random characters (`A$` through `E$`), each with a code in the range 0–9 optionally offset by 128 for inverse video (using `RND>.5` as a Boolean 0/1 multiplier). These are concatenated into `X$` and doubled seven times to produce a 640-character string, which is then printed repeatedly in a loop until a key is pressed, creating an animated random texture. ### Notable Techniques - Boolean arithmetic for boundary clamping: `R=R+(R<23)`, `C=C-(C>0)` - Computed `GOSUB` for movement dispatch: `GOSUB 400+(K-33)*10` - String doubling loop to fill screen efficiently without a character-by-character loop - Dual-print cursor technique: print cursor graphic `C$`, then immediately overprint with `A$` to leave paint while showing position - Machine code in REM for I-register manipulation affecting display timing - `LET ZZZ=USR` discards the USR return value without error, a common ZX81 idiom ### Anomalies and Notes Line 199 executes `GOTO 100`, but line 100 does not exist; the interpreter falls through to line 110, which is the intended start of the key-scan loop. Line 35 POKEs address 16418 (FRAMES low byte on a ZX81) to zero, likely to reset the frame counter at startup. The subroutine at lines 210–249 (called from line 170 as `GOSUB 200`) waits for a key-up then key-down event and loops 18 times — its purpose appears to be a keypress synchronization delay. The REM at line 9000 and decorative REM lines serve as a title block within the code rather than documentation. ## Source Code ``` 1 REM 3E1EED47C9 35 POKE 16418,0 40 SLOW 50 LET A$=" " 55 LET B$=A$ 60 LET C$="@@" 70 LET S$=A$ 80 LET R=11 85 LET K=0 90 LET C=13 95 LET I=30 110 LET K=CODE INKEY$ 120 IF K>=33 AND K<=36 THEN GOSUB 400+(K-33)*10 125 IF K>=112 AND K<=116 THEN GOSUB 450+(K-112)*10 130 IF K=46 THEN GOSUB 1050 135 IF K=28 THEN GOSUB 315 140 IF K=119 THEN GOSUB 250 145 IF K=42 THEN GOSUB 700 150 IF K=40 THEN GOSUB 800 155 IF K=15 THEN GOSUB 850 160 IF K=50 THEN GOSUB 900 165 IF K=18 THEN GOSUB 950 170 IF K=53 THEN GOSUB 200 175 IF K=41 THEN GOSUB 500 180 IF K=56 THEN SAVE "METAGRAPHIC%S" 185 IF K=43 THEN GOSUB 1100 190 IF K=61 THEN GOSUB 1000 195 IF K=39 THEN GOSUB 300 197 PRINT AT R,C;C$ 198 PRINT AT R,C;A$ 199 GOTO 100 210 IF INKEY$<>"" THEN GOTO 210 220 IF INKEY$="" THEN GOTO 220 230 FOR P=1 TO 18 240 NEXT P 249 RETURN 260 LET A$=" " 270 GOSUB 300 280 GOSUB 1000 290 LET C$="@@" 299 RETURN 310 LET B$=A$ 315 LET X$=B$ 320 FOR A=1 TO 8 330 LET X$=X$+X$ 340 NEXT A 350 PRINT AT 0,0;X$;X$;X$ 399 RETURN 401 LET C=C-(C>0) 403 PRINT AT R,C;C$ 404 PRINT AT R,C;A$ 405 IF INKEY$<>"" THEN GOTO 401 409 RETURN 411 LET R=R+(R<23) 413 PRINT AT R,C;C$ 414 PRINT AT R,C;A$ 415 IF INKEY$<>"" THEN GOTO 411 419 RETURN 421 LET R=R-(R>0) 423 PRINT AT R,C;C$ 424 PRINT AT R,C;A$ 425 IF INKEY$<>"" THEN GOTO 421 429 RETURN 431 LET C=C+(C<31) 433 PRINT AT R,C;C$ 434 PRINT AT R,C;A$ 435 IF INKEY$<>"" THEN GOTO 431 439 RETURN 451 LET R=R-(R>0) 452 LET C=C+(C<31) 453 PRINT AT R,C;C$ 454 PRINT AT R,C;A$ 455 IF INKEY$<>"" THEN GOTO 451 459 RETURN 461 LET R=R+(R<23) 462 LET C=C-(C>0) 463 PRINT AT R,C;C$ 464 PRINT AT R,C;A$ 465 IF INKEY$<>"" THEN GOTO 461 469 RETURN 471 LET R=R-(R>0) 472 LET C=C-(C>0) 473 PRINT AT R,C;C$ 474 PRINT AT R,C;A$ 475 IF INKEY$<>"" THEN GOTO 471 479 RETURN 481 LET R=R+(R<23) 482 LET C=C+(C<31) 483 PRINT AT R,C;C$ 484 PRINT AT R,C;A$ 485 IF INKEY$<>"" THEN GOTO 481 489 RETURN 510 LET D$=A$+A$+A$ 520 GOSUB 570 530 IF INKEY$="" THEN GOTO 530 540 LET D$=B$+B$+B$ 570 FOR Y=20 TO 22 580 PRINT AT Y,1;D$ 590 NEXT Y 595 IF INKEY$<>"" THEN GOTO 595 599 RETURN 705 IF A$=B$ THEN GOTO 750 710 LET S$=A$ 720 LET A$=B$ 730 IF INKEY$<>"" THEN GOTO 730 740 RETURN 750 LET A$=S$ 760 IF INKEY$<>"" THEN GOTO 760 779 RETURN 810 LET A$=CHR$ (CODE A$+1) 820 IF CODE A$=64 THEN LET A$=" " 830 IF CODE A$=192 THEN LET A$="% " 849 RETURN 860 IF A$=" " THEN LET A$=CHR$ 64 865 IF A$="% " THEN LET A$=CHR$ 192 870 LET A$=CHR$ (CODE A$-1) 899 RETURN 910 LET I=I+2 920 IF I>30 THEN LET I=0 930 POKE 16515,I 940 LET ZZZ=USR 16514 949 RETURN 960 IF I<=0 THEN LET I=32 970 LET I=I-2 980 POKE 16515,I 990 LET ZZZ=USR 16514 999 RETURN 1000 REM % % % %E%X%I%T% % % 1010 LET I=30 1020 POKE 16515,I 1030 LET ZZZ=USR 16514 1049 RETURN 1055 IF CODE A$>65 THEN GOTO 1075 1060 LET C$="%I" 1065 LET A$=CHR$ (CODE A$+128) 1067 IF INKEY$<>"" THEN GOTO 1067 1069 RETURN 1075 LET C$="@@" 1080 LET A$=CHR$ (CODE A$-128) 1085 IF INKEY$<>"" THEN GOTO 1085 1099 RETURN 1100 REM % % % %F%U%N%C%T%I%O%N% % % 1110 LET A$=CHR$ ((RND*10)+128*(RND>.5)) 1120 LET B$=CHR$ ((RND*10)+128*(RND>.5)) 1130 LET C$=CHR$ ((RND*10)+128*(RND>.5)) 1140 LET D$=CHR$ ((RND*10)+128*(RND>.5)) 1150 LET E$=CHR$ ((RND*10)+128*(RND>.5)) 1160 LET X$=A$+B$+C$+D$+E$ 1170 FOR A=1 TO 7 1180 LET X$=X$+X$ 1190 NEXT A 1200 PRINT AT 0,0; 1210 IF INKEY$<>"" THEN RETURN 1220 PRINT X$;X$( TO 128) 1230 GOTO 1100 8999 REM % % % % % % % % % % % % % % 9000 REM "METAGRAPHICS" 9001 REM % % % % % % % % % % % % % % 9002 REM ----------------------- 9010 LET A$="% " 9020 FOR I=1 TO 7 9030 LET A$=A$+A$ 9040 NEXT I 9050 CLS 9060 PRINT A$;A$;A$;A$;A$ 9100 PRINT AT 2,0;"%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=" 9110 PRINT AT 4,0;"% % % % % %M% %E% %T% %A% %G% %R% %A% %P% %H% %I% %C% %S% % % % " 9120 PRINT AT 6,0;"%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=" 9200 PRINT AT 10,5;"%C%O%P%Y%R%I%G%H%T% %1%9%8%2" 9210 PRINT AT 12,5;"%D%A%N% %T%A%N%D%B%E%R%G%,% %M%.%D%." 9220 PRINT AT 13,10;"%R%O%B%O%T%E%C%,% %I%N%C%." 9250 PRINT AT 21,0;"TOUCH ANY KEY TO BEGIN." 9260 PAUSE 400 9290 LET A$="" 9300 SAVE "METAGRAPHIC%S" 9310 IF INKEY$="" THEN GOTO 9310 9320 CLS 9330 RUN ```