Skip to content

Rectangle Overlap

Reuse a checked half-open rectangle test, understand touching edges and diagnose the equality mistake.

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

collision.inc
; 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
demo.asm
    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
caller.inc
; 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
custom.asm
    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
wrong-touch.inc
; 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
The working overlap caller displays 1.
Shared area: the supplied boxes overlap.
The faulty touching-box caller incorrectly displays 1.
Common error: equality is accepted, so an edge contact incorrectly reports overlap. The repaired caller returns 0.

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.