A maze character needs one direction even when the player holds a diagonal. Deciding what that means is an input policy, separate from detecting the buttons and deciding whether a wall blocks movement.

Include and initialise
Reserve public H/A/D/O, private L/R/U/V/X/Y and lines 3000–3240. There are no arrays. Combine routines.bas and the caller using build.py, with END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB.
Call 3000 at an intended reset boundary. It clears all public fields. A is retained axis preference: zero horizontal, one vertical. Set a different starting preference afterwards if needed. H is an integral byte whose low bits mean left, right, up and down respectively; higher bits are ignored.
Cancel first, then choose
Call 3100. Opposite directions cancel within each axis. If one axis remains, choose it. If both remain, prefer A. D is zero still, one left, two right, three up or four down. A records the last nonzero chosen axis, and H survives. O=1 reports valid input; an invalid H/A clears D/O and preserves H/A.
Reusable numbered routines
3000 LET H=0: LET A=0: LET D=0: LET O=0: RETURN
3100 LET D=0: LET O=0
3110 IF H<0 OR H>255 OR H<>INT (H) THEN RETURN
3120 IF A<>0 AND A<>1 THEN RETURN
3130 LET L=H-2*INT (H/2): LET R=INT (H/2)-2*INT (H/4)
3140 LET U=INT (H/4)-2*INT (H/8): LET V=INT (H/8)-2*INT (H/16)
3150 LET X=R-L: LET Y=V-U: LET O=1
3160 IF X=0 AND Y=0 THEN RETURN
3170 IF X<>0 AND (Y=0 OR A=0) THEN GOTO 3220
3180 LET A=1: LET D=3
3190 IF Y>0 THEN LET D=4
3200 RETURN
3220 LET A=0: LET D=1
3230 IF X>0 THEN LET D=2
3240 RETURN| Field | Use |
|---|---|
| H | Held mask: bits zero left, one right, two up, three down |
| A | Retained preferred axis, zero horizontal or one vertical |
| D / O | Chosen direction / valid-input flag |
Left plus right does not beat a valid Up request: horizontal intent cancels before arbitration. With no remaining direction, A survives for a later diagonal. This remembers the last chosen axis, not which physical button was pressed most recently.
Watch preference change
The caller supplies left/up, then up alone, then left/up again. Normal initial A=0 chooses left first; custom A=1 chooses up first. The up-only update sets vertical preference, so both finish by choosing up from the final diagonal.
Example calls from the maintained caller
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET A=0
90 LET H=5: GOSUB 3100: GOSUB 2000
100 LET H=4: GOSUB 3100: GOSUB 2000
110 LET H=5: GOSUB 3100: GOSUB 2000
120 IF D=3 THEN LET AA=1
130 IF A=1 THEN LET BB=1
140 PRINT "DIRS RESULTS";AA;BB
150 ?(MB+995)=AA: ?(MB+996)=BB: ?(MB+997)=GD: ?(MB+998)=WC-256*INT (WC/256): ?(MB+999)=INT (WC/256): ?(MB+1000)=239: STOPLine 2000, AA/BB/JJ (Z on Spectrum) and mailbox writes are observations. A game maps its input reader to H and uses D to propose movement. This routine does not move an actor, consult a tile map or buffer an intended turn until a junction. The chosen direction can still be blocked by game geometry.
Choose the policy for your game
A fixed horizontal priority needs no retained A, but treats every diagonal the same. Last-pressed priority needs press history and a rule for simultaneous edges. Buffered maze turns need another retained request and a policy for when a turn is legal. Eight-way motion should preserve both axes instead of collapsing them here.
Use one renamed context for each independently controlled player or menu. Resetting on focus/session changes is a deliberate choice; resetting on every update discards the very preference this helper maintains. Logical bits are not a claim about a particular joystick port layout.
Mistake and repair
The wrong caller resets A to horizontal before every service. The up-only update briefly sets vertical preference, but the next diagonal loses it and chooses left. Both saved final direction and preference checks fail. Keep A between updates. Repaired source and loaded program match normal exactly.

Cost and Maths for Games
The routine extracts four bits, forms two signed intents and chooses one axis with bounded comparisons. No search, allocation or input scanning occurs. In an interpreter with suitable bitwise operations extraction may be shorter; verify the same logical mapping and cancellation before substituting it.
Maths for Games connects signed axis vectors and cancellation. The Craft connects direction arbitration, buffered turns and the difference between detected input and allowed movement.
Global names and line numbers
BASIC numeric names, arrays and numbered lines are global. To combine helpers, rename public and private fields and renumber the helper together with every GO SUB/GO TO reference. Keep each state block under one owner. The maintained build.py rejects duplicate line numbers; it does not automatically namespace or renumber routines.
Machine and measured size
This helper uses scalar numeric fields and allocates no arrays. Its diagnostic uses MODE 7 and HIMEM=&6000 to separate program storage, mailbox and screen. The helper itself changes neither mode nor HIMEM.
The numbered helper occupies 363 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 and RAM mailbox are observation machinery, not required game state.
Execution evidence
Working, custom, deliberately wrong and exact repaired callers executed through BBC Micro’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-input-models.py four-way-input bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-input-models.py four-way-input.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.