--- title: "Screen Invert" id: 57249 type: "computer_media" slug: "screen-invert" url: "http://localhost/computer_media/screen-invert/" markdown_url: "http://localhost/computer_media/screen-invert.md" published_at: "2024-10-04T07:17:18+00:00" modified_at: "2026-04-03T07:58:40+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/139_ScnI.png" excerpt: "A self-modifying BASIC trick walks raw program memory and flips every character to inverse video — revealing how tokens are stored byte-by-byte in RAM." 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: 56734 title: "Timex Sinclair Public Domain Library Tape 1003" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1003/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/139_ScnI.png" media_type_tags: "Demo" --- This program inverts the video attribute of every character token stored in the current BASIC program listing held in RAM. It walks memory from the start of the BASIC program area (address held in system variables at 16396–16397) to the end (16400–16401), toggling bit 7 of each byte to switch between normal and inverse video, while skipping newline tokens (value 118, which marks end-of-line). The main routine at lines 10–50 lists the program, prints it to the printer, stops, saves, and reruns — forming a self-modifying display loop. The technique exploits the ZX81/TS1000 convention that inverse video characters are simply normal character codes ORed with 128 (bit 7 set). *** ## Program Analysis ### Program Structure The program is split into two functional blocks: a short main routine (lines 10–50) and a memory-walking subroutine (lines 8000–8080). - **Lines 10–50:** Call the inversion subroutine, `LIST` the (now-modified) program to screen, `LLIST` it to printer, `STOP`, `SAVE` the modified version, and `RUN` again. - **Lines 8000–8080:** Traverse every byte of the BASIC program in RAM and toggle bit 7 on each non-newline byte. ### Memory Map Usage The subroutine uses four system variable addresses to locate the program boundaries: | Address | System Variable | Role | | --- | --- | --- | | 16396–16397 | PROG | Start address of BASIC program | | 16400–16401 | VARS | Start of variables area (= end of program) | The two-byte little-endian reads (`PEEK addr + 256*PEEK (addr+1)`) reconstruct the full 16-bit pointer, a standard ZX81/TS1000 idiom for reading system variable word values. ### The Inversion Loop Lines 8010–8080 implement a `GOTO`-based loop. `INCA` is set to 8020 at line 8010, so `GOTO INCA` at line 8080 always jumps back to the increment step at line 8020. This avoids a literal line-number constant in the loop tail, but since `INCA` never changes, it is effectively a fixed `GOTO 8020`. The inversion logic at lines 8060–8070 is: - If the byte value is less than 128 (bit 7 clear → normal character), add 128 to set bit 7, making it inverse. - If the byte value is exactly 128, subtract 128, returning it to 0. This handles only the single case of inverse-space (code 128) → normal space (code 0). - Bytes 129–255 (other inverse characters) are not handled by line 8070 and are left unchanged — a logic gap (see Bugs section). Newline tokens (value 118, `NEWLINE` in ZX81 BASIC) are skipped by line 8050, preserving program line structure. ### Self-Modifying Behaviour Because the subroutine operates on the live program bytes in RAM — including its own lines — running it a second time (via `RUN` at line 50 after `SAVE`) would toggle all the bytes back, making the transformation fully reversible. The `SAVE` at line 40 captures the inverted state to tape. ### Notable Techniques - Using a variable (`INCA=8020`) as a `GOTO` target is a form of computed jump, though here the value is constant. - The `PKA` variable caches `PEEK AMEM` at line 8040, but line 8050 redundantly re-evaluates `PEEK AMEM` rather than using `PKA` — a minor inefficiency. - The main loop at lines 10–50 forms a tape-based iterative cycle: list → print → stop → save → run → repeat. ### Bugs and Anomalies - **Incomplete inverse reversal:** Line 8070 only un-inverts code 128 (inverse space). Any other already-inverse character (codes 129–255) passes through line 8060 untouched (condition `PKA<128` is false) and also misses line 8070 (`PKA=128` is false), so it is left as-is. A correct toggle would need `IF PKA>128 THEN POKE AMEM,(PKA-128)`. - **Off-by-one start:**`AMEM` is initialised to `PROG+1` (incremented at line 8020 before the first byte is examined), so the very first byte of the program is never processed. - **Line-number bytes also toggled:** ZX81 BASIC line format stores a two-byte line number followed by a two-byte length before the tokens. These structural bytes are not distinguished from character tokens and will also have bit 7 flipped, potentially corrupting line numbers on a second pass. ## Source Code ``` 10 LIST 20 GOSUB 8000 25 LLIST 30 STOP 40 SAVE "1013%9" 50 RUN 8000 LET AMEM=PEEK 16396+256*PEEK 16397 8010 LET INCA=8020 8020 LET AMEM=AMEM+1 8030 IF AMEM=PEEK 16400+256*PEEK 16401 THEN RETURN 8040 LET PKA=PEEK AMEM 8050 IF PEEK AMEM=118 THEN GOTO INCA 8060 IF PKA<128 THEN POKE AMEM,(PKA+128) 8070 IF PKA=128 THEN POKE AMEM,(PKA-128) 8080 GOTO INCA ```