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
Bis 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+1024iterates 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
Nindependently using the originalB, 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/256and line 240 restores it withPOKE 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
| Line | Idiom | Purpose |
|---|---|---|
| 30 | PEEK 23606 + 256 * PEEK 23607 | Read 16-bit little-endian word from system variables |
| 60 | IF RAM <>16 AND RAM <>48 THEN GO TO 50 | Input validation loop |
| 120–160 | Divisibility test as bit-clear check | Substitute for bitwise AND in standard BASIC |
| 290 | PAUSE 0 then CLEAR C:STOP | Wait for keypress, protect RAM, halt execution |
| 300 | SAVE "ALT CHAR" LINE 1 | Save with auto-run at line 1 |
Content
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.
