Skip to content

Clip a Rectangle to a View in C64 BASIC

Find the visible intersection of two half-open rectangles, separating an empty result from invalid reversed edges.

A sprite crosses the edge of a panel. The drawing routine needs the part inside that panel, not the part that spills into the score display. This query returns the rectangle that remains visible; a separate drawing routine decides how to render it.

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

Include and initialise

DIM C(4), A(4) and B(4). Reserve public F/V, output C, input rectangles A/B and private I. Reserve numbered lines 3000–3400, combine routines.bas with a caller using build.py and keep END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. Private I must not be an outer caller’s FOR variable.

Call 3000 at a reset boundary. It clears all fields and arrays. Supply A and B afterwards, each in left, top, right, bottom order. In this byte-contract example every edge is an integer from zero to 255.

Clip with half-open edges

Call 3100. The rectangles use half-open edges: the left/top edges are included and the right/bottom edges are excluded. A horizontal span [10,20) covers ten integer positions, from ten through nineteen. Adjacent spans can meet at an edge without sharing a pixel.

Field Use
A(1)..A(4) / B(1)..B(4) Object / viewport edges; preserved
C(1)..C(4) Visible left, top, right, bottom
F / V Has visible area / valid request

V=0 rejects a non-byte/non-integral edge or reversed horizontal/vertical edges. Outputs F/C become zero. Equal edges are valid empty rectangles. A valid empty intersection returns F=0,V=1,C all zero; a visible one returns F=1,V=1 and its intersection. The helper retains no state between queries and changes no display or drawing mode.

The reusable routine

Reusable numbered routines

routines.bas
3000 LET F=0: LET V=0: FOR I=1 TO 4: LET C(I)=0: LET A(I)=0: LET B(I)=0: NEXT I: RETURN
3100 LET F=0: LET V=0: FOR I=1 TO 4: LET C(I)=0: NEXT I
3110 FOR I=1 TO 4
3120 IF A(I)<0 OR A(I)>255 OR A(I)<>INT (A(I)) THEN RETURN
3130 IF B(I)<0 OR B(I)>255 OR B(I)<>INT (B(I)) THEN RETURN
3140 NEXT I
3150 IF A(1)>A(3) OR A(2)>A(4) OR B(1)>B(3) OR B(2)>B(4) THEN RETURN
3160 LET V=1: LET C(1)=A(1): LET C(2)=A(2): LET C(3)=A(3): LET C(4)=A(4)
3170 IF B(1)>C(1) THEN LET C(1)=B(1)
3180 IF B(2)>C(2) THEN LET C(2)=B(2)
3190 IF B(3)<C(3) THEN LET C(3)=B(3)
3200 IF B(4)<C(4) THEN LET C(4)=B(4)
3210 IF C(1)>=C(3) OR C(2)>=C(4) THEN GOTO 3400
3220 LET F=1: RETURN
3400 FOR I=1 TO 4: LET C(I)=0: NEXT I: RETURN

The visible start is the greater left/top edge; the visible end is the lesser right/bottom edge. Both width and height must be strictly positive. Touching at an edge gives no visible area, and every empty result is normalised to four zeroes.

Clip to the actual panel

The normal caller clips a 60-by-60 rectangle against a view from 10 to 40 on each axis. The result is [10,40) on both axes. The custom caller clips the maximum byte-bounded rectangle into a small inset view, then both callers query outside and empty rectangles too.

Example calls from the maintained caller

demo.bas, excerpt
110 GOSUB 3000: LET AA=0: LET BB=0
120 LET A(1)=0: LET A(2)=0: LET A(3)=60: LET A(4)=60
130 LET B(1)=10: LET B(2)=10: LET B(3)=40: LET B(4)=40
140 GOSUB 3100: GOSUB 2000
150 IF C(3)=40 THEN LET AA=1
160 IF C(3)-C(1)=30 THEN LET BB=1
170 LET A(1)=0: LET A(2)=0: LET A(3)=5: LET A(4)=5: GOSUB 3100: GOSUB 2000
180 LET A(1)=10: LET A(2)=10: LET A(3)=10: LET A(4)=20: GOSUB 3100: GOSUB 2000
190 LET A(1)=0: LET A(2)=0: LET A(3)=255: LET A(4)=255: GOSUB 3100: GOSUB 2000
200 PRINT "CLIP RESULTS";AA;BB
210 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/KK (Z/Q on Spectrum) and the mailbox belong to the diagnostic. A game supplies A/B, calls 3100 and uses C only with the appropriate F/V result. Its drawing backend can convert the visible offset into a source-sprite offset; that upload is separate from this query.

Customise coordinates

Use the same units and origin for both rectangles: pixels, character cells or map cells. Clipping a world-space rectangle against an unrelated screen-space view is a caller error. Convert between those spaces deliberately.

The byte envelope matches the assembly example. An exclusive edge 256 cannot be represented here, and this does not describe every machine’s full screen. BASIC can hold wider values; widening this contract means changing bounds and observation/storage encoding, then verifying the larger range. Do not assume the present byte tests establish a 320-pixel or larger viewport.

Complex shapes need a different clipping method. A bounding rectangle can cheaply reject an object before more expensive per-pixel work, but it does not itself prove that an opaque pixel remains visible.

Mistake and repair

The wrong caller replaces the panel’s right edge with 255. The clipped rectangle then leaks past the intended panel; both saved checks fail. Supply the real viewport bounds, not the largest representable coordinate. Repaired source and loaded program match the normal caller.

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

This is a bounded set of edge checks and comparisons, with four result cells. It performs no pixel visits or allocation. BASIC arrays occupy interpreter-managed numeric storage, not a packed fourteen-byte native structure. No frame-time budget is inferred.

Maths for Games connects interval intersections, coordinate spaces and half-open bounds. The Craft connects those rules to clean panels, camera windows and objects entering or leaving the view.

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. Array element zero exists but is unused here. The diagnostic uses a mailbox at $C000, outside the normal BASIC heap.

The numbered helper occupies 543 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/KK 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-geometry.py rectangle-clipping commodore-64 --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-geometry.py rectangle-clipping.

Explore further

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