A fast object can be left of a wall in one update and right of it in the next. Testing only its final position misses the crossing. This routine considers the path along one axis and stops at the first wall face.

Include and initialise
Reserve public P/H/V/X/T/S/A/B and private D/E. Reserve lines 3000–3230, combine routines.bas with a caller using build.py and keep END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. D/E are private endpoint scratch during a call.
Call 3000 at a reset boundary. It clears every public field. Supply old position X, requested target T, positive size S and wall start/end A/B afterwards. All use the same unsigned byte coordinate space.
Sweep one interval
The object occupies [X,X+S) and the wall [A,B). Call 3100. V=1 accepts the query; P is the resulting position and H says whether motion reached a wall face. Contact is inclusive: exactly reaching the face counts as a hit, even though the final half-open intervals do not overlap.
| Field | Use |
|---|---|
| X / T / S | Old start, requested start and positive size |
| A / B | Wall’s inclusive start / exclusive end |
| P / H / V | Resulting start / hit flag / valid flag |
Inputs survive. They must be integral bytes, both proposed object end edges must fit at or below 255, the wall must be nonempty, and the old interval must not already overlap it. Invalid requests return P=X,H=0,V=0. Depenetrating an object already inside a wall is a different problem.
Stationary motion is valid but produces no new hit. Moving away from a face is allowed. The routine owns no velocity, timer, display or object pool; it returns a constrained position for the caller to apply.
The reusable routine
Reusable numbered routines
3000 LET P=0: LET H=0: LET V=0: LET X=0: LET T=0: LET S=0: LET A=0: LET B=0: RETURN
3100 LET P=X: LET H=0: LET V=0
3110 IF X<0 OR X>255 OR X<>INT (X) THEN RETURN
3120 IF T<0 OR T>255 OR T<>INT (T) THEN RETURN
3130 IF S<1 OR S>255 OR S<>INT (S) THEN RETURN
3140 IF A<0 OR A>255 OR A<>INT (A) THEN RETURN
3150 IF B<0 OR B>255 OR B<>INT (B) THEN RETURN
3160 IF A>=B THEN RETURN
3170 LET D=X+S: LET E=T+S
3180 IF D>255 OR E>255 THEN RETURN
3190 IF X<B AND D>A THEN RETURN
3200 LET V=1: LET P=T
3210 IF T>X AND D<=A AND E>=A THEN LET P=A-S: LET H=1: RETURN
3220 IF T<X AND X>=B AND T<=B THEN LET P=B: LET H=1
3230 RETURNRightward motion follows the object’s right edge towards the wall’s left face. Leftward motion follows its left edge towards the opposite face. The complete crossing is checked with endpoint comparisons; there is no need to visit every intervening pixel.
Keep old and requested positions separate
The normal caller moves from 10 towards 100 with size 5 against wall [50,60). It stops at 45 and reports contact. The custom caller approaches from the right and stops at 60. Later calls check moving away and stopping just short of contact.
Example calls from the maintained caller
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET X=10: LET T=100: LET S=5: LET A=50: LET B=60
90 GOSUB 3100: GOSUB 2000
100 IF P=45 THEN LET AA=1
110 IF H=1 THEN LET BB=1
120 LET X=45: LET T=44: LET S=5: LET A=50: LET B=60: GOSUB 3100: GOSUB 2000
130 LET X=60: LET T=61: LET S=5: LET A=50: LET B=60: GOSUB 3100: GOSUB 2000
140 LET X=10: LET T=44: LET S=5: LET A=50: LET B=60: GOSUB 3100: GOSUB 2000
150 PRINT "PATH RESULTS";AA;BB
160 ?(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 the mailbox are diagnostics. A game computes its desired endpoint, supplies the query and applies P only after handling V/H. It can then choose its own stop, bounce or slide response.
Customise motion and response
Use this for one static wall interval along one axis. A full 2D collision system must also check overlap on the other axis and define ordering or a joint time-of-impact policy. Calling an axis helper twice does not automatically create a complete continuous solver for moving shapes.
The byte envelope matches the assembly contract; an end edge 256 is outside it. Wider coordinates, moving walls, acceleration during the step and several competing obstacles need deliberate extensions and new tests. A slower endpoint-only overlap test may be enough when movement is small, but it has a different guarantee.
Mistake and repair
The wrong caller passes the old position as T. The request becomes stationary, so no movement or contact is reported. Both saved checks fail. Keep the requested endpoint distinct from the position you are moving from. The repair matches normal source and loaded program exactly.

Cost and Maths for Games
The helper uses fixed validation and comparisons, independent of travel distance. Private D/E hold endpoint sums before checking the byte bound, so overflow is rejected before it could become a small wrapped endpoint. BASIC numeric storage and interpreter overhead still apply; no cycle budget is claimed.
Maths for Games connects intervals, swept motion and contact boundaries. The Craft connects tunnelling prevention to fast projectiles, predictable walls and the separate choice of collision response.
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 437 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-geometry.py swept-axis-collision bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-geometry.py swept-axis-collision.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.