Gran-Marquee is a scrolling marquee display program that smoothly shifts characters across the screen one pixel column at a time using a machine-code routine embedded in a REM statement. The machine code, stored at line 10 from address 16384 onwards, reads a character code from a fixed memory location, uses the ZX81’s character ROM data, and shifts pixel columns through display memory to create a hardware-level horizontal scroll effect. The user can set scroll speed (0–9), enter a custom message via INPUT, and control behaviour with single-key commands: C clears the credit overlay, S and F toggle display options via direct POKEs, and L adjusts display memory settings. The program uses USR to call the machine code, passing data via POKEs to address 16514, and the REM at line 11 contains inverse-video text forming the program’s title screen credit block.
Program Analysis
Program Structure
The program is organised into several functional sections:
- Lines 10–11: Machine code payload in a REM statement; inverse-video title credit block in a second REM.
- Lines 15–70: Initialisation — default message, speed prompt, speed input loop.
- Lines 90–190: Main scroll loop, calling machine code via
USR, overlaying credits, speed delay, and key dispatch. - Lines 200–240: Custom message INPUT handler.
- Lines 300–310: In-flight speed change handler.
- Lines 400–520: Display option toggle handlers via direct POKEs.
- Lines 600–610: Enable continuous scroll (skip credit overlay) mode.
- Lines 9998–9999: SAVE with auto-run flag, then
RUN.
Machine Code Routine (Line 10 REM)
The REM statement at line 10 contains a Z80 machine code routine beginning at address 16516 (the first byte after the REM token and length bytes at the start of line 10’s data area). The routine is invoked by USR 16516. Before each call, POKE 16514, CODE A$(I) places the current character code into a fixed memory cell that the routine reads.
Disassembly of the hex payload reveals the routine’s logic:
- Loads the character code from address
16514($40AF), subtracts 8 per pass to index into the character ROM at$1E00(for ZX81 character data). - Uses
LD HL,(16396)(2A 0C 40) to fetch the display file address from the system variableD_FILE. - Iterates 8 times (one per pixel column of the character), shifting each of 6 display rows one pixel left and inserting the new column on the right edge.
- The outer loop (
DJNZat$10E6) repeats for all 22 display rows worth of data. - Returns via
RET(C9); the BASIC system receives a dummy USR return value inX.
Key BASIC Idioms
LET X=USR 16516 at line 120 discards the return value but is required because USR in an expression context is the only way to call machine code from ZX81 BASIC. The result is assigned to X purely to satisfy the syntax.
Speed is encoded as S=30-3*VAL S$, mapping digit keys 0–9 to delay loop counts 30 down to 3. The delay at lines 150–160 is a simple FOR/NEXT busy-wait, with S=0 (speed 0 entered) giving a count of 30 and maximum wait, reversed from intuitive expectation — speed “9” gives the fastest scroll (smallest S).
The input filter at line 50 uses CODE S$ comparison against the ZX81 character codes for digits: codes 28–37 correspond to characters ‘0’–’9′, so only digit keys pass through to set speed.
Continuous Scroll Flag
The variable CS (initialised at line 15, settable via key F at line 600 or by pressing a key with code 43 at startup — the + key) acts as a “skip credit overlay” flag. When CS=1, line 125 executes NEXT I immediately, bypassing the PRINT at line 130 and the delay/key-check block, producing a faster uninterrupted scroll. Key C at line 122 resets CS to 0, re-enabling the credit overlay.
Display Memory POKEs (Lines 400–520)
Lines 400 and 500 write directly to addresses 16556 and 16566. On the ZX81, these addresses fall within the system variable area or early display file, and the specific values (0 vs. 9 and 0 vs. 128) suggest toggling between normal and inverted or alternate display modes, though their exact effect depends on the precise system variable layout at runtime.
| Key | Action | Lines |
|---|---|---|
| 0–9 | Change scroll speed | 167, 300 |
| C | Clear/disable continuous mode | 122, 163 |
| S | Set display POKEs to 0/0 | 164, 400–420 |
| L | Set display POKEs to 9/128 | 165, 500–520 |
| F | Enable continuous scroll | 166, 600–610 |
| Any other | Enter custom message | 170, 200–240 |
Notable Techniques
- Embedding Z80 machine code in a
REMstatement is the standard ZX81 technique for persistent machine code that survivesRUNandCLEAR. - The credit display at line 130 uses
PRINT ATwith inline inverse-video characters (encoded as%letterin the source) to render a title overlay on the scrolling display without clearing the screen. - The tail
GOTO 100at line 190 re-appends spaces (A$=A$+" "at line 90 runs only once per message entry) so the loop continues to add padding each time through — actuallyA$grows by 5 spaces every time line 90 is reached viaGOTO 100, which is a slow memory leak but unlikely to matter for a typical message length during a demo session. - The
SAVE "GRAN MARQUE%E"at line 9998 uses an inverse-E in the filename combined withRUNat 9999 as the auto-run mechanism.
Potential Anomalies
Line 90 (LET A$=A$+" ") is re-executed every time GOTO 100 is reached from line 190, causing the string to grow by 5 characters each full pass. Over many iterations this will gradually increase memory use and slow performance, though this is likely an oversight rather than intentional behaviour.
The speed-input loop at line 40–50 accepts code 43 (‘+’ key) to set CS=1, but line 50’s range check will then GOTO 40 again waiting for a valid digit — so ‘+’ enables continuous mode but still requires a speed digit to proceed, which is the intended startup behaviour.
Content
Source Code
10 REM 301E2A824026�029292911�11E192282403E7F32AF40�6�8C52A�C4011A4�019ED5B8240�6�6C5�121�0�01ACB3F28�73680�936�018�536�0�936�013C110E63AAF40D6�832AF40CDD140C110CAC92A�C40�616C523545D23�11F�0EDB0C110F3C9
11 REM % %G%R%A%N%-%M%A%R%Q%U%E%E% % %B%Y% %G%E%N%E% %B%U%Z%A% % % % % % % %O%F% % % % % % %S%Y%N%C%H%R%O%-%S%E%T%T%E%
15 LET CS=0
20 LET A$="GRAN-MARQUEE FROM SYNCHRO-SETTE - - - PRESS ANY KEY BUT BREAK OR THE CONTROL KEYS TO ENTER YOUR OWN MESSAGE - - -"
30 PRINT AT 10,8;"SPEED (0 TO 9)?"
40 LET S$=INKEY$
45 IF CODE S$=43 THEN LET CS=1
50 IF CODE S$<28 OR CODE S$>37 THEN GOTO 40
60 LET S=30-3*VAL S$
70 CLS
90 LET A$=A$+" "
100 FOR I=1 TO LEN A$
110 POKE 16514,CODE A$(I)
120 LET X=USR 16516
122 IF INKEY$="C" THEN LET CS=0
125 IF CS=1 THEN NEXT I
130 PRINT AT 1,2;" % %G%R%A%N% %M%A%R%Q%U%E%E% ";AT 20,0;" % %F%R%O%M% %S%Y%N%C%H%R%O%-%S%E%T%T%E% "
150 FOR N=1 TO S
160 NEXT N
162 LET S$=INKEY$
163 IF S$="C" THEN GOTO 180
164 IF S$="S" THEN GOTO 400
165 IF S$="L" THEN GOTO 500
166 IF S$="F" THEN GOTO 600
167 IF CODE S$>27 AND CODE S$<38 THEN GOTO 300
170 IF S$<>"" THEN GOTO 200
180 NEXT I
190 GOTO 100
200 CLS
210 PRINT AT 20,0;"ENTER YOUR MESSAGE"
220 INPUT A$
240 GOTO 30
300 LET S=30-3*VAL S$
310 GOTO 180
400 POKE 16556,0
410 POKE 16566,0
420 GOTO 180
500 POKE 16556,9
510 POKE 16566,128
520 GOTO 180
600 LET CS=1
610 GOTO 180
9998 SAVE "GRAN MARQUE%E"
9999 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
