Code status: tokenised/assembled and emulator-executed. This is the basic route, independently executed on the stock-ROM PAL 48K Spectrum. Put two boxes together and the result is 1. Move one until they only touch and it becomes 0.
Run make, load build/demo.tap through the 48K ROM tape loader and let its line-10 loader run. The normal digit is 1. The data-only customisation puts B at A’s right edge and produces 0. wrong accepts equality in the separation checks, so the same touching geometry incorrectly says 1; restore the original comparisons. repaired has exactly the custom caller’s payload. Reset to leave the assembly caller; BASIC stops normally.
These are half-open edge rectangles: left, top, right, bottom. [2,2,8,6] is six units wide and four high. Zero/reversed extents are empty, touching edges do not share area. Form edges without overflowing before calling. Neither helper adds position and size. Smaller collision boxes can make a player more forgiving; larger interaction boxes can make items easier to collect. Keep visual bounds and response policy separate.
BASIC contract
Merge overlap.bas lines 3000–3090 into a project whose line numbers are free. Set al/ay/ar/ab for A and bl/by/br/bb for B, then GO SUB 3000. rr returns 1 for overlap, 0 for empty/apart and -1 for invalid range. The eight input scalars survive; rr is the only helper output/state scalar. It uses BASIC’s normal GO SUB stack and no hardware, arrays or allocation beyond those numeric variables. Avoid name collisions and nested use of this shared state.
All edges must be whole numbers from 0–65535. This chosen bounded contract is wider than the assembly byte helper, not a claim that Sinclair BASIC has a 16-bit integer type. The ROM’s number format represents these integers exactly. Negative, too-large and fractional inputs return -1 before geometry is tested. Width/height are not inputs: form the edges first, then check rr. Invalid inputs are not silently treated as a hit.
caller.bas is the small usable example. Its CLEAR, POKE observation markers and screen setup belong to the native demonstration, not the helper. CLEAR 49999 reserves observation memory at 50000; a normal project using only the helper need not do that. The independent fixture has a much larger DATA table and explicit result bytes. Native tokenised program and variable extents are recorded separately; they include the chosen caller/runtime variables and are not a universal memory budget. BASIC does repeated interpreted comparisons and INT calls: it trades speed for readability and validation.
Costs and independent execution
The small BASIC caller plus helper tokenises to 702 bytes and leaves 64 bytes of native variables in this run. The independent 187-case DATA caller is 18,613 token bytes and 83 variable bytes; do not mistake that test fixture for the reusable helper cost. CLEAR 49999 reserves the upper RAM region for observations; only the qualification caller requires it.
The native verifier loads the real tape through the stock ROM. Assembly traverses 184 geometry cases in guest code. BASIC’s independent DATA/READ caller covers the same family at its wider limit plus negative, fractional and too-large inputs (187 cases). An independent host max/min interval oracle checks results; host arithmetic is not treated as native proof. Completion markers, full loaded code/tokenised listing, tool/ROM identities and screenshot glyphs must agree. Working/custom/wrong/repaired captures are retained. Falsification tests reject corrupted code, results, captures and provenance.
Run python3 scripts/check-spectrum-rectangle-overlap.py and python3 -m unittest discover -s scripts -p test_spectrum_rectangle_overlap.py -v from the repository root. Re-capture with verify-spectrum-rectangle-overlap.py and its explicit assembler/build198x/emulator/ROM paths. Selected tools: Asm198x 0.0.72, Build198x 0.2.9, Emu198x 0.44.2; Spectrum 48K PAL with the recorded stock ROM. No ROM is distributed. Qualification does not establish original-hardware timing or every Spectrum model.
Intervals, coordinate conversion and numerical range connect directly to Maths for Games. Boxes can reject most candidates before a more expensive mask/pixel narrow phase. Those refinements need their own code, data and measured CPU budget; they are not implied by these routines.
Maintained listings
3000 REM RR=-1 INVALID RANGE, 0 EMPTY/APART, 1 OVERLAP
3010 LET rr=-1
3020 IF al<0 OR ay<0 OR ar<0 OR ab<0 OR bl<0 OR by<0 OR br<0 OR bb<0 THEN RETURN
3030 IF al>65535 OR ay>65535 OR ar>65535 OR ab>65535 OR bl>65535 OR by>65535 OR br>65535 OR bb>65535 THEN RETURN
3040 IF al<>INT al OR ay<>INT ay OR ar<>INT ar OR ab<>INT ab OR bl<>INT bl OR by<>INT by OR br<>INT br OR bb<>INT bb THEN RETURN
3050 LET rr=0
3060 IF al>=ar OR ay>=ab OR bl>=br OR by>=bb THEN RETURN
3070 IF al>=br OR bl>=ar OR ay>=bb OR by>=ab THEN RETURN
3080 LET rr=1
3090 RETURN 10 CLEAR 49999: BORDER 0: PAPER 0: INK 7: CLS
20 LET al=2: LET ay=2: LET ar=8: LET ab=6
30 LET bl=6: LET by=4: LET br=11: LET bb=7
40 GO SUB 3000
50 PRINT AT 0,0;rr
60 POKE 50000,rr+1: POKE 50001,165
70 STOPimport os
import subprocess
from pathlib import Path
F = Path(__file__).parent
B = F / "build"
B.mkdir(exist_ok=True)
for name in ("demo", "custom", "wrong", "repaired"):
caller = (F / "caller.bas").read_text()
if name != "demo":
caller = caller.replace(
"LET bl=6: LET by=4: LET br=11: LET bb=7",
"LET bl=8: LET by=2: LET br=13: LET bb=6",
)
helper = (F / "overlap.bas").read_text()
if name == "wrong":
helper = helper.replace(
"al>=br OR bl>=ar OR ay>=bb OR by>=ab", "al>br OR bl>ar OR ay>bb OR by>ab"
)
text = "".join(
f"{int(n):4d} {body}\n"
for n, body in (
line.split(maxsplit=1) for line in (caller + helper).splitlines()
)
)
(B / (name + ".bas")).write_text(text)
subprocess.run(
[
os.environ.get("BUILD198X", "build198x"),
"basic",
str(B / (name + ".bas")),
"--machine",
"sinclair-zx-spectrum",
"-o",
str(B / (name + ".tap")),
],
check=True,
)

Pinned maintained source and full contract · Native configuration and evidence. Maths for Games explains intervals, coordinate ranges and overflow; AABB geometry is the language-independent companion.