Two players finish on the same score. Which name comes first? A small table needs an answer for that tie, for a full board, and for a new score just below the cut. This routine makes each rule visible.

Include and initialise
Reserve S(4) for scores, I(4) for their tags, public R/F/O/V/T/N and private J/K/P. Reserve lines 3000–3340, including 3320 after the shift loop. Combine routines.bas with your caller using build.py and put END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. Private loop/shift scratch must not be borrowed by an outer caller.
Call 3000 for a new table. It clears N, inputs V/T, F/O and arrays, and sets rank R=255. N is the active count, zero to four. Scores are descending in S(1)..S(N); I holds each score’s associated byte tag. Unused elements can retain old values and are ignored.
Insert a completed score
Supply byte score V and byte tag T, call 3100, then read R/F/O. Tags are identifiers, not name strings; your game can use them to find a name or another record. Keep each score/tag pair together during every move.
| Field | Use |
|---|---|
| N, S(1)..S(4), I(1)..I(4) | Retained table and active count |
| V / T | New score / associated tag; preserved |
| R / F / O | Zero-based rank / inserted flag / valid-input flag |
O=0 rejects invalid N, inputs, active score/tag values or a prefix that is not descending. N and the whole table survive. This BASIC guard checks the sorted-prefix precondition explicitly. Valid requests return O=1. If retained, F=1 and R=0..3; otherwise F=0,R=255. A valid low score can miss a full table without being an input error.
Existing equal scores keep their order, and the new equal score comes after them. A better score in a full table drops the last pair. A score tied with all four existing scores misses the cut. R=255 is an absence marker; a score of 255 is still valid.
The reusable routine
Reusable numbered routines
3000 LET R=255: LET F=0: LET O=0: LET V=0: LET T=0: LET N=0
3010 FOR J=1 TO 4: LET S(J)=0: LET I(J)=0: NEXT J: RETURN
3100 LET R=255: LET F=0: LET O=0
3110 IF N<0 OR N>4 OR N<>INT (N) THEN RETURN
3120 IF V<0 OR V>255 OR V<>INT (V) THEN RETURN
3130 IF T<0 OR T>255 OR T<>INT (T) THEN RETURN
3140 IF N=0 THEN GO TO 3210
3150 FOR J=1 TO N
3160 IF S(J)<0 OR S(J)>255 OR S(J)<>INT (S(J)) THEN RETURN
3170 IF I(J)<0 OR I(J)>255 OR I(J)<>INT (I(J)) THEN RETURN
3180 IF J=1 THEN GO TO 3200
3190 IF S(J)>S(J-1) THEN RETURN
3200 NEXT J
3210 LET O=1: LET P=N
3220 IF N=0 THEN GO TO 3260
3230 FOR J=1 TO N
3240 IF S(J)<V THEN LET P=J-1: GO TO 3260
3250 NEXT J
3260 IF P=4 THEN RETURN
3270 LET K=N+1
3280 IF K>4 THEN LET K=4
3290 IF K<=P+1 THEN GO TO 3320
3300 LET S(K)=S(K-1): LET I(K)=I(K-1): LET K=K-1: GO TO 3290
3320 LET S(P+1)=V: LET I(P+1)=T
3330 IF N<4 THEN LET N=N+1
3340 LET R=P: LET F=1: RETURNThe routine finds the first strictly smaller score and shifts the tail from the end backwards. Copying forwards would overwrite a pair before its old value was moved. The insertion position is zero-based; arrays are one-based, so R=0 maps to S(1)/I(1).
Keep one table across results
The normal caller submits repeated scores, a low score that misses the cut, a new maximum and a zero. It finishes with scores 255,100,100,90 and tags 6,1,3,4. The two original 100-point entries keep their order.
Example calls from the maintained caller
100 GO SUB 3000: LET AA=0: LET BB=0
110 LET V=100: LET T=1
120 GO SUB 3100: GO SUB 2000
130 LET V=80: LET T=2
140 GO SUB 3100: GO SUB 2000
150 LET V=100: LET T=3
160 GO SUB 3100: GO SUB 2000
170 LET V=90: LET T=4
180 GO SUB 3100: GO SUB 2000
190 LET V=70: LET T=5
200 GO SUB 3100: GO SUB 2000
210 LET V=255: LET T=6
220 GO SUB 3100: GO SUB 2000
230 LET V=0: LET T=7
240 GO SUB 3100: GO SUB 2000
250 LET AA=1: LET BB=1
260 IF S(1)<>255 THEN LET AA=0
270 IF S(2)<>100 THEN LET AA=0
280 IF S(3)<>100 THEN LET AA=0
290 IF S(4)<>90 THEN LET AA=0
300 IF I(1)<>6 THEN LET BB=0
310 IF I(2)<>1 THEN LET BB=0
320 IF I(3)<>3 THEN LET BB=0
330 IF I(4)<>4 THEN LET BB=0
340 PRINT "RANK RESULTS";AA;BB
350 POKE MB+995,AA: POKE MB+996,BB: POKE MB+997,GD: POKE MB+998,WC-256*INT (WC/256): POKE MB+999,INT (WC/256): POKE MB+1000,239: STOPThe diagnostic’s line 2000, AA/BB/JJ/KK (Z/Q on Spectrum) and mailbox can be removed. A game retains the table between completed sessions, passes the achieved V/T before resetting gameplay, and uses R/F/O to decide what to show.
Customise the table
The custom caller includes maximum 255, zero and a tie at the maximum. It finishes 255,255,1,0 with the two tied tags still in submission order. Choose what each tag identifies; the helper does not store a name or write a save file.
Wider scores need a wider comparison and bounds. More entries need larger arrays, a new rank/count limit and tests for the longest shift. If a whole unsorted collection needs ordering, sorting it once may be a better fit than repeatedly inserting. Four entries keep this component small enough to inspect.
Mistake and repair
The wrong caller clears V before insertion. The table can only record the zero it receives; both the expected scores and their tag order are wrong. Pass the completed score before resetting it. The repair matches the normal source and loaded program exactly.

