A crate should move with the player only when the space beyond it is free. Clearing its old cell before checking the destination can leave half a move behind. Validate first, then commit the complete logical action.

Include and initialise
DIM B(16). Reserve public C/R/O/D, private I/K/T/N/X/Y/A and lines 3000–3550. Private I must not be the caller’s outer FOR variable. Combine routines.bas with your caller through build.py and prevent fall-through with END, STOP or a jump. C64/BBC use GOSUB; Spectrum uses GO SUB.
Call 3000 at an intended reset boundary. It clears all public fields and map elements. Fill the four-by-four row-major map afterwards: zero is empty, two a crate, and any other byte blocks. C is the player’s separate zero-based cell index and must point at an empty tile, B(C+1)=0.
Request one move
Set D to one left, two right, three up or four down, then call 3100. Zero is not an idle request in this interface: call only for an intended action. A free adjacent cell gives R=1 and moves the player. A crate with free space beyond gives R=2, moves the crate and advances the player into its old cell.
Reusable numbered routines
3000 LET C=0: LET R=0: LET O=0: LET D=0
3010 FOR I=1 TO 16: LET B(I)=0: NEXT I: RETURN
3100 LET R=0: LET O=0
3110 IF C<0 OR C>15 OR C<>INT (C) THEN RETURN
3120 IF D<1 OR D>4 OR D<>INT (D) THEN RETURN
3130 FOR I=1 TO 16
3140 IF B(I)<0 OR B(I)>255 OR B(I)<>INT (B(I)) THEN RETURN
3150 NEXT I
3160 IF B(C+1)<>0 THEN RETURN
3170 LET O=1: LET K=C: LET T=D: GOSUB 3500
3180 IF N<0 THEN RETURN
3190 IF B(N+1)=0 THEN LET C=N: LET R=1: RETURN
3200 IF B(N+1)<>2 THEN RETURN
3210 LET A=N: LET K=A: GOSUB 3500
3220 IF N<0 THEN RETURN
3230 IF B(N+1)<>0 THEN RETURN
3240 LET B(A+1)=0: LET B(N+1)=2: LET C=A: LET R=2: RETURN
3500 LET N=-1: LET X=K-4*INT (K/4): LET Y=INT (K/4)
3510 IF T=1 AND X>0 THEN LET N=K-1: RETURN
3520 IF T=2 AND X<3 THEN LET N=K+1: RETURN
3530 IF T=3 AND Y>0 THEN LET N=K-4: RETURN
3540 IF T=4 AND Y<3 THEN LET N=K+4: RETURN
3550 RETURN| R / O | Meaning |
|---|---|
| 0 / 1 | Valid action, blocked by geometry |
| 1 / 1 | Walked into empty space |
| 2 / 1 | Pushed one crate |
| 0 / 0 | Invalid player, request or map data |
Every map value is an integral byte; C and D are integral and bounded. Invalid state preserves the player, map and input. A boundary-blocked action is a valid no-op. D survives. The helper checks both row/column boundaries and destination occupancy before the first map write.
Follow one transaction
Normal play starts at cell five with a crate at six. A right request pushes the crate to seven and the player to six. The custom caller starts at nine with a crate at five and pushes upwards to cell one, advancing the player to five. Repeating the push is then blocked by an edge; a later action walks sideways, and an invalid zero request changes nothing.
Example calls from the maintained caller
90 GOSUB 3000: LET AA=0: LET BB=0
100 LET B(1)=0: LET B(2)=0: LET B(3)=0: LET B(4)=0: LET B(5)=0: LET B(6)=0: LET B(7)=2: LET B(8)=0: LET B(9)=0: LET B(10)=0: LET B(11)=0: LET B(12)=0: LET B(13)=0: LET B(14)=0: LET B(15)=0: LET B(16)=0
110 LET C=5: LET D=2: GOSUB 3100: GOSUB 2000
120 IF R=2 THEN LET AA=1
130 IF C=6 THEN LET BB=1
140 GOSUB 3100: GOSUB 2000
150 LET D=4: GOSUB 3100: GOSUB 2000
160 LET D=0: GOSUB 3100: GOSUB 2000
170 PRINT "PUSH RESULTS";AA;BB
180 ?(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/KK (Z/Q on Spectrum) and the mailbox are observations. A game uses R/O to choose animation, sound or feedback after the logical result. The player is not painted into the map: keeping it separate avoids destroying the underlying floor during movement.
Keep one owner through the commit
The old crate cell becomes empty and the destination becomes two only after all checks pass. Keep exclusive ownership of the map while checking and committing. This is one logical transaction under a synchronous owner, not a claim that two BASIC assignments form one hardware atomic instruction.
Larger maps need dimensions, allocation, bounds and index width changed together. Several linked crates need a complete validation pass followed by a commit pass, or an explicitly reversible journal. Goals, undo, pixel animation and a full puzzle game need separate state and policies.
Mistake and repair
The wrong caller writes a wall into the crate’s destination before requesting the push. The helper correctly refuses the altered map, leaving the player stuck. Both saved push-result and player-position checks fail. Keep the authoritative map unchanged until the move has been validated. Repaired source and loaded program match normal exactly.

Cost and Maths for Games
Validation visits sixteen stored values. The action then inspects at most two neighbouring candidates and changes at most two map cells. A failed push leaves every map element untouched. No route search, allocation, rendering or input scanning occurs. Larger transactions need an explicit bound on both checking and commit work.
Maths for Games connects grid coordinates, bounds and conservation of crate count. The Craft connects clear push rules, blocked-action feedback and reversible puzzle moves.
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
BBC BASIC arrays include element zero; this one-based array interface leaves it unused. 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 597 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 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-world-policy.py push-block-transaction bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-world-policy.py push-block-transaction.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.