Under the hood
Boring where it counts.
A single Node process per network, an SQLite file on disk, no queue, no cache tier and no cluster. The interesting part of this system is the ranking, so everything around the ranking is arranged to be as uninteresting as possible.
Configuration
Five layers, each overriding the last
- code defaultsGeneric. Names no niche.
- instance fileThe niche. The only file you edit to add one.
- machine-local fileOptional, never committed.
- environmentSecrets and per-host values.
- runtime settingsWhat an admin may change without a deploy.
Anything omitted falls through, so an instance file states only what is genuinely different about the niche. Categories are the one deliberate exception: the file seeds them once and the database owns them afterwards, so an admin renaming a category is not undone by the next deploy.
Every read goes through one accessor, which means a hardcoded instance name anywhere in the source is a visible mistake rather than something that quietly ships.
Storage
Decisions worth stating
Scores are rebuildable
The score table is materialised and never authoritative. Everything in it can be replayed from the raw events, and that escape hatch is what makes an incremental recalculation safe to depend on.
Rejections are kept
An event that did not count is still written down, with the reason on the row. That is what the second half of the receipt is reading. An event that vanishes is an event nobody can audit.
Addresses are stored twice
The raw address for human abuse review, on a short retention. A keyed hash for the dedupe and cooldown logic, on a longer one. The hash is what the ranking touches; the raw address is what a moderator touches.
Nothing is hard deleted
Removal sets a timestamp, and unique indexes ignore removed rows, so a deleted name frees up while live rows stay unique. The audit log is the exception: it only ever grows.
Anti-abuse
Layers, not a silver bullet
Two of the six ranking stages are anti-abuse measures in their own right. These sit underneath them, and none of them is claimed to be sufficient on its own.
- Per-identity dedupe on both directions of traffic
- Diminishing returns per network, with no edge to sit underneath
- Vote cooldowns, set per niche
- Token bucket rate limiting with durable strikes, so a restart is not a reset
- Bot filtering, IP blocks, and a moderator queue that a flag lands in
- Request forgery protection, and outbound request guards on anything a member can point at us
Vote rewards
Two ways to hear about a vote
Polling, recommended
The game server asks for unclaimed votes on a timer. It needs no open port, no certificate and no fixed address, and it recovers by itself after downtime.
Signed webhooks
A signed request within seconds of the vote, retried with backoff. It needs the server to be publicly reachable. A webhook that fails for good never loses the vote: it stays claimable by polling for the rest of the window.
Claim tokens are single use and idempotent, so a reward cannot be granted twice by a retry, a race or a replayed request.
Operations
One process, one file, one proxy
Each network is a process with its own database file and its own environment. Adding one is a config file, an environment file, a migration and a line of proxy config. There is no database server to provision, and a backup is a file copy, taken continuously offsite and snapshotted nightly.
The recalculation, the vote callbacks and the retention purge all run inside the web process. Nothing else needs scheduling, and there is nothing else to notice has stopped.
Scope
Not in this version
Listed because a roadmap that only contains good news is not a roadmap.
- Themes and skinning beyond a colour and a mark
- Translations
- Forums and private messaging
- Paid placement, which is not planned at all rather than not planned yet
- A general third-party data API