Skip to content

Face a Target in Eight Directions in C64 BASIC

Choose a discrete facing from two positions using signed displacement and a deliberate 2:1 sector rule.

An enemy can face a target without first calculating an angle. Find the relative position, compare the two magnitudes and choose a useful sprite direction. The important detail is which sector boundaries the game actually wants.

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

Include and initialise

Reserve public D/U/V/X/Y/A/B/O, private S/T and lines 3000–3300. There are no arrays. Combine routines.bas with a caller using build.py, keeping 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.

Supply origin X/Y and target A/B as integral byte coordinates in the same space. Screen Y increases downwards. Call 3100. U/V become unsigned horizontal/vertical magnitudes and O=1 reports valid input. Inputs survive. Invalid coordinates clear D/U/V/O and preserve the inputs.

Read the facing

D is zero for coincident points. Otherwise the direction codes run clockwise: one north, two north-east, three east, four south-east, five south, six south-west, seven west and eight north-west. These codes are local to this interface; they differ from the four-way input/grid direction codes.

Reusable numbered routines

routines.bas
3000 LET D=0: LET U=0: LET V=0: LET X=0: LET Y=0: LET A=0: LET B=0: LET O=0: RETURN
3100 LET D=0: LET U=0: LET V=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 LET S=A-X: LET T=B-Y: LET U=ABS (S): LET V=ABS (T): LET O=1
3160 IF U=0 AND V=0 THEN RETURN
3170 IF U>=2*V THEN GOTO 3250
3180 IF V>=2*U THEN GOTO 3280
3190 LET D=8
3200 IF S>0 AND T<0 THEN LET D=2
3210 IF S>0 AND T>0 THEN LET D=4
3220 IF S<0 AND T>0 THEN LET D=6
3230 RETURN
3250 LET D=7
3260 IF S>0 THEN LET D=3
3270 RETURN
3280 LET D=1
3290 IF T>0 THEN LET D=5
3300 RETURN

A cardinal direction wins when its magnitude is at least twice the other. Remaining vectors are diagonal. The doubled intermediate can reach 510, so it stays wide before comparison. Signed displacement is computed before taking its magnitude: subtracting byte coordinates must not wrap a negative result.

Compare two targets

The normal caller faces from 100,100 towards 103,107. Vertical magnitude seven is at least twice horizontal three, so it returns south, code five. The custom target 91,94 returns north-west, code eight, with magnitudes nine and six. Further calls cover coincident points, an exact threshold and extreme coordinates.

Example calls from the maintained caller

demo.bas, excerpt
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET X=100: LET Y=100: LET A=103: LET B=107: GOSUB 3100: GOSUB 2000
90 IF D=5 THEN LET AA=1
100 IF V=7 THEN LET BB=1
110 LET X=0: LET Y=0: LET A=0: LET B=0: GOSUB 3100: GOSUB 2000
120 LET X=0: LET Y=0: LET A=2: LET B=1: GOSUB 3100: GOSUB 2000
130 LET X=255: LET Y=255: LET A=0: LET B=0: GOSUB 3100: GOSUB 2000
140 PRINT "FACE RESULTS";AA;BB
150 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: STOP

Line 2000, AA/BB/JJ (Z on Spectrum) and the mailbox are diagnostic observations. A game maps D to its own animation frames or facing policy. The helper chooses a facing; it does not move the actor, normalise diagonal speed or decide whether the target is visible.

Choose your sectors deliberately

This is a 2:1 slope quantiser, not eight equal angular sectors with exact 22.5-degree boundaries. Its comparison rule is cheap and predictable. Finer angle tables or another ratio can give a different visual result, with additional data or arithmetic.

A facing can chatter near a boundary when positions change slightly. Hysteresis needs retained direction and a deliberate switching rule; this independent query has neither. Wider or signed world coordinates need wider input bounds and corresponding verification. Separate facing contexts need renamed globals/line references when combined with other helpers.

Mistake and repair

The wrong caller replaces the target’s vertical coordinate with the origin’s Y. Vertical magnitude becomes zero, producing east or west instead of the intended facing. Both saved direction and magnitude checks fail. Keep both coordinates in the same space through the query. Repaired source and loaded program match normal exactly.

C64 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

The query uses fixed validation, signed differences, magnitudes and comparisons. It needs no trigonometric function, lookup table, allocation or device access. Assembly may compare half-magnitudes to avoid a narrow doubled value; BASIC can retain the widened intermediate here. That difference does not imply a whole-game frame budget.

Maths for Games connects displacement, quadrants, slope and quantisation. The Craft connects discrete facings to readable animation and aiming.

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

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. This helper uses scalar numeric fields and allocates no arrays. The diagnostic uses a mailbox at $C000, outside the normal BASIC heap.

The numbered helper occupies 495 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 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-world-policy.py eight-way-direction commodore-64 --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-world-policy.py eight-way-direction.

Explore further

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