Skip to content
Back to portfolio

Top Shot Reloaded — 1v1 Speedball Duel

A phone-first paintball duel built solo from prototype to beta: a deterministic 120 Hz headless simulation, a WebGL renderer, a print-inspired design system, lockstep multiplayer over a Cloudflare relay, and native shells for iOS, Android, and desktop.

Title screen, a live duel, and the HIT! verdict — unretouched captures from the current beta build.

Overview

Top Shot Reloaded is a one-on-one top-down speedball duel: two players, two mirrored inflatable bunkers, 45 seconds on the clock, and the question of when to step out and shoot. I have been building it solo since June 2026 as designer, engineer, and the person who writes the release notes — TypeScript and Vite on the web, wrapped with Capacitor for iOS and Android and Tauri 2 for macOS and Windows.

Title screen, practice setup, and the clinic — the calm "paper" layer of the game

The core idea: position is the trigger

No ammo count, no fire rate to manage. Every bunker has staged spots — left lane, right lane, forward over the top, back in deep cover — and each spot has two states: tucked and silent, or stepped out with the marker running itself. Holding FIRE steps you out; releasing ducks you back. Repositioning costs a beat and stepping out is a commitment, so every decision is a spend rather than a twitch. Paint behaves like paint: spread cones, drag, gravity drop, bounces off the bags, and trades — a ball already in the air can still take out the player who just shot you.

A duel from the on-deck card to live play to the verdict wipe

What I built

  • A headless, deterministic simulation on a fixed 1/120 s step with a seeded, split random stream, a world hash, and input-log replay — the sim never touches the DOM, so the same match can be re-run and verified anywhere
  • A WebGL render pipeline (three.js) over that sim, with a canvas2d fallback: characters baked from a rigged low-poly mesh, real bunker meshes from the NXL/PSP kit, paint splats, verdict wipes, and a strict one-way core → sim → render dependency firewall
  • An AI rival that decides every 0.3–1.2 s to peek, suppress, tuck, or hold, with a humanlike aim delay — five named rivals with distinct habits
  • Hold-and-steer touch controls: a position pad, an aim pad, and a FIRE button, magnetised so a thumb stays in its zone until it clearly commits to another
  • Career stats, run records, and a replay-verifiable leaderboard spine, persisted through a platform layer so saves survive on web, iOS, Android, and desktop
  • Lockstep netcode: a WebSocket relay on a Cloudflare Worker with Durable Objects, a wire protocol, a latency ladder, and a mirrored guest view, so two real engines play one relayed match with identical worlds
  • Around 1,760 tests across 97 files, including freeze guards that fail CI if a frozen visual surface changes and vocabulary tests that pin every player-facing string

A design system with two voices

The visual identity, "Match Data", is print-inspired and value-driven: a warm putty-and-ink neutral ladder, one amber SIGNAL accent, and a per-match neon paint that is the only loud colour on screen. It speaks in two registers. STATEMENT is in-match: Anton slams, tilted verdict wipes, ref calls like GAME ON!, HIT!, and TRADE!. PAPER is everywhere else: calm print furniture, tracked caps, hard offset shadows, zero exclamation marks. Identity comes from structure — ink frames, radius zero, down-right shadows — not from colour.

The system ships as a tracked bundle in the repo: tokens, component specs, motion tables, and a ratified vocabulary that resolved three generations of contradictory copy into one table, each row pinned by a test.

Trade verdict, the Gear Bag loadout, and the career stat card

Proving determinism before building on it

Multiplayer, replays, and the leaderboard all rest on one claim: two simulations fed the same inputs produce the same world, bit for bit. Rather than argue it, I measured it — 200 seeds × 3,000 ticks across two independent worlds joined only by a modelled link, at clean, 60 ms, and 200 ms round-trip latencies and at frame rates from 60 to 240 fps, with zero divergence. Then two real engines played a complete race to five through the live relay with identical worlds and phases throughout. An adversarial audit of the mirrored guest view raised 26 findings and confirmed 18, including three defects that hash comparison could never catch, because both clients were identically wrong.

Bunkers that block like bunkers

The field draws from the real inflatable shapes players snap-shoot behind — doritos, cans, cakes, temples, the X, the Rollie. The latest rules revision replaced every bunker's rectangular hitbox with its actual outline. Paint threaded past a bag's corner used to die on empty air; now it flies through, and ricochets come off the true curve. Each rules change bumps a versioned config that carries the measurements behind it, and it invalidates old replays on purpose, because the collision geometry is part of the rules.

Status

In private beta. Practice against the AI rival works offline, private matches run over the relay, and matchmaking is not built yet. The iOS shell builds through Capacitor and the desktop shells through Tauri 2. Next up: the on-device performance pass, the server side of the leaderboard, and store submission.

The game is in active development. If you'd like to playtest the beta on iOS, Android, or desktop, get in touch and I'll send you a build.