Skip to content

Test a Point Against a Circle in BBC Micro BASIC

Check an inclusive circular boundary without a square root, preserving full coordinate differences and enough arithmetic range for squared distances.

A round pickup should not behave like a square. A box can reject obviously distant points cheaply; this query follows that coarse test with the circular boundary, including points exactly on its edge.

BBC Micro BASIC shows both retained behaviour checks passing.
The working caller completes with both saved checks equal to one.

Include and initialise

Reserve public F/D/E/X/Y/A/B/R/O. There are no arrays or private scratch fields. Reserve lines 3000–3190, combine routines.bas and your caller using build.py and keep END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB.

Call 3000 at a reset boundary. It clears every public field. Supply point X/Y, centre A/B and radius R afterwards. Each is an integral byte in the same non-wrapping coordinate space.

Test the round boundary

Call 3100. Valid input sets O=1. D/E are absolute horizontal/vertical differences, and F=1 when D*D+E*E <= R*R. The boundary is included. Radius zero accepts only the centre. Inputs survive.

Field Use
X / Y / A / B / R Point, centre and radius; preserved
D / E Absolute coordinate differences
F / O Inside-or-on-boundary / valid flag

Invalid numeric input returns F=D=E=O=0. Valid outside points still return their real D/E with O=1,F=0. This validity guard is an explicit BASIC addition to the assembly helper’s byte-valued input contract.

The reusable routine

Reusable numbered routines

routines.bas
3000 LET F=0: LET D=0: LET E=0: LET X=0: LET Y=0: LET A=0: LET B=0: LET R=0: LET O=0: RETURN
3100 LET F=0: LET D=0: LET E=0: LET O=0
3110 IF X<0 OR X>255 OR X<>INT (X) THEN RETURN
3120 IF Y<0 OR Y>255 OR Y<>INT (Y) THEN RETURN
3130 IF A<0 OR A>255 OR A<>INT (A) THEN RETURN
3140 IF B<0 OR B>255 OR B<>INT (B) THEN RETURN
3150 IF R<0 OR R>255 OR R<>INT (R) THEN RETURN
3160 LET D=ABS (X-A): LET E=ABS (Y-B): LET O=1
3170 IF D>R OR E>R THEN RETURN
3180 IF D*D+E*E<=R*R THEN LET F=1
3190 RETURN

A point farther than R on either axis is immediately outside. Otherwise the helper compares squared distances. It needs no square root and writes no screen or collision response. The BASIC numeric intermediates safely cover the largest byte-coordinate sum, 130,050; an unsigned 16-bit sum alone would not.

Keep both axes

The normal caller tests differences 4 and 4 against radius 5: the point is outside even though it passes the box test. The custom caller uses the familiar 3–4–5 boundary and reports inside. Later queries cover radius zero and opposite corners.

Example calls from the maintained caller

demo.bas, excerpt
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET X=104: LET Y=104: LET A=100: LET B=100: LET R=5
90 GOSUB 3100: GOSUB 2000
100 IF D=4 AND E=4 THEN LET AA=1
110 IF Y=104 THEN LET BB=1
120 LET X=100: LET Y=100: LET A=100: LET B=100: LET R=0: GOSUB 3100: GOSUB 2000
130 LET X=101: LET Y=100: LET A=100: LET B=100: LET R=0: GOSUB 3100: GOSUB 2000
140 LET X=255: LET Y=255: LET A=0: LET B=0: LET R=255: GOSUB 3100: GOSUB 2000
150 PRINT "BALL 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: STOP

Line 2000, AA/BB/JJ (Z on Spectrum) and the mailbox are diagnostics. A game supplies its coordinates/radius, calls 3100 and handles O/F. The saved summary checks verify the intended coordinate differences and original point; the complete native trace separately verifies the hit result after every query.

Customise shape and space

Use one coordinate origin for point and centre. This is ordinary non-wrapping geometry: zero and 255 are 255 units apart, not one. A toroidal world needs a different shortest-wrapped-distance rule before applying the circle test.

A box-only test is cheaper but accepts corner points outside the circle. Combining a cheap box rejection with this exact round test can save multiplication for distant candidates. Ellipses, circles against rectangles and pixel masks need their own shape-specific rules. Wider coordinates need a sufficiently wide squared intermediate and separate range checks/tests.

Mistake and repair

The wrong caller replaces Y with the centre’s vertical coordinate. The outside point is tested as though it lay on the horizontal axis, so it becomes a false hit. Both saved checks fail because the vertical difference and supplied point have changed. Preserve both axes in the same space. Repaired source and loaded program match the normal caller.

BBC Micro BASIC shows two failed checks from the deliberate caller mistake.
The wrong caller runs to completion, but both saved behaviour checks are zero.

Cost and Maths for Games

There are fixed input checks and two absolute differences. The circular stage uses three multiplications only when the axis checks survive. At differences D=255, E=255 and radius 255, the sum must remain 130,050; wrapping it to sixteen bits could create a false hit. The qualified BASIC interpreters retain the required integer precision. No native-cycle or frame-time claim is made.

Maths for Games connects Pythagoras, squared comparisons, radius-zero and intermediate width. The Craft connects shape choice and coarse-to-fine tests to the collision behaviour a player expects.

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 354 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 point-circle bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-geometry.py point-circle.

Explore further

Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.