Case study
Pickle Royale
A 2v2 pickleball ladder I designed and built for my own group.
- Role
- Design and build, solo
- Type
- Personal project
- Live
- pickleball.dhrumilkherde.com/demo
Part one
Building the ladder
The problem, the look, and the shape of the rating system.
The problem
The problem
Me and my friends play 2v2 every week, random teams every time.
Nobody could remember who was actually any good. Rankings lived in people's heads and got argued about in the car park.
So I built the scoreboard.

My ownership
Solo did not mean unstructured
I framed the problem, designed the match flow and rating behaviour, chose the tradeoffs, built the frontend and backend, and handled deployment.
Claude Code accelerated implementation. The product decisions, references, constraints, and quality bar stayed mine.
For visual direction, I annotated specific references in Shottr and handed over the source folder, so implementation followed evidence instead of a vague style prompt.
The form follows the moment
The form follows the moment
Log Match asks: who played, what was the score, who carried.
That is the order you decide it in, standing on court with one phone between four people.
Not the order a database would prefer.
The form follows the moment
Three taps, one phone, no database thinking

Nobody picked yet

Step one: who played

Step two: what was the score
The system I was offered
Why a flat rating could not work
The AI proposed the simple version. Everyone starts at 100. Win, go to 110. Lose, go to 90. Every time.
Beat the best or worst player in the group: the same +10. It cannot tell those matches apart, so it cannot tell you anything.
Rate everyone, predict every match, and move people by how wrong the prediction was.
Beat someone you were meant to beat and barely anything happens. Lose to someone you were meant to beat and it costs you.
Hard to climb, easy to fall down. That is what makes the top of it worth anything.

Part two
What that looks like in play
Three real matches, logged live, with the ratings that came out of them.
What that looks like in play
The matchup
Priya and Kabir, rated 1490 and 1392. Ishaan and Sam, 1056 and 979.
The model gave the underdogs an 8.5% chance.
They won 11-9. Thirty points moved each way.
What that looks like in play
David beats Goliath, on a real phone

Margin, dampened
Margin, dampened
An 11-0 counts for more than an 11-9, on a log curve, so a blowout matters without being everything.
And a strong team crushing a weak one earns less than the scoreline suggests, deliberately. Otherwise whoever is top farms the bottom of the ladder and never defends anything.
Win the match you were expected to win and the whole table moves 48 points. Lose it and the same table moves 120.
Margin, dampened
Pickled, in context

Who carried
Who carried
In doubles a win is not split evenly, and everyone at the court knows it.
A slider from centre, 10/90 at the extremes. Drag it toward whoever did the work.
In a loss it inverts: whoever carried loses less. The split only moves points inside a team, never changes the team total.
Who carried
The same slider, at rest and dragged


Ratings are never stored, only replayed
Ratings are never stored, only replayed
Every rating is recomputed from the whole match log, in order, per group.
So deleting a mistyped match is safe, and one group's results cannot leak into another's.

Part three
A player is not just a score
Claiming a player, one debugging story, and what the ladder remembers.
A player is not a user
Claiming a player, end to end
A player is only a name on the ladder, so one admin can run the night from one phone. A user is a Google account: claim a player for your own history, then an admin approves it. If you were invited by that email it just happens.



A player is not a user
Invites, without a paid email service
Invites are a six character code and the share sheet your phone already has. I built email invites through Resend, then took them out of the flow. Free and open source only.

The redirect that was never there
The redirect that was never there
Google sign-in worked locally, broke in production, and curl kept telling me it was fine.
Cloudflare was serving a static asset for /auth/login before my Worker ever ran, so the redirect never happened. curl could not see it, because curl does not send Sec-Fetch-Mode: navigate, and without that header the asset layer answers differently.
Sending the browser's real headers turned a 302 into a 200 and exposed the fix: run_worker_first for /api/*, /auth/* and /demo.

What the ladder knows about you
Rating over time, form, and who you play well with
Once every match is stored, the ranking is the least interesting thing in it: rating over time, form, who you keep losing to, and who you play better with than without.



What the ladder knows about you
The ladder is not neutral, and that is the point


Then I had to let people in
Then I had to let people in
The login wall blocked everyone I wanted to show it to. So there is a demo: every visit mints a throwaway group seeded with a real season, signs you in, hands you a player. Log a match, watch the ladder move.
A daily cron deletes them. Real groups are never touched.

Outcome
A shared record replaced memory and debate
The group can log a match from one phone, see why every rating moved, and revisit the season as more than a leaderboard.
The honest outcome is qualitative: a working, deployed product for a known group, not a claim of market scale or growth.

Reflection
What I would validate next
I designed around a group I knew, which made the court context unusually clear but limited how much I learned from unfamiliar players.
Next I would observe new groups logging their first match, measure completion and correction rates, and test whether the contribution slider resolves debate or creates more of it.
Stack
Built with
- Frontend
- React, Vite, TypeScript
- Platform
- One Cloudflare Worker serving app and API, D1 for data, one deploy
- Auth
- Google sign-in
- Ratings
- shared/elo.ts: base 1200, K of 40 for the first ten games then 24, contribution clamped 10/90, margin of victory on a FiveThirtyEight-style log curve
- Made with
- Claude Code, with design references annotated in Shottr