Beginner / Collision
Read the Collector Collision Band One Condition at a Time

Gem Collector does not use a physics library. Its collision rule asks whether an object has entered a vertical band near the paddle and is close enough horizontally. This approximation is short enough to read beside the movement code, which makes it a useful first collision example.
What you'll build
We will trace one gem and one hazard through the current collision rule, explain its dimensions, and test the boundaries. The goal is to understand what the shipped engine actually counts as a catch before replacing it with a more precise shape test.
Prerequisites
Know logical Canvas coordinates and the collector state fields. The playfield height is 540, and the paddle is drawn near its bottom. Objects store center positions and a bad flag. The collector stores its horizontal center in state.x.
Read the vertical band literally
The test accepts an object's center when y is greater than HEIGHT - 78 and less than HEIGHT - 25. With the current height, those bounds are 462 and 515. The inequalities are strict, so an object exactly on either boundary is not accepted by that check.
function update(dt, direction = 0) {
if (state.finished) return;
state.time += dt;
state.spawn += dt;
state.x = clamp(state.x + direction * 500 * dt, 45, WIDTH - 45);
if (state.spawn >= 0.52 - difficulty * 0.035) {
state.spawn = 0;
state.objects.push({ x: 40 + random() * (WIDTH - 80), y: -25,
bad: random() < 0.23 + difficulty * 0.015 });
}
for (const object of state.objects) {
object.y += (150 + difficulty * 25 + state.time * 1.2) * dt;
if (object.y > HEIGHT - 78 && object.y < HEIGHT - 25 &&
Math.abs(object.x - state.x) < 52) {
object.y = HEIGHT + 60; // Consume once, then remove below.
if (object.bad) state.lives--;
else state.score++;
}
}
state.objects = state.objects.filter(object => object.y < HEIGHT + 30);
state.won = state.score >= goal;
state.finished = state.won || state.lives <= 0 || state.time >= 75;
}
The horizontal condition accepts a center distance smaller than 52 logical units. The paddle is 90 units wide, so the rule includes some allowance beyond its 45-unit half-width. It does not calculate a full circle-versus-rectangle intersection, and the allowance is not derived dynamically from each object's radius.
These choices define the game's feel. A small forgiving band can make a casual collector easier to control. The important point is to describe the approximation accurately rather than calling it exact shape collision.
Apply movement before checking contact
Each object moves downward by speed times delta, and then the engine evaluates the updated position. A fast enough object could cross the whole band between two updates without ever being sampled inside it. The scheduler's delta cap reduces that risk for current speeds but does not solve tunneling for arbitrary future settings.
This ordering also means a test should place an object where it will be after movement, not assume its starting y is the collision position. For a focused rule test, use a zero delta or calculate the expected displacement explicitly.
Consume the object before the next frame
On contact, the engine moves the object below the playfield. A gem increments score; a hazard decrements lives. The later filter removes the consumed object from the array, so it cannot score again on the next update.
Without that consumption step, an object could remain in the band for several frames and award several points. A visual overlap is not necessarily a new event every time it is sampled. The model needs a rule saying when a collision has already been handled.
Missed objects are also removed after leaving the visible region, but missing a gem does not currently cost a life. Do not infer penalties from object cleanup: removal and scoring are separate decisions.
Test the geometry independently of randomness
Construct a collector instance and insert an object directly into its state for a rule test. Put a gem at x=480, y=470 with the paddle centered at 480. Updating with a tiny delta should add one point and remove the object.
Then test a hazard at the same location, an object more than 52 units away horizontally, and objects just outside the vertical band. Check that rejected objects do not alter score or lives. A repeated update after a successful catch should not award a second point.
These tests need no browser renderer. The factory's state is available to the test, and drawing is a separate method. Browser tests remain useful for checking that the picture and controls align with this logical rule.
Consider simultaneous contacts
The loop visits every object in the array during one update. Several catches can therefore occur in the same frame. After processing them, the engine evaluates victory before other completion causes through its won flag. A target gem and a final hazard arriving together have a defined result because of that ordering.
If you want a different priority, change the outcome policy explicitly and add a simultaneous-contact test. Rearranging code during cleanup can otherwise change gameplay without any visible change to the collision formula.
Next steps
Use the collector source to compare the band with the actual paddle drawing. A later extension can use circle–rectangle geometry or a swept test, but first preserve the existing consumption, scoring, and completion rules so you can identify exactly which behavior your new geometry changes.