TinyGames · how it works

Swish

Flick basketball, seen side on. Draw back from anywhere on the court, let go, and the ball leaves on the line running from where your finger ended back to where it started - the same draw-back gesture Divot, Cushion, Carom and Snooker use. After that it is gravity, two rim posts and a backboard.

Draft. It is built, compiled and tested like every other game, and it is not on the hub: "draft": true in build/games.json keeps it off the cards, out of the sitemap and without a readme page. Take the flag off when it is worth advertising.

The drill

Sixty seconds. Six shooting marks in a fixed ladder that steps steadily backwards; a basket moves you on to the next one and a miss leaves you where you are, so the only thing a miss costs is time. The ladder comes round again and what climbs with every trip is the reward rather than the difficulty - a drill whose difficulty rises without limit always ends the same way, with the player failing at whatever point their skill ran out. This one ends when the clock does.

The fourth mark is where the hoop starts travelling. It hangs from a rail and slides, and its position is a pure function of the clock - it never stops, not while the ball is in the air and not while you are aiming, so leading it is a thing you can get good at rather than a thing that happens to you.

A basket pays 100, plus 40 for every step up the ladder, plus 60 for going through without touching rim or board, plus 25 for each basket already made in a row up to six. Nothing anywhere takes points away, so the score can only climb.

A shot released before the buzzer counts. The run cannot end while a ball is in the air, and a basket is credited the moment the ball drops through the mouth rather than when it lands - which is what makes the last second of the minute worth shooting in.

How it is put together

court.ts is the rules and touches nothing - no canvas, no engine, no storage, and no random numbers at all. swish.ts is everything that draws or listens. The split is what makes the hard half testable: node test-court.js runs against the shipped, minified JavaScript rather than the source, which is how a minification fault gets caught.

Every screen except the run itself belongs to the shared shell, so this game has one scene of its own, play. The menu, the pause card, the game-over screen and the high-score table come from src/shared/scenes.ts. The playfield is placed by src/shared/arena.ts at a ratio of 1.6, playable either way round - sideways it is nearly three times the area, and upright it is the same game at a smaller size, which is a better thing to offer somebody than a card asking them to turn their phone.

Two things the code is careful about

Y points up in court.ts and down everywhere else. A canvas measures downwards; a court under gravity does not. The flip lives in two functions in swish.ts and nowhere else.

The arc is exact. The flight is integrated with the velocity-Verlet update, which for a constant acceleration is not an approximation - stepping it n times lands on the same point as the closed-form parabola evaluated at n steps, to the last bit. That is why the same flick scores at 30fps and at 240Hz, and why the ball's energy can be asserted never to rise, which is in turn the argument that every shot ends.

Tests

node test-court.js - 139 assertions and 26 mutants, plus a no-op control that must pass. The independent reference is a "paper court" that works the same flight out from the algebra of a parabola sampled four thousand times a second, with no integrator, no velocity and no slicing in it; where a flight touches nothing the two must agree exactly on whether it went in, how it finished and when. A wide sweep of 36,288 shots covers every mark, angle and power and checks that all of them come down.