--- title: "Inverse Scroll" id: 57600 type: "computer_media" slug: "inverse-scroll" url: "http://localhost/computer_media/inverse-scroll/" markdown_url: "http://localhost/computer_media/inverse-scroll.md" published_at: "2024-10-05T22:57:42+00:00" modified_at: "2026-04-03T07:58:16+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/inverscroll.png" excerpt: "Enter any message and watch each word rendered in giant pixel characters, scrolling endlessly using direct ROM font data and bit-by-bit UNPLOT plotting." 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: "Home" slug: "home" taxonomy: "genre" url: "http://localhost/type/home/" media_contents: - id: 56723 title: "Synchro-Sette February 1983" type: "computer_media" url: "http://localhost/computer_media/synchro-sette-february-1983/" media_type: "Program" mediadate: "February 1983" images: - url: "http://localhost/wp-content/uploads/2024/10/inverscroll.png" media_type_tags: "Home" --- This program implements a word-by-word scrolling marquee display on the ZX81/TS1000, presenting each word of a user-entered message in large pixel-doubled characters built from the ROM character set. The program reads the character font directly from ROM at address 7680 (the ZX81 character set base), unpacking each 8×8 bitmap bit-by-bit using PEEK and plotting individual pixels via UNPLOT on the lo-res display. Words are extracted one at a time using a space-scanning subroutine at line 1000, and after each word the screen scrolls up by a configurable number of lines (stored in G). When the last word is reached, the subroutine at line 2000 resets S=1 so the message loops continuously and sets G=21 to clear the full screen before restarting. Inverse video characters in Z$ (a string of spaces) are used as a solid fill line to create a clean background during scrolling. *** ## Program Analysis ### Program Structure The program is organised into a main flow and three subroutines: 1. **Initialisation (lines 10–80):** Sets scroll gap `G=4`, word position `S=1`, builds a fill string `Z$` of inverse-video spaces, prompts for input, and appends two trailing spaces to `B$` to ensure the last word is correctly terminated. 2. **Main display loop (lines 100–360):** Clears the screen (GOSUB 3000), extracts a word (GOSUB 1000), scrolls `G` lines, then renders the word pixel-by-pixel using ROM font data before looping back to line 130. 3. **Subroutine 1000 – Word extractor:** Scans `B$` from position `S` for the next space, slices out the word into `A$`, and advances `S`. 4. **Subroutine 2000 – End-of-message detector:** Checks whether the current word index `N` is near the end of `B$`; if so, resets `S=1` and sets `G=21` to do a full-screen clear on the next cycle. 5. **Subroutine 3000 – Screen clear:** POKEs the display file pointer and scrolls 21 lines of `Z$` to fill the screen with inverse-video space. ### ROM Font Access The character bitmaps are read directly from ROM starting at address **7680**, which is the base of the ZX81 character set. Each character occupies 8 consecutive bytes, one per pixel row. The formula used is: `PEEK (7680 + C*8 + L)` where `C` is the character code and `L` is the row (0–7). For characters with codes ≥128 (inverse video), lines 220–240 subtract 128 and set `C=0`, mapping them to the space character—a limitation meaning inverse characters in the input are rendered as blank columns rather than their true glyphs. ### Bit-Unpacking Technique Each font byte is decoded by successively subtracting powers of two. The variable `V` starts at 128 and is halved each iteration. If the current byte value `P` is ≥ `V`, bit 7-J is set: `UNPLOT` is called to draw the pixel and `V` is subtracted from `P`. This is a standard shift-and-mask idiom for BASIC environments lacking bitwise operators. ### Pixel Placement with UNPLOT `UNPLOT` on the ZX81 sets a pixel in the lo-res 64×44 graphics grid. Characters are placed at horizontal position `8*(K-1)+J` and vertical position `10-L`, giving each character an 8-pixel-wide column and rendering rows top-to-bottom by inverting `L`. This effectively doubles the visual size of each character relative to normal text, since the lo-res grid maps roughly 2 PLOT units per character cell row. ### Scrolling Mechanism The ZX81 `SCROLL` keyword moves the display up one line. The loop at lines 170–190 scrolls `G` lines and prints `Z$` (a row of inverse spaces) after each scroll to maintain a solid background. Normally `G=4` (scrolling 4 lines between words); at message wrap it becomes `G=21` to blank the full screen before restarting. ### Screen Initialisation via POKE Line 3000 contains `POKE 16418,2`. Address 16418 is the system variable `DF_SZ` (display file size / lower screen lines). Setting it to 2 minimises the lower screen area, maximising the main display—a common ZX81 technique to reclaim display rows. ### Variable Summary | Variable | Role | | --- | --- | | `S` | Current start position within `B$` for word scanning | | `G` | Number of scroll lines between words (4 normal, 21 at wrap) | | `Z$` | A 64-character string of inverse spaces used as fill line | | `B$` | User-entered message (with two trailing spaces appended) | | `A$` | Current extracted word being displayed | | `C` | Character code of current character in word | | `M$` | Temporary holder when stripping inverse-video flag (≥128) | | `P` | Current font byte being unpacked | | `V` | Bit-mask value (128, 64, 32, … 1) | | `I,J,K,L,N,Z` | Loop counters | ### Bugs and Anomalies - The word-extractor at line 1020 (`NEXT N`) will run past the end of `B$` if no space is found before `LEN B$`, potentially causing a subscript error. The two appended spaces in line 80 mitigate this for most inputs but do not fully protect against it. - `M$` at line 230 is assigned but never used; the intent appears to have been to render the underlying non-inverse character, but `C` is set to 0 instead, so all inverse characters silently become blank columns. - Subroutine 2000 compares `N` against `LEN B$-1`. Because `B$` has two trailing spaces, the boundary check fires one word early, causing the last actual word to trigger the reset condition before it has fully rendered. - The pixel row rendering uses `UNPLOT` (which clears a pixel on ZX81), rather than `PLOT` which would set it. This means the program draws its characters in blank pixels against the inverse-space background—a deliberate inverse-video display effect consistent with the program’s name and the use of `Z$`. ## Source Code ``` 10 LET S=1 20 LET G=4 30 LET Z$="% % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % " 60 PRINT AT 10,0;"TYPE IN A MESSAGE." 70 INPUT B$ 80 LET B$=B$+" " 100 GOSUB 3000 130 GOSUB 1000 170 FOR I=1 TO G 180 SCROLL 185 PRINT Z$ 190 NEXT I 200 FOR K=1 TO LEN A$ 210 LET C=CODE A$(K) 220 IF C<128 THEN GOTO 250 230 LET M$=CHR$ (C-128) 240 LET C=0 250 FOR L=0 TO 7 260 LET P=PEEK (7680+C*8+L) 270 LET V=128 280 FOR J=0 TO 7 290 IF P=LEN B$-1 THEN LET S=1 2020 IF N>=LEN B$-1 THEN LET G=21 2030 RETURN 3000 POKE 16418,2 3010 FOR Z=1 TO 21 3020 SCROLL 3030 PRINT Z$ 3040 NEXT Z 3050 RETURN 9998 SAVE "INVERSCROL%L" 9999 RUN ```