Competition as infrastructure
Your players are nobody
on a world leaderboard.
Here they are champion of their neighbourhood. One shared layer for identity, leaderboards, qualifications, challenges, badges and geographic titles — from district to world. Free to self-host, billed by usage, never per player.
- 1kirstendistrict champion184,200
- 2tobi_hh171,850
- 3anna160,010
- 4monokel142,300
- 5you138,940
Why this exists
A leaderboard is worthless.
A title is not.
The difference between the two is not technology, it is credibility — and credibility has four parts. None of them is solved by the usual backends, all four are too expensive for a small team, and each one gets better the more apps take part.
Verification
The number was demonstrably played, not invented.
Scarcity
Seasons end. A region needs enough contenders and a unique winner.
Witness
The final was scheduled, recorded, public.
Portability
The title stays with the player, even if the game dies.
Not only games
One field decides what you are building.
A high-score game, a running app and a habit tracker differ by exactly one property on a discipline. Everything else — identity, regions, qualification, titles, challenges, badges — stays the same.
| aggregation | a player's value is | typical case | looks like |
|---|---|---|---|
best | the best single entry | high score, best lap | 184,200 pts |
sum | the sum over the season | kilometres, minutes, pages | 107 km |
count | the number of entries | finds, check-ins, sessions | 38 finds |
streak | the longest run of days | habits, practice, study | 61 days |
Integration
Three lines, then you are on the board.
Only the public key ever touches a client. Anything carrying authority — creating disciplines, reporting duels, granting collectibles — needs the secret key, and therefore a server.
import { connect } from '@moinsen/challenges'
const api = await connect({ base, appKey: 'pk_live_...' })
await api.submit('score-attack', 12500)
const board = await api.leaderboard('score-attack', { region: api.regionId })
board.entries[0].handle // 'kirsten'
board.title_eligible // true — this district can crown someonevar api := ChallengesAPI.new()
add_child(api)
await api.connect_anonymous("pk_live_...")
await api.submit("score-attack", score)
var board := await api.leaderboard("score-attack", api.region_id)
for row in board.entries:
print("%d. %s — %d" % [row.rank, row.handle, row.value])final api = await Challenges.connect(
base: base,
appKey: 'pk_live_...',
);
await api.submit('daily-practice', 1);
final me = await api.status('daily-practice');
me.streakDays; // 61
me.rank.region; // (rank: 2, of: 19)The JavaScript client ships today. Godot and Dart packages are phase 2 — see the roadmap, which says plainly what exists and what does not.
The ladder
Eleven people make a district mean something.
A global ranking needs millions of players to be interesting. A district ranking needs eleven. That inverts the usual cold-start problem: what is scarce is density in one place, not reach.
So a region only awards a title once it has enough contenders and a unique winner — and a closed region opens itself when enough people are waiting for it. Scarcity is code here, not marketing.
Home districts are locked for the season, nothing finer than a district is ever stored, and no discipline awards a title above its own trust tier.
Dimmed levels are not unlocked yet. A level that does not exist is more honest than a world champion with four opponents.
What you stop rebuilding
Sixteen things, once, for every app you make.
This list is the actual pitch. Every item is something a small team writes again in each new project, gets slightly wrong, and then maintains forever.
- Anonymous accounts and sessions
- A leaderboard that survives a restart
- Per-region and per-friend standings
- Streak counting across time zones
- A challenge between two players
- Elo or Glicko, done wrong the first time
- Badges, and the rules behind them
- Collections and set completion
- Daily seeds everyone shares
- Handle changes without breaking history
- Blocking, reporting, moderating
- Invite codes and closed betas
- Waitlists for a region or a launch
- Idempotency for a flaky mobile network
- Cheat plausibility limits
- GDPR export and deletion
Proof, not adjectives
Tested where it matters.
182 tests run inside the real Workers runtime against a real database, plus a smoke test over HTTP against a running instance. Coverage thresholds are enforced, so a regression fails the build.
The tests earned their keep: they found a race in idempotent submissions, a migration that opened with two dozenDROP TABLE statements, and a duplicate title event on a repeated season close.
statements covered
branches covered
tests, real runtime
public domain licence
Price and licence
The software is yours. The running of it is what costs.
The code is CC0 — take it, change it, sell it, host it yourself. The hosted service is billed by what it actually consumes: entries beyond the free allowance, replay verification by CPU, titles above city level.
Never per player. A per-user price punishes exactly the growth that makes the service valuable, and pushes developers into avoiding accounts.
Copy the code and you get an empty database. What you cannot copy is the eleven people already in Eimsbüttel.