--- title: "Tracker" id: 57273 type: "computer_media" slug: "tracker" url: "http://localhost/computer_media/tracker/" markdown_url: "http://localhost/computer_media/tracker.md" published_at: "2024-10-04T07:56:26+00:00" modified_at: "2026-04-03T07:58:38+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/161_Trac.png" excerpt: "A tape-based record tracker that uses embedded machine code to load fixed-length data, then sorts and prints formatted multi-column event histories on a printer." 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 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Hobby" slug: "hobby" taxonomy: "genre" url: "http://localhost/type/hobby/" media_contents: - id: 56734 title: "Timex Sinclair Public Domain Library Tape 1003" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1003/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/161_Trac.png" media_type_tags: "Hobby" --- This program is a ZX81/TS1000 athlete or competitor tracking utility called “TRACKER” that reads records from data tape and formats them for printed output. It uses two machine code routines embedded in the line 10 REM block: one at USR 16549 (called via RAND USR 16549) that handles tape loading of a fixed-length 1459-character string array record. The program manages three string arrays — A$ for raw tape data (1×1459), B$ for formatted name output (1×63/70), and C$ for formatted event lines (12×70) — plus a numeric array A(48) storing field start positions within A$. Events are sorted by a simple bubble sort on the first 10 characters of each C$ row, then printed across three LPRINT passes showing name, place, and additional data columns separated by decorative lines. *** ## Program Analysis ### Program Structure The program is organized into clearly delineated phases, each preceded by an inverse-video REM label acting as a section header. The overall flow is: 1. Lines 10–50: Machine code in REM, array declarations 2. Lines 60–150: Interactive setup — enter 12 event titles into `F$()` and 48 field positions into `A()` 3. Lines 160–240: Print both arrays to the printer for verification 4. Lines 250–380: Load a single tape record into `A$(1)` via machine code 5. Lines 390–530: Display the loaded record and ask if this is the correct person 6. Lines 540–930: Format name into `B$`, build event lines into `C$` 7. Lines 940–1020: Bubble sort `C$` rows by date/event field 8. Lines 1030–1240: Three-pass LPRINT output; loop back to load next record 9. Lines 1260–1280: SAVE routine (apparently reached only by direct RUN or manual GO TO) ### Machine Code Routine (REM Line 10) The REM at line 10 contains 69 bytes of Z80 machine code. `RAND USR 16549` at line 330 jumps into this code. Disassembly of the first routine (starting at approximately offset 1 within the REM data, mapped to address 16549) implements a tape-loading loop: it reads bytes from the tape port (`DB FE` = `IN A,(254)`), assembles bits with rotation instructions, and stores them into memory addressed via the `A$` array’s data area obtained from a pointer at address `0x4010` (`2A 10 40` = `LD HL,(16400)`). The routine loads exactly the length defined by `DIM A$(1,1459)`. A second sub-routine within the REM handles a delay or sync loop. The use of `DB FE`/`D3 FF` (IN/OUT on port 254/255) confirms this is ZX81/TS1000 tape I/O at the hardware level. ### Array Layout and Field Addressing The 1459-character flat string `A$(1)` stores a complete fixed-format record. Fields within it are not hard-coded; instead, the numeric array `A(48)` holds 48 start positions, used in groups of four per event (indexed as `A(X)`, `A(X+1)`, `A(X+2)`, `A(X+3)`) for date, city, state/region, and country respectively. This indirection makes the program adaptable to different record layouts without rewriting field references. | Array | Dimensions | Purpose | | --- | --- | --- | | `A$(1,1459)` | 1 row × 1459 chars | Raw tape record | | `B$(1,63)` / redimmed to 70 | 1 row × 70 chars | Formatted centred name | | `F$(12,10)` | 12 rows × 10 chars | Event title labels | | `A(48)` | 48 elements | Field start positions in A$ | | `C$(12,70)` | 12 rows × 70 chars | Formatted event output lines | ### Name and Place Formatting Subroutines Lines 620–690 compress the name stored in `B$` by iterating character by character, suppressing runs of spaces but inserting a single space between words, building a compacted string in `D$`. The result is then centred within a 70-character field using the expression `(70-LEN D$)/2` as the start position of a string slice assignment — a neat ZX81 BASIC idiom for centring text without explicit PRINT AT arithmetic. The subroutine at lines 850–930 performs an identical compaction on the place string `Z$` (up to 47 characters), called via `GOSUB 0850` from line 799. Both routines rely on checking the *next* character (`B$(1,X+1)` or `Z$(Z+1)`) without bounds-guarding, which could cause a subscript error on the last iteration if the final character is not a space — a latent off-by-one bug. ### Bubble Sort Implementation Lines 950–1020 implement an O(n²) selection/bubble sort on the `C$()` array, comparing the first 10 characters of each row (the date/event field) using standard BASIC string comparison. Row swaps use a temporary string variable `S$`. Because `C$` is a two-dimensional string array, each assignment copies an entire 70-character row, which is memory-intensive but functionally correct. ### Three-Pass Printer Output Rather than printing each full 70-character event line in one pass, the LPRINT section makes three separate passes over the sorted `C$` array, each printing a different slice of each row: - Pass 1 (lines 1040–1100): Characters 1–19 — date and event name, with decorative inverse-space padding - Pass 2 (lines 1120–1170): Characters 20–51 — place information - Pass 3 (lines 1180–1240): Characters 52–end — additional data with trailing inverse-space decoration Each pass is separated by a 32-dash rule printed via LPRINT. This approach effectively creates a three-column printed layout on a 32-column ZX printer by printing each column as a separate block, with the name in `B$` as a header for each section. ### Notable Techniques and Idioms - `IF INKEY$="" THEN GOTO 0490` at line 490 is a busy-wait key poll loop — standard ZX81 practice in the absence of a blocking INPUT for single keystrokes. - `DIM B$(1,70)` at line 570 re-dimensions `B$` mid-program (originally declared as 1×63 at line 30), clearing it and enlarging it. This is intentional to reclaim the layout space needed for centring. - The `GOTO 0250` at line 1250 creates an indefinite loop: after printing, the program loops back to load the next tape record without requiring a restart. - Lines 1070, 1140, and 1210 use `IF C$(X,1)=" " THEN NEXT X` to skip blank/unused rows in `C$` — a ZX81 idiom for sparse array iteration. - The SAVE at line 1270 uses `"1016%1"` where `%1` is inverse “1”, functioning as an auto-run flag. ### Potential Bugs and Anomalies - The off-by-one risk in the compaction loops (lines 660 and 900) accessing index `X+1` or `Z+1` on the final iteration without a bounds check. - `DIM C$(12,70)` at line 710 is declared inside the main processing block rather than at program start; if the code path is re-entered (e.g. looping back through line 250), this re-dimensions and clears `C$` each time, which is actually correct behaviour for processing a new record. - Variable `L` is used as the event line counter but is never explicitly reset to 1 before the `FOR I=1 TO 12` loop at line 740 on subsequent iterations — however, line 730 does reset it to 1 each pass through the format block. ## Source Code ``` 10 REM CDE726E210FF2B7CB520FB10F62A1040234E234623C55ECD1F3C1B78B120F4C9CDE722A1040234E234623C51E8DBFED3FF1730F9E9461ADDBFE17CB797938F510F5204FE5630E43FCB161D20DEC1B78B1C818D3 20 DIM A$(1,1459) 30 DIM B$(1,63) 40 DIM F$(12,10) 50 DIM A(48) 60 REM %L%O%A%D% %F%$% %A%R%R%A%Y% % % % % % % % % % 70 FOR X=1 TO 12 80 PRINT AT 10,0;"ENTER EVENT TITLE";TAB 19;X;TAB 24;"(10 POS)" 90 INPUT F$(X) 100 NEXT X 110 REM %L%O%A%D% %A% %A%R%R%A%Y% % % % % % % % % % % 120 FOR X=1 TO 48 130 PRINT AT 10,0;"ENTER STARTING POSITION OF FIELD" 140 INPUT A(X) 150 NEXT X 160 REM %P%R%I%N%T% %A%R%R%A%Y%S% % % % % % % % % % % 170 LPRINT "F$ ARRAY",,,, 180 FOR X=1 TO 12 STEP 2 190 LPRINT X;TAB 3;F$(X);TAB 15;X+1;TAB 19;F$(X+1) 200 NEXT X 210 LPRINT ,,"A ARRAY",,,, 220 FOR X=1 TO 48 STEP 3 230 LPRINT X;TAB 3;A(X);TAB 10;X+1;TAB 13;A(X+1);TAB 20;X+2;TAB 23;A(X+2) 240 NEXT X 250 REM %R%E%A%D% %A% %R%E%C%O%R%D% %F%R%O%M% %T%A%P%E 260 CLS 270 SLOW 280 PRINT AT 2,13;"TRACKER" 290 PRINT AT 10,0;"PLACE DATA TAPE IN TAPE DECK" 300 PRINT AT 12,0;"PRESS ""PLAY"" ON TAPE DECK" 310 PRINT AT 14,0;"PRESS ""ENTER"" ON COMPUTER" 320 INPUT Z$ 330 RAND USR 16549 340 CLS 350 SLOW 360 PRINT AT 12,0;"PRESS ""STOP"" ON TAPE DECK" 370 PRINT AT 14,0;"PRESS ""ENTER"" ON COMPUTER" 380 INPUT Z$ 390 REM %C%H%E%C%K% %F%O%R% %R%I%G%H%T% %P%E%R%S%O%N% 400 CLS 410 PRINT AT 2,13;"TRACKER" 420 PRINT AT 8,0;"GEN/CODE NBR. ";A$(1, TO 5) 430 PRINT "FIRST NAME ";A$(1,6 TO 20) 440 PRINT "SECOND NAME ";A$(1,21 TO 35) 450 PRINT "THIRD NAME ";A$(1,36 TO 50) 460 PRINT "LAST NAME ";A$(1,51 TO 65) 470 PRINT AT 14,0;"DO YOU WISH TO TRACK THIS PERSON" 480 PRINT "(Y OR N)" 490 IF INKEY$="" THEN GOTO 0490 500 LET I$=INKEY$ 510 IF I$<>"Y" AND I$<>"N" THEN GOTO 0480 520 CLS 530 IF I$="N" THEN GOTO 0250 540 REM %F%O%R%M%A%T% %R%E%C%O%R%D%S% % % % % % % % % 550 REM %R%E%D%U%C%E% %N%A%M%E% %L%E%N%G%T%H% % % % % 560 FAST 570 DIM B$(1,70) 580 LET B$(1, TO 15)=A$(1,6 TO 20) 590 LET B$(1,17 TO 31)=A$(1,21 TO 35) 600 LET B$(1,33 TO 47)=A$(1,36 TO 50) 610 LET B$(1,49 TO 63)=A$(1,51 TO 65) 620 LET D$="" 630 FOR X=1 TO 63 640 IF B$(1,X)=" " THEN GOTO 0670 650 LET D$=D$+B$(1,X) 660 IF B$(1,X+1)=" " THEN LET D$=D$+" " 670 NEXT X 680 LET B$(1)="" 690 LET B$(1,((70-LEN D$)/2) TO )=D$ 700 REM %F%O%R%M%A%T% %E%V%E%N%T% %L%I%N%E%S% % % % % 710 DIM C$(12,70) 720 LET X=1 730 LET L=1 740 FOR I=1 TO 12 750 IF A$(1,A(X))=" " THEN GOTO 0820 760 LET C$(L, TO 10)=A$(1,A(X) TO (A(X)+9)) 770 LET C$(L,12 TO 21)=F$(I) 780 LET Z$=A$(1,A(X+1) TO (A(X+1)+14))+" "+A$(1,A(X+2) TO (A(X+2)+14))+" "+"CO. "+A$(1,A(X+3) TO (A(X+3)+14))+" " 790 GOSUB 0850 800 LET C$(L,23 TO )=Z$ 810 LET L=L+1 820 LET X=X+4 830 NEXT I 840 GOTO 0940 850 REM %R%E%D%U%C%E% %P%L%A%C%E% %L%E%N%G%T%H% % % % 860 LET D$="" 870 FOR Z=1 TO 47 880 IF Z$(Z)=" " THEN GOTO 0910 890 LET D$=D$+Z$(Z) 900 IF Z$(Z+1)=" " THEN LET D$=D$+" " 910 NEXT Z 920 LET Z$=D$ 930 RETURN 940 REM %S%O%R%T% %R%E%C%O%R%D%S% % % % % % % % % % % 950 FOR X=1 TO L-1 960 FOR S=X+1 TO L 970 IF C$(X, TO 10)