← All work

BoostBracket

Tournament platform that reads the live score out of the game itself

Role
Sole developer - API, realtime layer, tournament engine, broadcast tooling, LAN bridge
Period
2026 - present
Status
In development
Stack
TypeScript · Fastify · Next.js · Socket.IO · Prisma · BullMQ · Redis · LiveKit
0
database calls in the bracket rules
1 day
life of a live-score token
12
workspaces in the monorepo

Everything a community Rocket League tournament needs: registration, seeding, a verifiable draw, group standings, knockout brackets, dual-confirmed results with a dispute path, broadcast overlays, caster rooms - and a bridge that pulls the live score out of the running game.

01Live score, from a game that does not want to give it to you

Rocket League runs Easy Anti-Cheat online, where BakkesMod is neither permitted nor functional. Reading live game state therefore requires the organiser to host a local match that players join by direct IP - which is a support burden, so the platform documents the three steps publicly and stores the address and port encrypted per match, visible only to the assigned players and staff.

A bridge on the host machine reads the SOS plugin and posts the score to the API, which commits it and broadcasts to overlays and public pages. The chain is: game to BakkesMod to bridge to an ingest endpoint to an outbox event to every connected client.

The token that bridge uses is scoped to one match, expires within a day, is revocable, and is displayed exactly once. It carries a bearer credential and no cookie, so it is outside the session and CSRF surface entirely. Crucially it cannot submit, revise or finalise a result - and the referee who created it stays the audited actor, re-authorised on every call, so removing their role stops the feed.

Observed games are advisory. They pre-fill the referee’s result form so it is confirmed rather than retyped, and they are stored separately from the dual-confirmed records that actually decide the bracket. Automation that quietly becomes the source of truth is how a tournament ends in an argument.

What may inform a result, and what may decide oneThe live-score path runs from a locally hosted match through the BakkesMod SOS plugin and a host bridge to an ingest endpoint holding a match-scoped token, and is stored separately as an observed game. It pre-fills the referee’s form but cannot submit it. The authoritative path is two teams confirming and a referee finalising; only that result reaches the bracket, and only then does an outbox event inside the same transaction fan out to connected clients.ADVISORY — MAY INFORM, MAY NOT DECIDERocket Leaguelocal match, direct IPBakkesMod · SOSlive game stateHost bridgeposts the scoreIngest endpointone match · one daybearer, no cookieObserved gamestored apartAUTHORITATIVE — DECIDES THE BRACKETTeam A confirmsor opens a disputeTeam B confirmsor opens a disputeRefereere-authorised on every callConfirmed resultall the bracket ever readspre-fills the form · never submits itand only once it is confirmed does anything else hear about itWrite + outbox eventone database transaction, or neitherRedis · Socket.IOa typed notificationClientstreat it as a hint, refetchon a gap or a reconnect
Automation that quietly becomes the source of truth is how a tournament ends in an argument. The feed makes the referee’s job faster; two teams still decide the match, and the socket is never the thing that holds the answer.

02Realtime without lying about it

REST snapshots stay authoritative. A domain mutation writes an outbox event inside the same database transaction that made the change, and only then does Socket.IO broadcast a typed notification through Redis. Clients treat those as hints and refetch on reconnect or when they notice a version gap.

The alternative - trusting the socket to carry state - produces a bracket that disagrees with itself the first time somebody’s wifi drops mid-match. Here the worst case is a client that is briefly stale and knows it.

03What it will not do yet

The media path is direct WebRTC playback plus browser or OBS publishing through LiveKit. Recording and HLS exist as obvious next features and are deliberately not exposed, because their storage, consent, egress and delivery lifecycles are not implemented and verified end to end.

Similarly, the API refuses to start in production without a real session secret, non-development database and Redis URLs, private object storage, secure cookies and development auth switched off. Sign-in runs an authorization-code flow with state, PKCE and a single-use server-side record for every provider, and an unconfigured provider is refused with a 503 rather than half-working.

04Decisions worth explaining

The tournament engine knows nothing about the stack

Draw, standings and bracket rules live in a package with no dependency on Fastify, Prisma, React or LiveKit. Those rules are the part that has to be correct and the part people will argue about, so they are testable without a database, a browser or a media server anywhere near them.

An automated feed that cannot report a result

The live-score bridge can only inform. Confirmation stays with two teams and a referee, because a score feed that could finalise matches turns every plugin glitch into a disputed result, and the whole point of dual confirmation is that a machine is not the one being trusted.

Original branding, on purpose

The platform is unaffiliated with Psyonix and Epic Games and ships none of their assets - colours, logos and naming are original and configurable. A community tool that borrows a publisher’s trade dress is a takedown waiting for an audience.

Questions about this project?

I am happy to walk through the architecture, the parts that did not work, or the code itself.