INVERSION is a font-manipulation utility that offers four distinct operations: vertically flipping the ROM character set to create an “inverted” display, reverting to the standard font, loading a custom lowercase alphabet, and self-destructing via a USR 0 call. The inversion routine copies the 768-byte ROM character set (addresses 15616–16384) to RAM at address 64368, then reverses the eight scan-line bytes of each character so glyphs appear upside-down. System variables at addresses 23606–23607 are then patched to point the Spectrum’s character generator to the RAM copy rather than the ROM. The custom character set in the NEW Chrs routine uses DATA statements containing the variable b as a substitute for zero, reducing the size of the DATA block by using a short variable name instead of repeated numeric literals.
Program Structure
The program is organized as a menu-driven multi-entry utility. Line 10 prints instructions listing the available GO TO entry points, and line 20 halts execution so the user can manually jump to the desired routine. Each routine is clearly delimited by a REM banner at its start and a STOP at its end.
| Entry Point | Purpose |
|---|---|
| 30–240 | Vertical inversion of ROM character set |
| 250–270 | Revert to standard ROM font |
| 220–240 | Redirect character pointer (also reached from inversion) |
| 280–450 | Load custom lowercase character set via DATA |
| 460–470 | Destroy program with RANDOMIZE USR 0 |
Font Inversion Routine (Lines 30–240)
The routine begins by copying 769 bytes of ROM character data from addresses 15616–16384 into RAM starting at 64368 using POKE/PEEK. It then iterates over the 96 printable ASCII characters (32–127). For each character, it reads the eight scan-line bytes into array d(8), reverses their order into array e(8), and writes them back. This flips every glyph vertically — the top scan-line of each character becomes the bottom, producing an upside-down appearance.
After modification, lines 220–230 patch system variables at 23606 and 23607 to the value 250*256+112 = 64112… actually POKE 23606,112: POKE 23607,250 sets the 16-bit pointer to 250*256+112 = 64112. The Spectrum stores the UDG/character base address minus 256 in these two bytes (low byte first), so the effective character base becomes 112 + 250*256 = 64112, and adding 256 gives 64368 — exactly where the RAM copy starts. This redirects the character generator away from ROM.
Revert Routine (Lines 250–270)
Restoring the standard font simply resets the same two system variables: POKE 23606,0: POKE 23607,60 sets the pointer back to 60*256 = 15360, and adding 256 gives 15616 — the start of the ROM character set.
Custom Lowercase Routine (Lines 280–450)
This routine again copies the ROM font to 64368, then overwrites 207 bytes starting at offset 264 from the base (line 350: LET x=64368+264). Offset 264 corresponds to character code 32 + 264/8 = 65 — capital ‘A’ — though the REM comment in line 360 notes that changing 520 to 264 selects uppercase. The DATA covers 26 characters × 8 bytes minus one = 207 bytes, providing a custom glyph set read byte by byte into RAM.
A notable technique in the DATA statements: the variable b appears extensively as a data value. At the point where DATA is READ, b holds whatever numeric value it was last assigned — in context this is expected to be 0 (its value after the copy loop completes, since the loop ends with b=769… actually b ends at 769 after the copy). This is likely a bug or oversight: b is not explicitly reset to 0 before the READ loop, so the DATA values represented by b will be 769 rather than 0, corrupting the custom character bitmaps. The programmer likely intended b to equal 0 as a compact way to embed zero bytes in DATA without typing many literal zeros.
Self-Destruct Routine (Lines 460–470)
RANDOMIZE USR 0 calls the address 0 in ROM, which on the Spectrum performs a full restart, effectively clearing the running program from memory.
Notable Techniques
- Dual-use of line 220: the inversion routine falls through to line 220 to set the character pointer, and line 220 is also listed as a standalone entry point in the menu — though jumping to 220 without first running the copy/inversion loop would redirect to uninitialized or stale RAM.
- Using a variable name (
b) inside DATA statements as a shorthand for a repeated value is an unconventional idiom; READ interprets it as a numeric expression, evaluating the current value ofbat runtime. - The copy loop uses
FOR a=15616 TO 16384, which is 769 iterations, copying one byte beyond the 768-byte character table — a minor off-by-one that is unlikely to cause harm. - The line
110 LET q=a-32::LET b=q*8uses the double-colon statement separator (valid on the Spectrum) to chain two assignments on one line, saving a line number. - Arrays
dandeare redimensioned inside the loop at line 120/160, which is inefficient but functionally correct sinceDIMre-creates the array each iteration.
Potential Bugs and Anomalies
- The variable
eis used both as a loop counter (FOR e=c TO c+7at line 130, andFOR e=1 TO 8at line 160) and as an array name (DIM e(8)at line 160). On the TS2068, a numeric variable and a numeric array of the same single-letter name coexist in separate namespaces, so this is valid, if confusing.
Content
Source Code
10 CLS :PRINT ''"Enter:"''"GO TO 30 for inversion"''"GO TO 250 to revert to normal"''"GO TO 220 for inversion"''"GO TO 280 for New Chrs"''"GO TO 460 to destroy program"
20 STOP
30 REM \{20}\{1} Inversion \{20}\{0}
40 CLEAR 64367
50 BEEP .2,-10:BEEP .4,20
60 PRINT AT 10,5; PAPER 1; INK 7;"\{18}\{1}Please wait a minute...\{18}\{0}"
70 LET mem=64368
80 LET b=0:FOR a=15616 TO 16384
90 POKE mem+b, PEEK a
100 LET b=b+1:NEXT a
110 FOR a=32 TO 127:LET q=a-32::LET b=q*8:LET c=mem+b
120 DIM d(8):LET b=1
130 FOR e=c TO c+7
140 LET d(b)= PEEK e
150 LET b=b+1:NEXT e
160 DIM e(8):FOR e=1 TO 8
170 LET e(e)=d(9-e)
180 NEXT e
190 LET b=1:FOR e=c TO c+7
200 POKE e,e(b)
210 LET b=b+1:NEXT e:NEXT a
220 POKE 23606,112:POKE 23607,250
230 BEEP .2,20:BEEP .4,-10
240 STOP
250 REM \{20}\{1} revert to normal \{20}\{0}
260 POKE 23606,0:POKE 23607,60
270 STOP
280 REM \{20}\{1} NEW Chrs \{20}\{0}
290 CLEAR 64367
300 BEEP .2,-5:BEEP .4,15
310 PRINT AT 10,5; FLASH 1; PAPER 1; INK 7;"Please wait 20 seconds..."
320 LET b=0:FOR a=15616 TO 16384
330 POKE 64368+b, PEEK a
340 LET b=b+1:NEXT a
350 LET x=64368+264
360 REM change 520 to 264 in line 350 for upper case letters
370 FOR a=x TO x+206
380 READ b:POKE a,b
390 NEXT a
400 POKE 23606,112:POKE 23607,250
410 DATA 0,124,76,b,124,76,b,0,b,124,100,b,124,100,124,0,b,124,76,64,76,b,124,0,b,124,76,b,b,b,124,0,b,124,96,b,124,96,124,0,b,124,96,b,124,96,b,0,b,124,76,64,92,76,124,0,b,100,b,b,124,100,b,0,b,24,b,b,b,b,b,0,b
420 DATA 12,b,b,b,b,60,0,b,100,b,b,124,76,b,0,b,96,b,b,b,b,120,0,b,126,90,b,b,b,b,0,b,124,76,b,b,b,b,0,b,124,100,b,b,b,124,0,b,124,100,b,124,64,b,0,b,124,100,b,b,b,126,0,b,124,100,b,124,76,b,0,b,124,100,96,28,76,124,0,b,126,24,b,b,b,b,0,b
430 DATA 100,b,b,b,b,124,0,b,100,b,b,b,b,56,0,b,90,b,b,b,b,126,0,b,100,b,b,124,92,b,0,b,104,b,b,120,16,b,0,b,124,100,28,32,76,124
440 BEEP .2,15:BEEP .4,-5
450 STOP
460 REM \{20}\{1} destroy program \{20}\{0}
470 RANDOMIZE USR 0
500 SAVE "INVERSION" LINE 10
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
