Alternate Character Set

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

This program generates an alternate character set for the ZX Spectrum by copying the ROM character data to a user-specified RAM address (either 31488 for 16K machines or 64000 for 48K machines) and then modifying each byte with a series of bit-shifting additions. It reads the current character set base address from system variables at addresses 23606 and 23607, then iterates over 1025 bytes, applying conditional transformations based on divisibility tests that effectively set additional bits in each character bitmap row. After generation, it POKEs address 23607 to redirect the system to use the new character set and prints instructions for both switching to the alternate set and reverting to normal. The program also instructs the user how to SAVE the 1024-byte character data as a CODE block and how to use CLEAR to preserve the data above RAMTOP after NEWing the BASIC program.


Program Analysis

Program Structure

The program is divided into three logical phases separated by REM statements. Lines 30–90 handle initialization: reading the current character set pointer from system variables and prompting the user to choose between 16K and 48K memory configurations. Lines 100–190 perform the character set generation loop. Lines 200–290 display usage instructions and demonstrate switching between the alternate and normal character sets.

Character Set Pointer Mechanics

Line 30 reads the 16-bit character set base address from system variables 23606 (low byte) and 23607 (high byte) using PEEK 23606 + 256 * PEEK 23607. This is the standard ZX Spectrum idiom for reading a two-byte little-endian word. The character set pointer normally points 256 bytes before the actual character data (the system skips the first 32 non-printable characters), so the pointer at 23607 encodes base_address / 256. Line 210 and 240 manipulate only the high byte (23607) as a shortcut, which works correctly only when the target addresses are page-aligned — 31488 = 123×256 and 64000 = 250×256, both of which are exact multiples of 256, making this safe.

The Generation Loop

Lines 110–190 form a single FOR/NEXT loop from C to C+1024, iterating 1025 times (covering 1024 bytes of character data plus one extra iteration — a minor off-by-one). Each byte B is read from the ROM character set via PEEK A and written to RAM at address N.

The modification logic in lines 120–170 applies additive bit manipulations based on divisibility tests. Divisibility by a power of 2 is used as a proxy for testing whether a specific bit is clear:

  • Line 120: if B is divisible by 4, bit 1 is clear → add 2 (set bit 1)
  • Line 130: if divisible by 8, bit 2 is clear → add 4
  • Line 140: if divisible by 16, bit 3 is clear → add 8
  • Line 150: if divisible by 32, bit 4 is clear → add 16
  • Line 160: if divisible by 64, bit 5 is clear → add 32
  • Line 170: special case for B=66 → add 32
  • Line 180: if B=0, force the byte back to 0 (undo any accumulated additions)

This logic effectively ORs in additional bits, thickening the strokes of each character glyph. However, because the additions are applied cumulatively to POKE N rather than to a running accumulator, each conditional tests the original B value but the POKE overwrites the result of the previous POKE — meaning only the last satisfied condition’s addition takes effect. This is a significant behavioral anomaly: the modifications do not accumulate.

Notable Bugs and Anomalies

  • Off-by-one in loop range: FOR N=C TO C+1024 iterates 1025 times, but a character set is exactly 768 bytes (96 printable characters × 8 bytes each). The loop runs well past the character data, writing transformed ROM bytes into adjacent memory.
  • Non-accumulating POKEs: Lines 120–160 each POKE N independently using the original B, not the previously modified value, so only the last triggered condition is preserved in RAM.
  • High-byte-only pointer update: Line 210 sets POKE 23607, C/256 and line 240 restores it with POKE 23607, 60 (the normal value). This works only because both destination addresses happen to be exact multiples of 256; the low byte at 23606 is left unchanged throughout.
  • Special case B=66: Decimal 66 is the byte value 01000010, corresponding to a specific ROM character row pattern. This hardcoded special case is unexplained and suggests manual tuning of a particular glyph.

User Instructions Embedded in Output

Lines 220–280 print a self-contained usage guide directly to the screen, telling the user the exact POKE commands needed to activate and deactivate the alternate character set, as well as the SAVE command syntax for preserving the 1024-byte block as a machine code file. Line 290 uses PAUSE 0 followed by CLEAR C and STOP to wait for a keypress, then move RAMTOP to protect the generated character data in RAM even after the BASIC program is NEWed.

Key BASIC Idioms

LineIdiomPurpose
30PEEK 23606 + 256 * PEEK 23607Read 16-bit little-endian word from system variables
60IF RAM <>16 AND RAM <>48 THEN GO TO 50Input validation loop
120–160Divisibility test as bit-clear checkSubstitute for bitwise AND in standard BASIC
290PAUSE 0 then CLEAR C:STOPWait for keypress, protect RAM, halt execution
300SAVE "ALT CHAR" LINE 1Save with auto-run at line 1

Content

Appears On

Related Products

Related Articles

Related Content

Image Gallery

Source Code

  10 REM ALTERNATE CHARACTER SET
  30 LET A= PEEK 23606+256* PEEK 23607
  40 PRINT AT 21,2;"ENTER MEMORY SIZE"
  50 INPUT "16 OR 48 ";RAM
  60 IF RAM <>16 AND RAM <>48 THEN GO TO 50
  70 PRINT AT 20,6;"PLEASE WAIT 2 MINUTES        CHARACTER SET BEING GENERATED."
  80 IF RAM=16 THEN LET C=31488
  90 IF RAM=48 THEN LET C=64000
 100 REM >>TRANSFER AND MODIFY<<
 110 FOR N=C TO C+1024:LET B= PEEK A:POKE N,B
 120 IF B/4= INT (B/4) THEN POKE N,B+2
 130 IF B/8= INT (B/8) THEN POKE N,B+4
 140 IF B/16= INT (B/16) THEN POKE N,B+8
 150 IF B/32= INT (B/32) THEN POKE N,B+16
 160 IF B/64= INT (B/64) THEN POKE N,B+32
 170 IF B=66 THEN POKE N,B+32
 180 IF B=0 THEN POKE N,0
 190 LET A=A+1:NEXT N:BEEP .1,25
 200 REM >>DISPLAY RESULTS<<
 210 CLS :POKE 23607,C/256
 220 PRINT AT 2,9;"POKE 23607,";C/256
 230 PRINT '"TO OBTAIN THIS CHARACTER SET."
 240 POKE 23607,60
 250 PRINT AT 8,9;"POKE 23607,60"
 260 PRINT '"TO RETURN TO NORMAL CHARACTERS."
 270 PRINT AT 14,0;"SAVE:-SAVE NAME CODE ";C;",1024"
 280 PRINT ''"THIS BASIC PROGRAM MAY BE ""NEWED"" LEAVING SUB-ROUTINE ABOVE RAMTOP"''"     PRESS ANY KEY TO STOP"
 290 PAUSE 0:CLEAR C:STOP 
 300 SAVE "ALT CHAR" LINE 1

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

People

No people associated with this content.

Scroll to Top