A projectile can ignore its owner while still testing opponents in the same patch of screen. Group rules answer whether a pair is worth considering; geometry answers whether those two objects actually touch.

Include and initialise
Reserve public K/A/B/C/D/S/T/O, private E/F/G/H/I/U/V/J/L and lines 3000–3280. There are no arrays. Private I must not be the caller’s outer FOR variable. Combine routines.bas and a caller through build.py, keeping END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. Call 3000 to clear the public fields, then supply the two objects’ policies.
A/B are the first object’s group-membership and accepted-group masks. C/D are the second object’s membership and accepted-group masks. S/T are their solidity flags, zero false and nonzero true. Every input is an integral byte; each bit can represent a chosen category.
Classify the pair
Call 3100. Both directions must accept: some bit of A must be present in D, and some bit of C in B. If either rejects, K=0. An accepted pair returns K=1 when at least one object is non-solid, otherwise K=2. O=1 reports a valid policy query; invalid input clears K/O and preserves all inputs.
Reusable numbered routines
3000 LET K=0: LET A=0: LET B=0: LET C=0: LET D=0: LET S=0: LET T=0: LET O=0: RETURN
3100 LET K=0: LET O=0
3110 IF A<0 OR A>255 OR A<>INT (A) THEN RETURN
3120 IF B<0 OR B>255 OR B<>INT (B) THEN RETURN
3130 IF C<0 OR C>255 OR C<>INT (C) THEN RETURN
3140 IF D<0 OR D>255 OR D<>INT (D) THEN RETURN
3150 IF S<0 OR S>255 OR S<>INT (S) THEN RETURN
3160 IF T<0 OR T>255 OR T<>INT (T) THEN RETURN
3170 LET E=A: LET F=D: LET G=C: LET H=B: LET J=0: LET L=0
3180 FOR I=1 TO 8
3190 LET U=E-2*INT (E/2): LET V=F-2*INT (F/2)
3200 IF U=1 AND V=1 THEN LET J=1
3210 LET U=G-2*INT (G/2): LET V=H-2*INT (H/2)
3220 IF U=1 AND V=1 THEN LET L=1
3230 LET E=INT (E/2): LET F=INT (F/2): LET G=INT (G/2): LET H=INT (H/2): NEXT I
3240 LET O=1
3250 IF J=0 OR L=0 THEN RETURN
3260 LET K=1
3270 IF S<>0 AND T<>0 THEN LET K=2
3280 RETURN| K | Meaning before the spatial test |
|---|---|
| 0 | Ignored by policy |
| 1 | Accepted sensor pair |
| 2 | Accepted pair with both solid flags nonzero |
K=1 or two is not evidence of contact. A spatial test still has to establish overlap, followed by the game’s sensor event or collision response. Zero masks or membership sets are valid and can reject every pair deliberately.
Check both directions
The first caller query has B=0, so the first object accepts nobody even though the second accepts it. The correct result is ignored. Later queries enable the mask, reject from the reverse direction and finally accept a solid pair. The custom caller uses a high membership bit and a sensor flag.
Example calls from the maintained caller
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET A=1: LET B=0: LET C=2: LET D=1: LET S=1: LET T=1: GOSUB 3100: GOSUB 2000
90 IF K=0 THEN LET AA=1
100 IF B=0 THEN LET BB=1
110 LET B=2: GOSUB 3100: GOSUB 2000
120 LET D=0: GOSUB 3100: GOSUB 2000
130 LET D=1: LET S=1: LET T=1: GOSUB 3100: GOSUB 2000
140 PRINT "PAIR 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 the mailbox are diagnostics. A game supplies stable bit meanings and consumes K/O before choosing its spatial and response paths. The masks describe policy, not a screen cell, sprite ID or hardware collision register.
Customise collision rules
Use membership sets when an object belongs to several categories. Use acceptance masks to choose which of those categories matter to each object. Mutual acceptance keeps one object’s broad mask from silently overriding the other’s exclusions.
The portable BASIC implementation walks eight bit positions because bitwise facilities differ. A native mask operation can be cheaper after checking width and signedness. Filtering can save expensive geometry work when it rejects enough pairs; it also adds work of its own. Measure the actual candidate population rather than assuming that another filter always helps.
Mistake and repair
The wrong caller widens the intended B=0 mask to 255 before asking the helper. A rejected pair becomes accepted. Both saved kind and preserved-policy checks fail: the helper cannot recover the caller’s original intention. Keep authoritative masks intact and pass the intended policy. Repaired source and loaded program match normal exactly.

Cost and Maths for Games
The component makes a bounded eight-bit comparison of the two intersections. It reads no geometry, allocates nothing and performs no response or device write. Broad-phase buckets, box tests and pixel-level tests remain separate choices with their own costs.
Maths for Games connects membership sets and mutual intersections. The Craft connects collision categories, sensor events and the rules the player experiences.
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 590 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-world-policy.py collision-layer-filter bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-world-policy.py collision-layer-filter.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.