--- title: "Newscript" id: 56246 type: "computer_media" slug: "newscript" url: "http://localhost/computer_media/newscript/" markdown_url: "http://localhost/computer_media/newscript.md" published_at: "2024-07-22T01:39:33+00:00" modified_at: "2026-03-30T21:44:18+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A self-destructing font generator that rewrites the ROM character set byte-by-byte into RAM, then hands you a bolder typeface and vanishes." 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: "Font" slug: "font" taxonomy: "genre" url: "http://localhost/type/font/" media_contents: - id: 56205 title: "CATS Library Tape 11" type: "computer_media" url: "http://localhost/computer_media/cats-library-tape-11/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Newscript%20%28198x%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" media_type_tags: "Font" --- # Newscript This program generates a custom character set font by reading the built-in ROM character data (addresses 15616–16383) and transforming individual byte values through a series of substitution rules before storing the results at address 64000. The transformation logic applies piecewise replacements to each byte — for example, value 32 becomes 48, value 64 becomes 96 — effectively widening or shifting the pixel patterns to create a bolder or enhanced typeface. System variables at 23606/23607 (CHARS low/high bytes) are then POKEd to point to the new data at 64000 (offset by 256), redirecting the ROM to use the custom font. After printing instructions and producing two hardcopy listings via COPY (one with the new font, one with the standard font at 23607=60), the program calls NEW to self-destruct, leaving only the custom character data in RAM. The font can be preserved with SAVE “text” CODE 64000,1536, which also captures the UDG area. *** ## Program Analysis ### Program Structure The program is organised into four broad phases: 1. **Initialisation (lines 1–23):** A short startup fanfare via `BEEP`, screen setup, a “please wait” message, and a temporary adjustment to the `CHARS` system variable. 2. **Font generation – printable ASCII range (lines 40–210):** Iterates over ROM bytes 15616–16135 (characters 32–127, the normal printable set), applies substitution rules to each byte, and writes the result to 64000 onward. 3. **Font generation – upper range (lines 215–520):** Iterates over ROM bytes 16136–16383 (the remaining character data / token characters), applies a separate but similar set of substitution rules, writing from address 64520 onward. 4. **Output and self-destruction (lines 620–860, subroutines 1000–2030):** Redirects the CHARS pointer to the new data, displays instructions, produces two COPY hardcopies (one enhanced, one normal), and ends with `NEW`. ### System Variable Manipulation The ZX Spectrum/TS2068 system variable `CHARS` is a two-byte pointer at addresses 23606–23607. It normally points 256 bytes before the start of the ROM character table so that `CHR$ 32` (space) maps to offset 0. The program exploits this in several ways: | Line(s) | POKE | Effect | | --- | --- | --- | | 2, 23 | 23609,125 / 23607,150 | Temporary adjustments during generation (23609 is FLAGS2; 23607 alone shifts the font mid-run) | | 620–630 | 23607,249 / 23606,0 | Points CHARS to 63999+1 = 64000, activating the new font (249 × 256 = 63744; +256 offset = 64000) | | 810 | 23607,60 | Points CHARS back toward ROM standard font for the second COPY comparison | ### Byte-Substitution Font Transformation The core algorithm (lines 80–210 and 220–520) reads each byte of the ROM font data with `PEEK`, passes it through a chain of `IF` comparisons, and substitutes a replacement value. This is not a mathematical transform but a hand-crafted lookup table encoded as sequential `IF` statements. Because multiple `IF`s can match the same variable in a single pass (BASIC evaluates each `IF` line independently), there is a subtle bug risk: a substituted value could be matched and re-substituted by a later rule on the same iteration. For example in the first loop, line 91 changes 128→192, and no later rule matches 192, so this is safe; but line 92 has a compound statement (`IF b=98 THEN LET b=102: IF b=82 THEN LET b=114`) where the second condition can never trigger because the first assignment changes `b` to 102 before evaluation — the `IF b=82` clause is unreachable as written. ### Memory Layout | Address range | Purpose | | --- | --- | | 15616–16135 | ROM character bitmaps for CHR$ 32–127 (printable ASCII) | | 16136–16383 | ROM character bitmaps, upper portion | | 64000–64519 | New font data for printable ASCII range (written by first loop) | | 64520–64999 | New font data for upper range (written by second loop) | | 64000–65535 | Protected by `CLEAR 63999` (line 10) so BASIC heap cannot overwrite it | ### Notable Techniques - **Self-destruction via `NEW`:** Line 860 calls `NEW`, which wipes the BASIC program and variables but leaves the font data above `RAMTOP` (set by `CLEAR 63999`) intact. - **Dual hardcopy:** Lines 800 and 840 each call `COPY` to produce a printer listing of the instructions — one with the new font active, one with the standard ROM font — for direct comparison on paper. - **Descending BEEP scale:** Subroutine 2000 plays a descending glide from note −20 to −30 as a pacing separator between instruction paragraphs. - **Startup fanfare:** Lines 3 uses five `BEEP` calls at pitches 0, 12, 24, 36, 48 semitones (a two-octave chromatic ascent in octave steps) as an audio cue that the program has started. - **Zero-initialisation pass:** Lines 40–60 zero all 1001 bytes from 64000 to 65000 before writing font data, ensuring unused positions default to blank rather than random RAM content. - **Author credit:** Line 650 embeds `"@ m.p.biddell"` in the confirmation message. ### Bugs and Anomalies - **Dead compound condition (line 92):**`IF b=98 THEN LET b=102: IF b=82 THEN LET b=114` — after `b` is set to 102, the condition `b=82` is always false, so the `LET b=114` branch is never executed. - **Possible re-substitution:** In the second loop, several substitution chains could theoretically cascade (e.g. 48→56 at line 243, then 56→60 at line 242 on a later iteration), but since each byte is processed exactly once per loop iteration and the `IF`s are on separate lines, only one substitution per byte value can fire — the risk is real but limited to values that are both a source and a target within the same pass. - **Gap at 64520:** The second loop starts writing at `a=64520` (line 215), leaving bytes 64500–64519 as zeros from the initialisation pass, creating a small gap between the two font segments. ## Source Code ``` 1 REM "CHR$ gen" 2 POKE 23609,125 3 BEEP .2,0: BEEP .2,12: BEEP .2,24: BEEP .2,36: BEEP .2,48 10 CLEAR 63999 15 PAPER 0: BORDER 0: CLS 20 PRINT AT 10,0; INK 7; FLASH 1; PAPER 1;"PLEASE WAIT-CREATING NEW SCRIPT" 23 POKE 23607,150 40 FOR j=64000 TO 65000 50 POKE j,0 60 NEXT j 70 LET a=64000 80 FOR j=15616 TO 16135 90 LET b=(PEEK j) 91 IF b=128 THEN LET b=b+64 92 IF b=98 THEN LET b=102: IF b=82 THEN LET b=114 93 IF b=70 THEN LET b=102 101 IF b=68 THEN LET b=b+40 105 IF b=66 THEN LET b=102 110 IF b=64 THEN LET b=b+32 120 IF b=32 THEN LET b=b+16 130 IF b=16 THEN LET b=b+8 135 IF b=8 THEN LET b=b+4 140 IF b=4 THEN LET b=b+2 150 IF b=2 THEN LET b=b+4 160 IF b>255 THEN GO TO 200 170 IF b=0 THEN GO TO 200 180 POKE a,b 200 LET a=a+1 210 NEXT j 215 LET a=64520 220 FOR j=16136 TO 16383 222 LET b=PEEK j 230 IF b=120 THEN LET b=124 231 IF b=146 THEN LET b=214 232 IF b=84 THEN LET b=214 233 IF b=104 THEN LET b=238 234 IF b=96 THEN LET b=112 235 IF b=68 THEN LET b=102 238 IF b=64 THEN LET b=96 240 IF b=60 THEN LET b=126 242 IF b=56 THEN LET b=60 243 IF b=48 THEN LET b=56 244 IF b=40 THEN LET b=44 245 IF b=36 THEN LET b=38 248 IF b=34 THEN LET b=102 250 IF b=32 THEN LET b=96 255 IF b=28 THEN LET b=60 260 IF b=24 THEN LET b=30 265 IF b=16 THEN LET b=24 270 IF b=12 THEN LET b=30 280 IF b=4 THEN LET b=6 500 POKE a,b 510 LET a=a+1 520 NEXT j 620 POKE 23607,249 630 POKE 23606,00 635 PAPER 0 636 BORDER 0 640 CLS 650 PRINT AT 10,0; INK 5; PAPER 0;"CHARACTERS loaded-@ m.p.biddell" 660 PRINT 670 PAUSE 200 680 CLS 690 GO SUB 1000 705 PAUSE 100 800 COPY 810 POKE 23607,60 815 CLS 820 GO SUB 1000 840 COPY 850 PAUSE 100 860 NEW 1000 PRINT INK 6;"INSTRUCTIONS-NEW CHARACTER FONT" 1010 GO SUB 2000 1020 PRINT INK 5;"1. After this program has self destructed, poke 23607,249 to turn on the new character set." 1030 GO SUB 2000 1040 PRINT INK 5;"2. Then load your own program inthe normal way, Poke 23607,249 and it is instantly converted to the new text. Now use LLIST to see the enhanced output from the 2040-printer." 1050 GO SUB 2000 1060 PRINT INK 5;"3. Now Poke 23607,60 and LLIST .Compare this normal listing withthe enhanced one." 1070 GO SUB 2000 1080 PRINT INK 5;"4. SAVE 'text'CODE ,64000,1536 (this also saves U.D.G's.)" 1090 GO SUB 2000 1100 RETURN 2000 PRINT : FOR j=20 TO 30 2010 BEEP .1,-j 2020 NEXT j 2030 RETURN ```