Dev Patel

Live on web · Jul 2026 → Present

Rune Duel

Draw the rune.
Cast the spell.

A real-time 3D wizard duel played by drawing runes with a finger or a mouse — in any browser, with the server re-drawing every stroke so a client cannot lie about it.

A practice duel in the arena — health bars, the five-spell hand, and the rune you draw to cast.

Highlights

30spells, five to a deck
Serverre-scores every stroke
1codebase, web + iOS

The problem.

Gesture spellcasting is a VR idea. I wanted the feel of it — draw a shape, a spell fires — on the surfaces people actually carry: a phone screen and a trackpad. No headset, no install, no download between wanting to play and playing.

Taking it online turns a rendering problem into a trust problem. The moment two strangers duel over the internet, the number that decides how much damage a spell does is being computed on a machine I do not control.

What I built.

The game is React Three Fiber over Three.js: a first-person arena, an opponent across from you, and a $1 gesture recognizer that scores a single-stroke rune against a template. Power is accuracy weighted against speed, so a clean slow rune and a fast sloppy one are both punished — that weighting is the whole game, and it is why drawing has to feel like skill rather than a menu with extra steps.

The recognizer and the combat resolution live in a shared workspace that both sides import. Offline practice runs them in the browser; an online duel runs them again on a Node and Socket.io server that re-recognizes the raw stroke points itself and ignores whatever the client claimed the draw was worth. Same code on both sides of the socket, so the two can never disagree about what a drawing meant.

Accounts, ranked Elo and the leaderboard sit in Supabase — Postgres with row-level security and a function that settles both players' ratings in one transaction — while guests keep an unranked record on device and never see a sign-in wall. The same web build is wrapped with Capacitor for iOS, so the phone app and the browser are one codebase drawing on one pool of opponents.

Screens.

Before the duel: five spells chosen from thirty, quick match or a room code, and a graphics tier detected per device.
Before the duel: five spells chosen from thirty, quick match or a room code, and a graphics tier detected per device.

What I considered.

Rejected

Rebuild the game natively in Unity for iOS

Better frame pacing and store-native tooling, but it forks the product into two codebases and splits the player pool — fatal for a 1v1 game nobody has heard of yet.

Rejected

Stay WebXR-first and require a headset

It is the obvious reading of a spellcasting game, and it shrinks the audience to people who own a headset and are willing to put it on before they know whether the game is any good.

Chosen

Flat-first web build, wrapped with Capacitor for the App Store

One build, one match server, one matchmaking pool; the phone gets an installable app and everyone else gets the same game at a URL. The cost is the WebGL ceiling on graphics, which is the cheapest thing in the list to give up.

Results.

  • Playable in a browser today: practice against three AI tiers offline, or duel someone through quick match or a six-letter room code.
  • Online 1v1 verified end to end over the internet, with the server scoring strokes rather than trusting the client's claim.
  • Ranked accounts, Elo and a leaderboard on Supabase, with guests kept playable and unranked on device.
  • Packaged for iOS with Capacitor from the same build and submitted to the App Store.

Architecture

How it fits together.

Browser / iOSReact Three Fiber· Capacitor shellStroke capturepoints + timingShared module$1 recognizer· combat rulesMatch serverNode · Socket.iore-recognizes server-sideSupabaseaccounts · Elo· leaderboard
  1. 01

    Browser / iOSReact Three Fiber · · Capacitor shell

  2. 02

    Stroke capturepoints + timing

  3. 03

    Shared module$1 recognizer · · combat rules

  4. 04

    Match serverNode · Socket.io · re-recognizes server-side

  5. 05

    Supabaseaccounts · Elo · · leaderboard

The recognizer is the same module on both sides of the socket. The server is the copy that counts.

In hindsight

Rooms live in one server process's memory. That kept the matchmaking code small and honest, and it makes the match server a single point of failure — when its hosting lapsed, online play went dark while practice and pass-and-play carried on regardless. Moving rooms behind Redis is the difference between a game that survives a restart and one that does not.

Tech specs.

Type
Project
Where
Personal
When
Jul 2026 → Present
Duration
4 mo
Status
Live on web
Stack
JavaScriptReactThree.jsReact Three FiberNode.jsSocket.ioSupabasePostgreSQLCapacitorViteFly.ioVercel