--- title: "New CHR$" id: 55137 type: "computer_media" slug: "new-chr" url: "http://localhost/computer_media/new-chr/" markdown_url: "http://localhost/computer_media/new-chr.md" published_at: "2024-06-17T00:22:05+00:00" modified_at: "2026-03-30T21:44:18+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/06/SCR-20240616-rqoy.png" excerpt: "Flip your entire character set upside-down, load hand-crafted letter shapes, or restore the ROM font — all by jumping to a single line number." 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 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Demo" slug: "demo" taxonomy: "genre" url: "http://localhost/type/demo/" media_contents: - id: 54965 title: "ISTUG Public Domain Library 5" type: "computer_media" url: "http://localhost/computer_media/istug-public-domain-library-5/" - id: 54964 title: "ISTUG Public Domain Library 6" type: "computer_media" url: "http://localhost/computer_media/istug-public-domain-library-6/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/06/SCR-20240616-rqoy.png" - url: "http://localhost/wp-content/uploads/2024/06/SCR-20240616-rrmi.png" - url: "http://localhost/wp-content/uploads/2024/06/SCR-20240616-rrac.png" media_type_tags: "Demo" --- # New CHR$ This program offers several character-set manipulation utilities, accessible by jumping to specific line numbers. The main routine (line 30) copies the ROM character set from address 15616–16384 into RAM at 64368, then reverses the vertical order of each character’s eight pixel rows, producing an upside-down font. It then redirects the system variable CHARS (held at addresses 23606–23607) to point to the new RAM-based table at 64368 (stored as the two-byte value 64368−256 = 250×256+112, i.e. POKE 23606,112: POKE 23607,250), making the inverted set live immediately. The “New Chrs” routine (line 280) instead replaces the lowercase letter definitions (offset 264 bytes into the table, covering characters from ASCII 65 onward) with custom pixel data supplied via DATA statements, each row encoded with the literal variable `b` used as a zero placeholder. Line 460 uses RANDOMIZE USR 0 to crash/reset the machine, and lines 500–510 provide LOAD and SAVE commands for persisting the custom character data as a CODE block. *** ## Program Analysis ### Program Structure The program is a menu-driven collection of independent utilities, each entered by jumping to a labeled line. Line 10 prints a directory of entry points, and line 20 halts execution if the program is simply RUN from the start. Each routine ends with `STOP`, so routines are self-contained. | Entry Line | Routine | Description | | --- | --- | --- | | 30 | Inversion | Copies ROM font to RAM, reverses row order of every glyph, redirects CHARS pointer | | 220 | Apply inversion pointer | Only POKEs CHARS to point to 64368; skips the copy/transform work | | 250 | Revert to normal | Restores CHARS pointer to ROM default (60×256 = 15360, offset by −256 → 23606=0, 23607=60) | | 280 | New Chrs | Copies ROM font to RAM, then overwrites lowercase glyph data with custom DATA | | 460 | Destroy program | Executes `RANDOMIZE USR 0` to force a hard reset | ### Font Inversion Technique (Lines 30–210) The ZX Spectrum ROM character bitmaps occupy addresses 15616–16383 (768 bytes, covering ASCII 32–127, 8 bytes per character). The routine copies this block byte-for-byte into RAM at 64368. It then iterates over each of the 96 characters: for each character it reads its 8 row bytes into array `d(8)`, writes them back in reverse order into array `e(8)` (line 170: `LET e(e)=d(9-e)`), and POKEs the reversed bytes back to the RAM copy. This vertically flips every glyph. ### CHARS System Variable Manipulation The system variable CHARS is stored at addresses 23606–23607 as a 16-bit little-endian value equal to the character table base address minus 256 (because the Spectrum calculates the glyph address as `CHARS + 8*(code-32) + 256`). The ROM default is 15360 (= 15616 − 256), stored as POKE 23606,0 : POKE 23607,60 (60×256 = 15360). Redirecting to the RAM copy at 64368 requires storing 64112 (= 64368 − 256), which is 112 + 250×256, giving POKE 23606,112 : POKE 23607,250. ### New Characters Routine (Lines 280–450) After copying the ROM font to 64368, this routine uses `LET x=64368+264` to target the lowercase region (character code 65+32=97 starts at offset 264; the comment on line 360 notes that changing 264 to 520 would target uppercase). It then RESTOREs the DATA pointer and reads 207 bytes of custom pixel data via a FOR/READ/POKE loop, overwriting those glyphs with hand-drawn bitmaps. The DATA statements use the literal variable name `b` as a zero placeholder (since at this point in execution `b` holds 0 from the copy loop). This is an unusual idiom: reading a variable name from a DATA statement would normally cause a syntax error in Spectrum BASIC, so `b` here actually refers to the numeric value 0 written symbolically as the letter `b` — but in practice this only works because `READ b` at line 380 reads numeric DATA, and the DATA values labeled `b` are in fact the numeric token for the letter b being interpreted as the number stored in variable `b`. This is a notable quirk and could be fragile if `b` were not zero. ### RESTORE and DATA Pointer Reset Line 370 calls `RESTORE` without a line number, resetting the DATA pointer to the first DATA statement in the program. Because the first DATA statements appear at line 410, the loop correctly reads the custom glyph bytes. A second set of DATA statements at line 480 (inside the “destroy” section) is never reached by the restore/read loop and appears to be an earlier or alternate version of the glyph data left in the program. ### Notable Anomalies and Issues - The variable conflict on line 160–180: `e` is used both as an array (`DIM e(8)`) and as a `FOR` loop variable (`FOR e=1 TO 8`). In Spectrum BASIC, a numeric variable and an array with the same single-letter name can coexist, so this is legal, though confusing. - Similarly on line 110, `q` is assigned but the double colon (`::`) at the end of the line is a Spectrum BASIC separator (equivalent to `:`), not a block-graphic escape — it simply separates statements. - The copy loop range is 15616 TO 16384 (769 bytes), which is one byte more than the 768-byte ROM font. This extra byte is harmless but unnecessary. - `CLEAR 64367` is used to both protect the RAM area at 64368 from BASIC and to reset the stack pointer, which is standard practice for machine-code or data areas placed above RAMTOP. - Lines 500–510 provide LOAD and SAVE commands as executable lines but they are only reachable manually; SAVE at line 510 saves the BASIC program itself with a LINE 10 autostart, while the CODE SAVE is shown only in the trailing REM, requiring manual editing to use. ### Destroy Routine (Line 460) `RANDOMIZE USR 0` jumps to address 0 in ROM, which is the cold-start entry point, effectively performing a hard reset and clearing the BASIC program from RAM. This is a common trick used as a dramatic “self-destruct” mechanism. ## 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 250 to revert to normal"''"GO TO 460 to destroy program" 20 STOP 30 REM Inversion 40 CLEAR 64367 50 BEEP .2,-10: BEEP .4,20 60 PRINT AT 10,5; PAPER 1; INK 7;"Please wait a minute..." 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 revert to normal 260 POKE 23606,0: POKE 23607,60 270 STOP 280 REM New Chrs 290 CLEAR 64367 300 BEEP .2,-5: BEEP .4,15 310 PRINT AT 10,5; 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 RESTORE : 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,76,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 destroy program 470 RANDOMIZE USR 0 480 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,b,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 490 REM *********************** NEW CHR$ *********************** 500 LOAD "NEW CHR$"CODE 64368,264 510 SAVE "NEW CHR$" LINE 10: REM SAVE "NEW CHR$"CODE 64368,264 ```