INVERSION

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Font

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 PointPurpose
30–240Vertical inversion of ROM character set
250–270Revert to standard ROM font
220–240Redirect character pointer (also reached from inversion)
280–450Load custom lowercase character set via DATA
460–470Destroy 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 of b at 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*8 uses the double-colon statement separator (valid on the Spectrum) to chain two assignments on one line, saving a line number.
  • Arrays d and e are redimensioned inside the loop at line 120/160, which is inefficient but functionally correct since DIM re-creates the array each iteration.

Potential Bugs and Anomalies

  • The variable e is used both as a loop counter (FOR e=c TO c+7 at line 130, and FOR e=1 TO 8 at 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

Appears On

Related Products

Related Articles

Related Content

Image Gallery

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.

People

No people associated with this content.

Scroll to Top