Cost and Maths for Games
A call validates at most four active pairs, searches at most four scores and moves at most three pairs. It allocates no dynamic storage and performs no display or persistent I/O. BASIC numeric fields and arrays have interpreter-managed cost; these are not 14 packed native bytes.
Maths for Games connects ordering, stable ties and insertion bounds. The Craft connects the table’s rules to fair, predictable player feedback and when a completed score should become a saved result.
Machine and measured size
Spectrum FOR variables must be single letters: this diagnostic uses Z/Q instead of the other callers’ JJ/KK. Numeric arrays start at one. AND/OR in these listings join comparisons; neither is being used as a bitwise operator. The diagnostic reserves its mailbox above CLEAR 59999.
The numbered helper occupies 822 tokenised program bytes on this interpreter, including line headers. This excludes the caller, test tables, numeric scalar/array storage, interpreter stacks and ROM. Tokenised size is not machine-code size or a hardware-cycle count. No real-time or whole-game performance budget is inferred from the diagnostic. The caller’s AA/BB, Z/Q and RAM mailbox are observation machinery, not required game state.
Execution evidence
Working, custom, deliberately wrong and exact repaired callers executed through Spectrum’s stock BASIC interpreter under Emu198x 0.44.2. Build198x 0.2.9 tokenises C64 PRG and Spectrum TAP programs. C64 enters stock RUN, Spectrum uses ROM tape loading, and BBC enters every numbered source line through MOS/BASIC. Actual loaded program bytes, complete state packets, positive completion/count/guards and visible result pixels are checked. After the positive completion marker, the observer advances two complete display frames before capturing, so a partially scanned final character cannot masquerade as a finished picture. Firmware is not distributed.
An independent mathematical/order model checks the trace and rejects altered source, configuration, loaded program, state, completion and image evidence. Host exhaustive checks complement the native boundary/sequential examples; those host ranges are not claimed as natively exhaustive. The supplied diagnostic callers deliberately end with STOP; the interpreter report is expected. The wrong caller prints 0,0 and the normal/custom/repaired callers print 1,1.
Public CI checks retained evidence; it does not execute restricted firmware. Refresh with python3 scripts/verify-basic-records.py high-score-insertion sinclair-zx-spectrum --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-records.py high-score-insertion.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.