.png)
The bullet sails through the asteroid. The rocket parks inside it. Nothing in the machine objects, because nothing in the machine knows they are things: the VDP draws rectangles of pixels where it is told, every frame, and overlap is not an error - it is Tuesday. c137 put one sprite in front of another on purpose; this is the same event wearing a different meaning.
Touching is a meaning games add. A pickup, a wall, a kill - all of them begin as the same question: do these two rectangles overlap right now? This section builds the routine that answers it, and answers it exactly.
The VDP does keep watch: one bit in its status register goes up when any two sprites' drawn pixels meet anywhere on screen. One bit. It does not say which two sprites, or where - and reading it wipes it, along with flags the machine's timing depends on, under rules S25 will spell out. As a game's collision system, "somewhere, something touched" answers almost nothing: the pair that matters - bullet and enemy? player and pickup? - is exactly what it cannot name.
So games do collision themselves, from the coordinates they already own. Your objects' positions live in RAM - the sprites are just where those numbers get published - which means the question "do they overlap?" never needs the chip at all. It is arithmetic on your own variables.
Two 8-pixel boxes overlap unless something separates them. There are only four somethings: entirely left, entirely right, entirely above, entirely below. Rule out all four and they are touching - there is nowhere else to be.
A box at X occupies columns X through X+7 - eight of them, counting the first. So "A is entirely left of B" is precisely:
A's last column < B's first
AX + 7 < BX
LD A,D ; asteroid X
ADD A,7 ; its LAST column
CP B ; against the body's first
JR C,miss ; strictly left - a separating edge
Four of those - two horizontal, two vertical - and falling through them all IS the collision. No overlap arithmetic, no widths multiplied: just "is there daylight on any side?"
Why 7 and not 8? Because X+7 is the last pixel and X+8 is the fence past it - and the difference is not pedantry, it is two real bugs:
The rocket is sixteen pixels tall and its box in this section is eight - the body. Flying the flame through the asteroid costs nothing, and that is not a compromise: exhaust is not ship. What can be hit is a design decision that happens to be expressed in geometry, and most good games lie about it in the player's favour - hitboxes a little smaller than the art, so every near miss feels earned. The picture is for the eyes; the box is for the rules. They answer to different masters.
The test needs somewhere to say what it found. The backdrop is ideal: one register write, visible everywhere, and gone next pass if the answer changes:
LD A,246 ; hit: dark red backdrop
JR tset
miss: LD A,244 ; miss: the machine's blue
tset: OUT (9),A
LD A,135 ; register 7
OUT (9),A
Note it repaints every pass, both ways. Set it only on hit and the first touch stains the screen forever - a latch, when what you want while building a game is an instrument.
ORG 256
; --- five patterns: body, flame A, flame B, bullet, asteroid
LD A,0
OUT (9),A
LD A,88
OUT (9),A
LD HL,rocket
LD B,40
pat: LD A,(HL)
OUT (8),A
INC HL
DJNZ pat
; --- the asteroid sits still: publish it once, then the terminator
LD A,12
OUT (9),A
LD A,123
OUT (9),A
LD A,(astY)
OUT (8),A
LD A,(astX)
OUT (8),A
LD A,4
OUT (8),A
LD A,3 ; light green
OUT (8),A
LD A,208 ; close the list at sprite 4
OUT (8),A
main: CALL place
CALL pause
CALL input
CALL animate
CALL bullet
CALL touch
JR main
; --- the box test: any separating edge means no collision
touch: LD A,(rockX)
LD B,A
LD A,(rockY)
LD C,A
LD A,(astX)
LD D,A
LD A,(astY)
LD E,A
LD A,D
ADD A,7 ; asteroid's last column
CP B
JR C,miss ; entirely left of the body
LD A,B
ADD A,7 ; body's last column
CP D
JR C,miss ; entirely left of the asteroid
LD A,E
ADD A,7 ; asteroid's last row
CP C
JR C,miss ; entirely above the body
LD A,C
ADD A,7 ; body's last row
CP E
JR C,miss ; entirely above the asteroid
LD A,246 ; hit: dark red backdrop
JR tset
miss: LD A,244 ; miss: the machine's blue
tset: OUT (9),A
LD A,135
OUT (9),A
RET
; --- rocket, bullet, fire, animate, input: as S23
place: LD A,0
OUT (9),A
LD A,123
OUT (9),A
LD A,(rockY)
OUT (8),A
LD A,(rockX)
OUT (8),A
LD A,0
OUT (8),A
LD A,15
OUT (8),A
LD A,(rockY)
ADD A,8
OUT (8),A
LD A,(rockX)
OUT (8),A
LD A,(flameP)
OUT (8),A
LD A,11
OUT (8),A
RET
bullet: LD A,(bullA)
OR A
JR Z,bpub
LD A,(bullY)
SUB 3
CP 8
JR C,bstop
LD (bullY),A
JR bpub
bstop: LD A,0
LD (bullA),A
LD A,224
LD (bullY),A
bpub: LD A,8
OUT (9),A
LD A,123
OUT (9),A
LD A,(bullY)
OUT (8),A
LD A,(bullX)
OUT (8),A
LD A,3
OUT (8),A
LD A,15
OUT (8),A
RET
fire: LD A,(bullA)
OR A
RET NZ
LD A,1
LD (bullA),A
LD A,(rockY)
LD (bullY),A
LD A,(rockX)
LD (bullX),A
RET
animate:
LD A,(flick)
DEC A
LD (flick),A
RET NZ
LD A,8
LD (flick),A
LD A,(flameP)
XOR 3
LD (flameP),A
RET
input: RST 8
DEFB 181 ; ZKSCAN
OR A
RET Z
CP 32
JR Z,fire
CP 87
JR Z,up
CP 83
JR Z,down
CP 65
JR Z,left
CP 68
JR Z,right
RET
up: LD A,(rockY)
OR A
RET Z
DEC A
LD (rockY),A
RET
down: LD A,(rockY)
CP 175
RET Z
INC A
LD (rockY),A
RET
left: LD A,(rockX)
OR A
RET Z
DEC A
LD (rockX),A
RET
right: LD A,(rockX)
CP 248
RET Z
INC A
LD (rockX),A
RET
pause: LD BC,3000
dloop: DEC BC
LD A,B
OR C
JR NZ,dloop
RET
rockX: DEFB 124
rockY: DEFB 88
flameP: DEFB 1
flick: DEFB 8
bullX: DEFB 0
bullY: DEFB 224
bullA: DEFB 0
astX: DEFB 156
astY: DEFB 88
rocket: ; body
DEFB 00011000b
DEFB 00111100b
DEFB 00111100b
DEFB 01111110b
DEFB 01111110b
DEFB 11111111b
DEFB 11011011b
DEFB 10011001b
; flame frame A
DEFB 01011010b
DEFB 01111110b
DEFB 01111110b
DEFB 00111100b
DEFB 00111100b
DEFB 00011000b
DEFB 00010000b
DEFB 00001000b
; flame frame B
DEFB 00111100b
DEFB 01111110b
DEFB 00111100b
DEFB 00111100b
DEFB 00011000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
; the bullet
DEFB 00011000b
DEFB 00011000b
DEFB 00011000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
; the asteroid
DEFB 00111100b
DEFB 01111110b
DEFB 11111111b
DEFB 11101111b
DEFB 11111101b
DEFB 11111111b
DEFB 01111110b
DEFB 00111100b
W, A, S, D to fly, space still fires.
What you should see
The rocket, the flame, the bullet - and a green asteroid to the right. Fly at it. The instant the body's pixels reach it the whole backdrop and border snap dark red; back off one pixel and the blue returns. Skim past above or below; drag the flame straight through it - nothing, and the nothing is correct: the flame is not in the box.

