An item code can lead to a weight, a tile number to a property, or a command token to an action identifier. A small dictionary makes that association explicit without a long chain of conditions.

Include and initialise
Reserve A(8) for keys, B(8) for values, public V/F/O/K/N and private I. Reserve lines 3000–3200, combine routines.bas and your caller with build.py and keep END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. Private I must not be an outer caller’s FOR variable.
Call 3000 at a reset boundary. It clears N, request K, results V/F/O and both arrays. Fill the dictionary afterwards. N selects the first zero to eight pairs. The dictionary is read-only during each lookup; your caller owns any deliberate edits between calls.
Look up a key
Set byte key K and call 3100. O=1 means the request and selected dictionary prefix are valid. If a match exists, F=1 and V is its value; otherwise F=0,V=0. Duplicate keys use the first matching pair, in array order.
| Field | Use |
|---|---|
| A(1)..A(8), B(1)..B(8), N | Retained keys, values and active count |
| K | Supplied key; preserved |
| V / F / O | Value / found flag / valid-input flag |
Key 255 is valid, and a stored value zero is valid too. Neither is an absence marker. Check F to distinguish a zero value from a miss, and O to distinguish a miss from invalid input. Invalid count, key or active key/value element returns V=0,F=0,O=0 and preserves the whole dictionary and inputs. Unused pairs are ignored.
The reusable routine
Reusable numbered routines
3000 LET V=0: LET F=0: LET O=0: LET K=0: LET N=0
3010 FOR I=1 TO 8: LET A(I)=0: LET B(I)=0: NEXT I: RETURN
3100 LET V=0: LET F=0: LET O=0
3110 IF N<0 OR N>8 OR N<>INT (N) THEN RETURN
3120 IF K<0 OR K>255 OR K<>INT (K) THEN RETURN
3130 IF N=0 THEN LET O=1: RETURN
3140 FOR I=1 TO N
3150 IF A(I)<0 OR A(I)>255 OR A(I)<>INT (A(I)) THEN RETURN
3160 IF B(I)<0 OR B(I)>255 OR B(I)<>INT (B(I)) THEN RETURN
3170 NEXT I: LET O=1
3180 FOR I=1 TO N
3190 IF A(I)=K THEN LET V=B(I): LET F=1: RETURN
3200 NEXT I: RETURNThe helper validates the whole selected prefix before searching it. A bad later pair cannot be hidden by an early match. It then walks the keys from the beginning, so the first-duplicate rule is straightforward to inspect.
Find values and handle misses
The normal caller looks up key 7 in a dictionary containing that key twice; the first value 99 wins over 88. The custom caller looks up 255 whose stored value is zero. Later requests cover the duplicate again, a miss, a stored zero and key zero.
Example calls from the maintained caller
100 GOSUB 3000: LET AA=0: LET BB=0
110 LET A(1)=0: LET A(2)=7: LET A(3)=7: LET A(4)=255
120 LET A(5)=8: LET A(6)=9: LET A(7)=10: LET A(8)=11
130 LET B(1)=11: LET B(2)=99: LET B(3)=88: LET B(4)=0
140 LET B(5)=18: LET B(6)=19: LET B(7)=20: LET B(8)=21
150 LET N=4: LET K=7
160 GOSUB 3100: GOSUB 2000
170 IF V=99 THEN LET AA=1
180 IF F=1 AND K=7 THEN LET BB=1
190 LET K=7: GOSUB 3100: GOSUB 2000
200 LET K=9: GOSUB 3100: GOSUB 2000
210 LET K=255: GOSUB 3100: GOSUB 2000
220 LET K=0: GOSUB 3100: GOSUB 2000
230 PRINT "FIND RESULTS";AA;BB
240 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: STOPLine 2000, AA/BB/JJ/KK (Z/Q on Spectrum) and mailbox writes are observations. A game keeps its dictionary, supplies K, calls 3100 and consumes V only with the appropriate F/O result. The complete trace verifies every dictionary element is preserved.
Customise the table
Choose what your byte keys and values identify. This is not a text parser: tokenising words and mapping names to these byte IDs are separate work. It can supply one small association table inside a larger system without deciding that system’s grammar.
Linear lookup is easy to inspect for eight entries. A sorted table and binary search can reduce comparisons for a much larger immutable dictionary, but sorting changes how duplicate keys must be handled. A direct 256-entry array trades more storage for immediate indexing. A hash table adds hashing, collision and capacity rules that this small component does not need.
Mistake and repair
The wrong caller clears K before searching. It accidentally matches a real key-zero pair and returns the wrong value, rather than merely missing. Both saved checks fail. Preserve the requested key and inspect F/O; a successful lookup can still be the wrong request. The repair matches the normal source and loaded program exactly.

Cost and Maths for Games
The routine validates at most eight pairs and then makes at most eight key comparisons. It performs no allocation, display update or input read. BASIC numeric cells are not a packed 21-byte structure, and no cycle budget is claimed.
Maths for Games connects mappings, ordered search and the distinction between a value and membership. The Craft helps decide where a small data table is clearer than a growing collection of special cases.
Machine and measured size
C64 BASIC V2 recognises only the first two significant characters of numeric variable names. Short names make the ownership visible; check for collisions before including another component. Array element zero exists but is unused here. The diagnostic uses a mailbox at $C000, outside the normal BASIC heap.
The numbered helper occupies 367 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, JJ/KK and RAM mailbox are observation machinery, not required game state.
Execution evidence
Working, custom, deliberately wrong and exact repaired callers executed through C64’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-services.py dictionary-lookup commodore-64 --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-services.py dictionary-lookup.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.