CONFUSION is a utility that allows users to alter the visible (text) representation of numeric literals in a BASIC program without changing the underlying 5-byte floating-point value stored internally. It walks through each line of the program in memory, starting from PROG (system variable 23635/23636), decoding tokenized BASIC byte by byte and pausing whenever it encounters a number token (byte value 14) to prompt the user for a replacement display string. A 5-byte machine code routine, POKEd to a user-supplied address, is used to print Spectrum keyword tokens directly from their byte values. The utility also permits renumbering individual line numbers by overwriting the two-byte line number field at the start of each line, making those changes permanent in memory.
Program Analysis
Program Structure
The program enters via line 8800, which immediately jumps to the initialization subroutine at 9990. Control then returns to 9902, which begins the main loop that walks the tokenized BASIC program in memory line by line. The code is organized into several logical subroutines:
9990–9995: Initialization — prints the introduction, reads the user-supplied address for the machine code routine, POKEs 5 bytes of machine code there, and sets up system variable pointers.9996–9999: Introduction text display subroutine.9902–9918: Main loop — reads line number and length from the current PROG pointer, offers optional line-number renaming, then iterates over the line’s bytes.9920–9946: Per-byte dispatch — handles string literals, keyword tokens, printable ASCII ranges, number tokens (byte14), and control codes.9950–9970: Number-token handler — uses the machine code routine to print the keyword, shows the true floating-point value alongside the text representation, and allows the user to overwrite the text digits.9986–9988: Cursor position save/restore helpers using system variables23688/23689.
Machine Code Usage
Because BASIC alone cannot easily print a Spectrum keyword token as text, the program asks the user to supply a safe 5-byte address and POKEs the following machine code there:
| Offset | Byte (decimal) | Instruction |
|---|---|---|
| 0 | 62 | LD A, n (opcode; operand is patched at offset 1) |
| 1 | 0 | Operand — patched via POKE usr+1, Y-165 |
| 2 | 195 | JP nn |
| 3 | 69 | Low byte of target address (0x0745 = ROM keyword-print routine) |
| 4 | 7 | High byte of target address |
Before calling RANDOMIZE USR usr, the program patches the operand of LD A with Y-165, converting the token byte to the zero-based keyword index expected by the ROM routine at address 0x0745. This is called both in the keyword-printing path (9928) and in the number-token handler (9950).
Tokenized BASIC Line Format
The Spectrum stores each BASIC line as: 2 bytes of line number (big-endian), 2 bytes of line length (little-endian), then the tokenized bytes of the statement terminated by 0x0D (13). Numeric literals in the source are stored as their ASCII digit characters followed by a byte 14 (the number token) and then 5 bytes of floating-point value. The program uses PEEK PROG / PEEK (PROG+1) for the big-endian line number and PEEK (PROG+2) + 256*PEEK (PROG+3) for the little-endian length, advancing PROG by K+4 after each line.
Key BASIC Idioms and Techniques
- Self-modifying dispatch via POKE + USR: Rather than a lookup table, the token printer patches a single byte in machine code and calls it repeatedly — an efficient technique given BASIC’s lack of indirect calls.
- System variable access:
VARS(address from23627/23628) is used to expose the 5-byte floating-point value to the user at line9958; cursor position is saved and restored through23688/23689. - Text vs. real value separation: When the user enters replacement digits at
9970, only the ASCII digit bytes before the14-token are overwritten; the 5-byte float is never touched, so the program runs correctly while displaying misleading text. - Cursor positioning for display: Lines
9960–9962usePRINT AT 0,0twice to overlay the “true value” and “max chars” hint in the top-left corner without scrolling. - String padding: Lines
9969–9970pad the user’s input with spaces to exactlyJcharacters before POKEing, ensuring the replacement string fits precisely into the space vacated by the original digits.
Byte-Value Dispatch Logic
The per-byte handler at 9920–9946 uses a series of cascading IF tests rather than a ON ... GO TO table. The ranges tested are:
Y = 34(quote): enters string-literal copy loop at9922.Y = 234: a keyword token inside a string — routed to the machine code printer at9928.Y > 31 AND Y < 40orY > 47 AND Y < 59: printable punctuation/digit range.Y > 62 AND Y < 92orY > 94 AND Y < 165: upper-ASCII printable range.Y > 164: keyword token — calls machine code printer via9950.Y = 13: end-of-line, returns.Y = 14: number token — skips 5 bytes (LET K=K+5) since the text digits before it were already printed.Y = 41, 46, 93: specific punctuation characters printed directly.
Notable Anomalies
- The
CYmanipulation at lines9906and9960(incrementing the saved cursor row from 2 to 3 or 3 to 4) is an ad-hoc layout adjustment to avoid overwriting the status line; it relies on the specific screen row where the program begins printing. - Line
9944falls through to9951(the trailing-space consumer) viaGO TO 9951after printing, which means any byte in theY > 31catch-all also triggers space-skipping — likely intentional for token spacing but could consume spaces that are part of string content if the dispatch reaches this branch unexpectedly. - The check at
9952(PEEK (Z+1) < 48 OR > 57) guards against non-digit bytes after a token, but when a number token is immediately followed by the14byte (which has value 14, well below 48), the subroutine returns without processing — this is the normal case and is correct behavior. - Line
9904stops processing whenLINE >= 10000, which acts as a sentinel: line numbers 10000+ are treated as end-of-program, consistent with the Spectrum’s 9999-line limit for valid BASIC.
Content
Source Code
8800 GO SUB 9990
9901 REM ** CONFUSION ** from Practical Computing September 1983 entered by: F. Chrysler
9902 LET LINE=256*PEEK PROG+PEEK (PROG+1)
9903 LET LENGTH=PEEK (PROG+2)+256*PEEK (PROG+3)
9904 IF LINE>=10000 THEN STOP
9906 GO SUB 9988: IF CY=2 THEN LET CY=3
9907 PRINT LINE;" ";
9908 INPUT "ENTER NEW LINE NO. ";B$: IF B$="" THEN GO TO 9914
9910 LET LINE1=VAL B$: IF LINE1<=0 OR LINE1>9999 THEN GO TO 9908
9912 POKE PROG,INT (LINE1/256): POKE PROG+1,LINE1-256*PEEK PROG: LET LINE=LINE1
9914 GO SUB 9986: PRINT LINE;" ";
9916 FOR K=0 TO LENGTH-1: LET Z=PROG+4+K: GO SUB 9920: NEXT K
9918 PRINT : LET PROG=PROG+K+4: GO TO 9902
9920 LET Y=PEEK Z: IF Y<>34 THEN GO TO 9926
9922 PRINT CHR$ Y;: LET K=K+1: LET Z=Z+1: LET Y=PEEK Z
9923 IF Y<>34 THEN GO TO 9922
9924 PRINT CHR$ Y;: RETURN
9926 IF Y<>234 THEN GO TO 9934
9928 POKE usr+1,Y-165: RANDOMIZE USR usr
9930 LET K=K+1: LET Z=Z+1: LET Y=PEEK Z
9931 IF Y<>13 THEN PRINT CHR$ Y;: GO TO 9930
9932 RETURN
9934 IF (Y>31 AND Y<40) OR (Y>47 AND Y<59) THEN PRINT CHR$ Y;: RETURN
9935 IF (Y>62 AND Y<92) OR (Y>94 AND Y<165) THEN PRINT CHR$ Y;: RETURN
9936 IF Y>164 THEN GO SUB 9950: RETURN
9938 IF Y=13 THEN RETURN
9940 IF Y=14 THEN LET K=K+5: RETURN
9942 IF Y=41 OR Y=46 OR Y=93 THEN PRINT CHR$ Y;: RETURN
9944 IF Y>31 THEN PRINT CHR$ Y;: GO TO 9951
9946 RETURN
9950 POKE usr+1,Y-165: RANDOMIZE USR usr
9951 IF PEEK (Z+1)=32 THEN PRINT " ";: LET K=K+1: LET Z=Z+1: GO TO 9951
9952 IF PEEK (Z+1)<48 OR PEEK (Z+1)>57 THEN RETURN
9954 LET Z=Z+1: LET J=0: LET A$=""
9956 IF PEEK (Z+J)<>14 THEN LET A$=A$+CHR$ (PEEK (Z+J)): LET J=J+1: GO TO 9956
9958 FOR L=0 TO 4: POKE VARS+L,PEEK (Z+J+L+1): NEXT L
9960 GO SUB 9988: IF CY=3 AND CX-J<1 THEN LET CY=4
9962 PRINT A$;: PRINT AT 0,0;C$: PRINT AT 0,0;"Really ";A,J;" CHARS MAX."
9966 GO SUB 9986: INPUT B$: IF B$="" THEN RETURN
9968 IF CODE B$(1)<48 OR CODE B$(1)>57 OR LEN B$>J THEN GO TO 9966
9969 IF LEN B$<J THEN FOR L=LEN B$+1 TO J: LET B$=B$+" ": NEXT L
9970 FOR L=1 TO J: POKE Z+L-1,CODE (B$(L)): NEXT L: RETURN
9986 POKE 23688,CX: POKE 23689,CY: RETURN
9988 LET CX=PEEK 23688: LET CY=PEEK 23689: RETURN
9990 CLS : LET A=0: LET C$=" ": LET C$=C$+C$
9991 GO SUB 9996: RESTORE 9993: INPUT "ENTER ADDRESS FOR USR ";usr
9992 FOR K=usr TO usr+4: READ L: POKE K,L: NEXT K
9993 DATA 62,0,195,69,7
9994 LET VARS=PEEK 23627+256*PEEK 23628+1
9995 LET PROG=PEEK 23635+256*PEEK 23636: CLS : PRINT AT 2,0;"": RETURN
9996 PRINT " CONFUSION": PRINT : PRINT "The idea of this program is to enable the user to enter false numeric data into a programme.": PRINT "Every time a number is found in the text you will be prompted toenter an alternative value."
9997 PRINT "If you don't wish to change it, press <ENTER>. Otherwise type inthe value you want, first. Note that the 'real' value is not changed, only the text value."
9998 PRINT "The present true value is shown at top left. The maximum number of characters allowed is at top centre."
9999 PRINT "You can also change the line numbers, but this is for real. You will now be asked to enter the address where a 5 byte machine code routine may be put.": RETURN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