The bullet still flies straight through the rock. It has no box yet - that is the next thing this game grows, and by then you will know the test by heart.
rockX to 152 and take your hands off the keyboard. Then 153. One of
these is red. Get the other one red by pressing D exactly once.ADD A,7 to ADD A,8. Repeat the 152 experiment. Where
does the light come on now, and what would a player call that?ADD A,15 from rockY. Now the flame kills. Fly under the asteroid and
decide which version your game wants.miss: repaint - set the backdrop only on hit, like the old
machines' burglar alarms. What is the difference between a latch and an
instrument, and when would you want each?| Symptom | Cause |
|---|---|
| Hits register one pixel before contact | +8 with a carry-only compare: edge-touching boxes are not rejected. The last column is X+7. |
| Phantom hits or misses at the right or bottom edge | The +8 sums wrapped past 255. +7 within the movement clamps cannot. |
| The light comes on and never goes off | The verdict is only written on hit. Repaint both ways, every pass. |
| The flame hits things / the flame never hits things | Neither is a bug. The hitbox is whatever you defined - decide what the object IS, then write its box. |
| Fine for minutes, then chaos | Still S21's leak. Every CALLed routine, every path, RET. |
ADD A,7, compare, reject on carry.S25. Every key at once. Try to fly diagonally and the machine shrugs: the ROM's key call hands back one byte, one key. Underneath it, the keyboard is a grid that will happily report every key you are holding - read it yourself and the shrug goes away.