Code status: tokenised/assembled and emulator-executed. This is the assembly 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.
Assembly contract
Include the maintained ../../framework/collect-and-respond/collision.inc once, outside fall-through execution. It is unchanged. Write rect_a and rect_b (four bytes each), then call boxes_overlap. Carry set means overlap; carry clear means empty/apart. AF/B change; C/DE/HL/IX/IY survive. There is no initialisation or I/O. It uses eight edge-state bytes and the caller’s normal CALL stack; serialize access with interrupt code.
Edges are unsigned bytes, 0–255. Do not wrap a right edge from 260 to 4. Translate/clip to a validated local domain, or choose a wider implementation. Negative coordinates are unsupported. A left edge of 255 cannot form a positive-width box in this domain.
caller.inc owns stack/display and retains stock IY for ROM CLS. It uses the same font writer as the maintained input entry, then stays at a hold label. This is not a returning BASIC USR routine. The helper’s actual code bytes and full payload size are recorded separately in verification/results.json; the 184-case table, output bytes, font writer and diagnostic caller are not collision-helper costs. No cycle measurement is claimed.
Costs and independent execution
The assembly helper is 84 instruction bytes plus eight edge bytes. Its full diagnostic/test payload is 1,903 bytes; the deliberate fault adds eight code bytes.
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
; Half-open unsigned byte edges [left,top,right,bottom].
; Reject empty/reversed boxes and edge-only contact. C=1 means overlap.
; Changes AF/B; preserves C/DE/HL/IX/IY. Caller forms edges without wrapping.
boxes_overlap:
ld a,(rect_a+2)
ld b,a
ld a,(rect_a)
cp b
jr nc,boxes_apart
ld a,(rect_a+3)
ld b,a
ld a,(rect_a+1)
cp b
jr nc,boxes_apart
ld a,(rect_b+2)
ld b,a
ld a,(rect_b)
cp b
jr nc,boxes_apart
ld a,(rect_b+3)
ld b,a
ld a,(rect_b+1)
cp b
jr nc,boxes_apart
ld a,(rect_b+2)
ld b,a
ld a,(rect_a)
cp b
jr nc,boxes_apart
ld a,(rect_a+2)
ld b,a
ld a,(rect_b)
cp b
jr nc,boxes_apart
ld a,(rect_b+3)
ld b,a
ld a,(rect_a+1)
cp b
jr nc,boxes_apart
ld a,(rect_a+3)
ld b,a
ld a,(rect_b+1)
cp b
jr nc,boxes_apart
scf
ret
boxes_apart:
or a
ret
rect_a: defb 0,0,0,0
rect_b: defb 10,5,11,6 org $8000
include "cases.inc"
include "caller.inc"
include "../../framework/collect-and-respond/collision.inc"
include "display.inc"
example: defb 2,2,8,6,6,4,11,7
include "case-table.inc"
end start; 48K PAL stock ROM; own stack/display, reset to leave.
start:
di
ld sp,$ff00
call $0daf
ld hl,case_table
ld ix,results
ld a,CASE_COUNT
ld (remaining),a
case_loop:
ld de,rect_a
ld bc,8
ldir
call boxes_overlap
ld a,0
adc a,0
ld (ix+0),a
inc ix
ld a,(remaining)
dec a
ld (remaining),a
jr nz,case_loop
ld a,165
ld (completed),a
ld hl,example
ld de,rect_a
ld bc,8
ldir
call boxes_overlap
ld a,0
adc a,0
ld (result),a
add a,48
ld de,$4000
call show_char
ld a,239
ld (ready),a
hold:
jr hold
completed: defb 0
ready: defb 0
remaining: defb 0
result: defb 0
results: defs CASE_COUNT,0 org $8000
include "cases.inc"
include "caller.inc"
include "../../framework/collect-and-respond/collision.inc"
include "display.inc"
example: defb 2,2,8,6,8,2,13,6
include "case-table.inc"
end start; Deliberate fault: accept touching boundaries.
; Half-open unsigned byte edges [left,top,right,bottom].
; Reject empty/reversed boxes and edge-only contact. C=1 means overlap.
; Changes AF/B; preserves C/DE/HL/IX/IY. Caller forms edges without wrapping.
boxes_overlap:
ld a,(rect_a+2)
ld b,a
ld a,(rect_a)
cp b
jr nc,boxes_apart
ld a,(rect_a+3)
ld b,a
ld a,(rect_a+1)
cp b
jr nc,boxes_apart
ld a,(rect_b+2)
ld b,a
ld a,(rect_b)
cp b
jr nc,boxes_apart
ld a,(rect_b+3)
ld b,a
ld a,(rect_b+1)
cp b
jr nc,boxes_apart
ld a,(rect_b+2)
ld b,a
ld a,(rect_a)
cp b
jr z,wrong_accept_5
jr nc,boxes_apart
wrong_accept_5:
ld a,(rect_a+2)
ld b,a
ld a,(rect_b)
cp b
jr z,wrong_accept_6
jr nc,boxes_apart
wrong_accept_6:
ld a,(rect_b+3)
ld b,a
ld a,(rect_a+1)
cp b
jr z,wrong_accept_7
jr nc,boxes_apart
wrong_accept_7:
ld a,(rect_a+3)
ld b,a
ld a,(rect_b+1)
cp b
jr z,wrong_accept_8
jr nc,boxes_apart
wrong_accept_8:
scf
ret
boxes_apart:
or a
ret
rect_a: defb 0,0,0,0
rect_b: defb 10,5,11,6

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.