--- title: "TSUG Demo" id: 57133 type: "computer_media" slug: "tsug-demo-2" url: "http://localhost/computer_media/tsug-demo-2/" markdown_url: "http://localhost/computer_media/tsug-demo-2.md" published_at: "2024-10-04T00:02:48+00:00" modified_at: "2026-03-30T21:19:39+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/82_TSUG.png" excerpt: "Eight Z80 machine code screen-effect routines hidden inside a REM statement power this endlessly looping banner display for a user group meeting demo." 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/" indiv: - name: "Tim Ward" slug: "tim-ward" taxonomy: "indiv" url: "http://localhost/indiv/tim-ward/" genre: - name: "Demo" slug: "demo" taxonomy: "genre" url: "http://localhost/type/demo/" media_contents: - id: 56733 title: "Timex Sinclair Public Domain Library Tape 1002" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1002/" media_type: "Program" programmers: - name: "Tim Ward" slug: "tim-ward" taxonomy: "indiv" url: "http://localhost/indiv/tim-ward/" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/82_TSUG.png" media_type_tags: "Demo" --- # TSUG Demo This program is a demonstration/banner display for a Timex/Sinclair user group, cycling through eight machine code routines stored as addresses in array S. The REM line at line 0 contains a substantial block of Z80 machine code (approximately 100 bytes) that implements the eight display effect subroutines called via RAND USR. Each routine is called 32 times per screen fill, and the program loops endlessly through all eight effects after printing the group’s name 22 times to fill the display. The S() array holds direct entry-point addresses into the machine code embedded in the REM statement, which begins at address 16385 (the byte after the REM token on line 0). *** ## Program Analysis ### Program Structure The program has two distinct layers: a BASIC driver (lines 1–20) and a library of Z80 machine code routines embedded in the `REM` statement at line 0. The BASIC layer fills the screen with the group name, invokes each machine code effect 32 times, clears the screen, then advances to the next effect in an infinite loop. 1. **Line 0** — `REM` containing raw Z80 opcodes (~100 bytes) 2. **Lines 1–9** — Initialise array `S(8)` with eight machine code entry-point addresses 3. **Lines 11–19** — Outer loop over 8 effects; inner loops fill screen then call the current effect 32 times 4. **Line 20** — `GOTO 11` for perpetual looping 5. **Lines 21–22** — `SAVE` and `RUN` for distribution purposes only ### Machine Code in the REM Statement On the ZX81/TS1000, line 0’s `REM` body starts at address 16385 (0x4001). The eight entry points stored in `S(1)`–`S(8)` are offsets into this block: | Array element | Address (decimal) | Offset into REM (hex) | | --- | --- | --- | | `S(1)` | 16626 | +0xF1 (241) | | `S(2)` | 16514 | +0x81 (129) | | `S(3)` | 16633 | +0xF8 (248) | | `S(4)` | 16560 | +0xAF (175) | | `S(5)` | 16588 | +0xCB (203) | | `S(6)` | 16619 | +0xEA (234) | | `S(7)` | 16529 | +0x90 (144) | | `S(8)` | 16612 | +0xE3 (227) | The machine code is called via `RAND USR S(X)`, the standard ZX81 idiom for invoking Z80 routines from BASIC. The return value on the calculator stack is ignored. ### Disassembly Notes Tracing the byte sequence reveals several recognisable Z80 patterns: - `2A 0C 40` — `LD HL,(400C)`: loads the display file address (D_FILE system variable at 0x400C) - `ED B0` / `ED B8` — `LDIR` / `LDDR`: block copy instructions, strongly suggesting scroll or copy screen effects - `11 21 00 19 D1` — sets `DE` to 0x0021 (33, one display row width) and loads it back, consistent with per-row addressing - `01 D6 02` — `LD BC,02D6` = 726 bytes, which is 22 rows × 33 bytes (the full ZX81 display file size) - `36 00` / `10 FB` — `LD (HL),00 / DJNZ`: clearing loop, used in what appears to be a screen-clear or wipe effect - `FE 76` — `CP 76h (HALT)`: tests for the ZX81 NEWLINE character (0x76) used as a row terminator in the display file - `CD CC 40` / `CD 91 40` / `CD 82 40` / `CD B0 40` — internal `CALL`s between sub-routines within the REM block, confirming a modular design with shared helper routines ### Key BASIC Idioms - **`RAND USR address`** — the canonical ZX81 method for calling machine code; `RANDOMIZE` discards the USR return value cleanly. - **Pre-initialised address array** — storing entry points in `S()` allows the outer loop to dispatch to any of the eight routines with a single `RAND USR S(X)` statement, avoiding a large `IF`/`GOTO` dispatch table. - **Screen fill via `PRINT`** — 22 repetitions of the 33-character banner string exactly fills the 22-row ZX81 display before the effect routines run. ### Notable Techniques Embedding machine code in a `REM` statement at line 0 is a well-established ZX81 technique: line 0 guarantees the code sits at the lowest possible address in the BASIC program area (0x4001 + a small header), making the absolute entry-point addresses predictable and stable regardless of other program content. The eight effects are likely scroll, wipe, invert, and similar display manipulations, each implemented as a small subroutine that shares helper code via internal `CALL`s, keeping the total footprint compact. ### Potential Issues - The entry-point addresses in `S()` are hardcoded and will break if the `REM` line content or any preceding program line changes length, as this shifts the machine code to a different address. - There is no guard against BASIC editing inadvertently corrupting the machine code bytes in the `REM` statement. ## Source Code ``` 0 REM \2A\0C\40\E5\11\21\00\19\D1\01\D6\02\ED\B0\C9\2A\10\40\11\43\00\ED\52\E5\11\21\00\ED\52\D1\01\B5\02\ED\B8\2A\0C\40\06\20\23\36\00\10\FB\C9\2A\0C\40\11\D6\02\19\06\16\2B\4E\36\00\2B\7E\FE\76\28\02\18\03\10\F2\C9\71\4F\18\F1\2A\0C\40\06\16\23\4E\36\00\23\7E\FE\76\28\02\18\03\10\F2\C9\71\4F\18\F1\CD\CC\40\CD\91\40\C9\CD\B0\40\CD\91\40\C9\CD\B0\40\CD\82\40\C9\CD\CC\40\CD\82\40\C9 1 DIM S(8) 2 LET S(1)=16626 3 LET S(2)=16514 4 LET S(3)=16633 5 LET S(4)=16560 6 LET S(5)=16588 7 LET S(6)=16619 8 LET S(7)=16529 9 LET S(8)=16612 11 FOR X=1 TO 8 12 FOR I=1 TO 22 13 PRINT "==TIMEX/SINCLAIR USER""S GROUP==" 14 NEXT I 15 FOR I=1 TO 32 16 RAND USR S(X) 17 NEXT I 18 CLS 19 NEXT X 20 GOTO 11 21 SAVE "1008%2" 22 RUN ```