Tournament System Design Spec
Full replacement of the ladder/ranked season system with tournament-based competitive play, inspired by Richard Garfield’s philosophy that tournaments build better communities than ladders.
Design Decisions
| Decision | Choice |
|---|---|
| Strategic direction | Full ladder replacement |
| Organizer model | Progressive unlock (anyone can run small, earn bigger) |
| Bracket types (v1) | Single elimination, double elimination |
| Bracket types (later) | Swiss, Swiss + top cut, round robin, GSL groups |
| Timing modes | Both sync (live) and async (deadline windows), organizer chooses |
| Progression model | Trophy cabinet (permanent, accumulative) + tiered championship qualifications |
| Economy | Free + premium (most free, championship events cost coins) |
| Match types | Best-of-1, Best-of-3 |
| Transition | Clean break: archive leaderboard, launch tournaments |
| Communication | Existing notification system + Discord webhooks |
| Format validation | Enforce existing formats at deck registration |
| Engine architecture | Event-sourced (immutable event log, state derived from replay) |
1. Core Domain Model
Tournament
Top-level entity representing a single tournament event.
Tournament
id: String (MongoDB ObjectId)
name: String
slug: String (URL-friendly, unique)
description: String (markdown)
organizerId: String (userId)
status: TournamentStatus
bracketType: BracketType
matchType: MatchType
timingMode: TimingMode
formatId: String (links to existing Format)
maxPlayers: int
minPlayers: int (default 4)
scheduledStartTime: LocalDateTime
registrationOpenTime: LocalDateTime
registrationCloseTime: LocalDateTime
checkInDurationMinutes: int (sync only, default 15, 0 to disable)
asyncRoundDeadlineHours: int (async only, default 48)
entryFee: int (coins, 0 = free)
prizeConfig: PrizeConfig
tier: Integer (1-5 for platform championships, null for community)
visibility: Visibility
inviteCode: String (for PRIVATE visibility)
rules: String (organizer-defined text)
discordWebhookUrl: String (optional)
currentRound: int
createdAt: LocalDateTime
completedAt: LocalDateTime
Enums
TournamentStatus: DRAFT, REGISTRATION_OPEN, CHECK_IN, IN_PROGRESS, COMPLETED, CANCELLED
BracketType: SINGLE_ELIMINATION, DOUBLE_ELIMINATION (later: SWISS, SWISS_TOP_CUT)
MatchType: BO1, BO3
TimingMode: SYNC, ASYNC
Visibility: PUBLIC, PRIVATE
MatchStatus: PENDING, AWAITING_PLAYERS, IN_PROGRESS, COMPLETED, BYE, FORFEIT, NO_SHOW
RegistrationStatus: REGISTERED, CHECKED_IN, DROPPED, DISQUALIFIED
TournamentRegistration
TournamentRegistration
id: String
tournamentId: String
userId: String
deckEnumIds: List<String> (validated against format)
status: RegistrationStatus
seed: Integer (optional, for seeded brackets)
registeredAt: LocalDateTime
checkedInAt: LocalDateTime
Decks are locked at registration. Validated against the tournament’s format using existing format legality checks in CardDirectory and DeckService and FormatService.
TournamentRound
TournamentRound
id: String
tournamentId: String
roundNumber: int
status: RoundStatus (PENDING, ACTIVE, COMPLETED)
bracket: BracketSide (WINNERS, LOSERS for double elim; null for single)
deadline: LocalDateTime (async only)
startedAt: LocalDateTime
completedAt: LocalDateTime
TournamentMatch
A single match (or Bo3 series) between two players within a round.
TournamentMatch
id: String
tournamentId: String
roundId: String
bracketPosition: int (position within the bracket for rendering)
player1Id: String (userId)
player2Id: String (userId, null for BYE)
winnerId: String
status: MatchStatus
games: List<MatchGame>
reportedAt: LocalDateTime
reportedBy: String (userId of reporter)
MatchGame
gameNumber: int (1, 2, or 3 for Bo3)
gameLogId: String (links to existing GameLog)
winnerId: String
For Bo1: games has 1 entry. For Bo3: up to 3 entries. Winner is first to 2 wins.
TournamentEvent (Event Store)
Immutable event log. The source of truth for all tournament state changes.
TournamentEvent
id: String
tournamentId: String
sequenceNumber: long (monotonically increasing per tournament)
eventType: TournamentEventType
payload: Document (event-specific JSON data)
actorId: String (userId of who caused the event, or "SYSTEM")
reason: String (required for overrides and admin actions)
timestamp: LocalDateTime
Event types:
TournamentEventType:
TOURNAMENT_CREATED
TOURNAMENT_UPDATED
REGISTRATION_OPENED
REGISTRATION_CLOSED
PLAYER_REGISTERED
PLAYER_DROPPED
PLAYER_DISQUALIFIED
CHECK_IN_STARTED
PLAYER_CHECKED_IN
ROUND_GENERATED
ROUND_STARTED
MATCH_STARTED
MATCH_GAME_COMPLETED
MATCH_COMPLETED
MATCH_RESULT_OVERRIDDEN
ROUND_COMPLETED
TOURNAMENT_COMPLETED
TOURNAMENT_CANCELLED
PRIZES_DISTRIBUTED
Trophy
Permanent, accumulative record of tournament results. Never deleted.
Trophy
id: String
userId: String
tournamentId: String
tournamentName: String
placement: String ("1st", "2nd", "Top 4", "Top 8", "Participant")
placementRank: int (1, 2, 3-4, 5-8, etc. — for sorting/filtering)
tier: Integer (null for community events)
formatId: String
playerCount: int
bracketType: BracketType
date: LocalDateTime
TierTicket
Qualification tokens for platform championship events.
TierTicket
id: String
userId: String
tier: int (the tier this ticket grants access TO)
earnedFromTournamentId: String
usedInTournamentId: String (null if unused)
expiresAt: LocalDateTime (default 30 days)
createdAt: LocalDateTime
PrizeConfig
PrizeConfig
placements: List<PlacementPrize>
PlacementPrize
placement: String ("1st", "2nd", "Top 4")
coins: int
trophyAwarded: boolean (always true)
tierTicketAwarded: Integer (tier number, null if none)
2. Event-Sourced Architecture
Principles
- Events are immutable. Once written, never modified or deleted.
- Tournament entities are projections. Tournament, Round, Match, and Registration entities are read-models derived from the event stream. They are kept in sync for fast queries but the event log is the source of truth.
- Corrections are new events. An admin override is a
MATCH_RESULT_OVERRIDDENevent, not a modification of the originalMATCH_COMPLETEDevent. The override event’s payload includes the corrected data and a mandatoryreasonfield. - Replay for recovery. If a projection becomes corrupted, it can be rebuilt by replaying all events for that tournament.
Write Path
TournamentCommandService
- Validates the command (e.g., "can this player register?")
- Produces one or more TournamentEvent(s)
- Persists events to the event store (MongoDB, append-only)
- Updates the projection entities (Tournament, Round, Match, etc.)
- Publishes side effects (notifications, Discord webhooks)
Read Path
TournamentQueryService
- Reads directly from projection entities (Tournament, Round, Match, Registration)
- No event replay needed for normal queries
- Event log available for audit/history views
Concurrency
Like the existing CareerService pattern, a single-threaded ExecutorService per tournament serializes all mutating operations. This prevents race conditions without needing MongoDB transactions.
The executor is keyed by tournamentId — a ConcurrentHashMap<String, ExecutorService> with lazy creation. Executors for completed tournaments are cleaned up after a cooldown period.
3. Bracket Engines
Strategy pattern: each bracket type implements a common interface. Engines produce events; they don’t mutate state directly.
interface BracketEngine {
List<TournamentMatch> generateInitialBracket(List<TournamentRegistration> players);
List<TournamentMatch> generateNextRound(Tournament tournament, List<TournamentMatch> completedMatches);
List<Standing> getStandings(Tournament tournament, List<TournamentMatch> allMatches);
boolean isTournamentComplete(Tournament tournament, List<TournamentMatch> allMatches);
}
Single Elimination
- Players seeded randomly (or by seed if provided).
- If player count is not a power of 2, top seeds receive byes in round 1.
- Each round: losers eliminated, winners advance.
- Optional 3rd-place match (organizer-configurable).
- Final: winner takes 1st, loser takes 2nd.
Double Elimination
- Two brackets: Winners and Losers.
- First loss sends player to Losers bracket.
- Second loss eliminates.
- Winners bracket final loser drops to Losers bracket final.
- Grand final: Winners bracket champion vs Losers bracket champion.
- If Losers champion wins grand final, a reset match is played (organizer-configurable).
Future: Swiss
- N rounds (typically ceil(log2(players))).
- Each round pairs players with similar records, avoiding rematches.
- Tiebreakers: opponent match-win percentage, game-win percentage.
- Swiss + Top Cut: Swiss rounds followed by single-elimination top cut (top 4 or 8).
4. Tournament Lifecycle
State Machine
DRAFT → REGISTRATION_OPEN → CHECK_IN → IN_PROGRESS → COMPLETED
↓ ↑
(skip if no check-in) |
↓ |
IN_PROGRESS ──────────────────┘
↓
CANCELLED (from any state except COMPLETED)
Sync Tournament Flow
- Organizer creates tournament (DRAFT). Sets format, bracket type, match type, schedule, player limits, prizes.
- Registration opens (REGISTRATION_OPEN). Players register with a format-legal deck. Deck validated and locked.
- Check-in period (CHECK_IN, optional). Configurable window (default 15 min before start). Registered players must check in. No-shows are dropped. If fewer than
minPlayerscheck in, tournament is cancelled with refund of entry fees. - Tournament starts (IN_PROGRESS). Bracket engine generates round 1 pairings. All matches in the round are created simultaneously.
- Players play matches. Each pairing starts a game through the platform. Game result (via GameLog) auto-updates the TournamentMatch. For Bo3, games are played sequentially until a player reaches 2 wins.
- Round advances. When all matches in a round are complete (or deadline hit in async), engine generates next round pairings.
- Tournament completes (COMPLETED). Final standings calculated. Trophies created. Prizes distributed. Tier tickets issued.
Async Tournament Flow
Same as sync, except:
- No check-in period (players are expected to be available within the deadline).
- Each round has a deadline (configurable hours, default 48). Players coordinate to play within the window.
- If a match isn’t completed by the deadline, the organizer is prompted to assign a forfeit or extend.
- Notifications sent when rounds start (“You’ve been paired with X. Play your match within 48 hours.”).
Match-to-Game Integration
Games played through TCG ONE already produce GameLog entries. To link tournament matches to games:
- Add
tournamentMatchId: Stringfield toGameLog. - When a player clicks “Play Match” from the tournament page, the game server creates a game with
tournamentMatchIdset. PostGameConsumerServicechecks fortournamentMatchIdafter existing processing. If set, callsTournamentService.reportGameResult(tournamentMatchId, gameLogId, winnerId).- Tournament games still award career XP and currency through the existing scoring pipeline. Only the Elo/Glicko-2 updates are removed (part of the ladder sunset).
- Players are not blocked from playing casual games while they have an active tournament match. They are free to play normally — they just need to complete their tournament match before the round deadline (async) or in a timely manner (sync).
5. Organizer System & Progressive Unlock
Organizer Levels
| Level | Requirement | Max Players | Features |
|---|---|---|---|
| 0 (any user) | None | 8 | Create public/private tournaments, basic settings |
| 1 | Organized 3+ completed tournaments with 4+ unique players each | 16 | Custom rules text |
| 2 | Organized 5+ completed tournaments with 8+ unique players each | 32 | Discord webhook, async mode |
| 3 | Patreon tier 5+ OR Moderator role OR manually promoted | 64 | Featured placement in browse page |
| Admin | ADMIN/DEVELOPER role | Unlimited | All features, override any tournament, platform championship creation |
Computed on-the-fly from the user’s tournament history (count of completed tournaments they organized, with player count filters). No migration needed — just a query at tournament creation time, which is infrequent.
Organizer Controls
Available through the tournament management page:
- Start / advance round — Manual trigger (or auto-advance when all matches complete).
- Override match result — Set winner manually. Creates a
MATCH_RESULT_OVERRIDDENevent with mandatory reason. The overridden match’s GameLog links remain intact for audit. - Disqualify player — Removes player, opponent advances. Creates
PLAYER_DISQUALIFIEDevent with reason. - Drop player — Player voluntarily drops (or organizer drops for no-show). Creates
PLAYER_DROPPEDevent. - Extend deadline — For async rounds. Pushes the deadline forward.
- Cancel tournament — From any state except COMPLETED. Entry fees refunded. Creates
TOURNAMENT_CANCELLEDevent. - Event log view — Full audit trail of all events for the tournament.
Admin Overrides
Admins (ADMIN/DEVELOPER role) can:
- Override any tournament regardless of organizer.
- Manually assign organizer levels.
- Create platform championship (tiered) tournaments.
- Force-complete or force-cancel stuck tournaments.
- View event logs for any tournament.
6. Trophy & Tier Championship System
Trophies
Every tournament completion generates trophies for all participants:
| Placement | Single Elim (8p) | Single Elim (16p) | Double Elim (8p) |
|---|---|---|---|
| 1st | Winner | Winner | Grand Final Winner |
| 2nd | Runner-up | Runner-up | Grand Final Loser |
| Top 4 | Semifinalists | Semifinalists | Losers Semi losers |
| Top 8 | N/A | Quarterfinal losers | Losers Quarter losers |
| Participant | Round 1 losers | Round 1-2 losers | Early eliminations |
Trophies are permanent and displayed on the user’s profile in a trophy cabinet. They can be filtered by format, tier, placement, and date.
Trophy display on profile replaces the current Elo/ranked season display.
Tiered Championship System
Platform-run recurring tournaments that feed into higher tiers. Inspired by Garfield’s tiered concept.
Tier 1 — Open Qualifiers
- Open to all players. No ticket required.
- Run frequently (multiple per week, auto-scheduled by the platform).
- 8 players, single elimination, Bo1.
- Free entry.
- Prizes: Trophies for all. Winner receives a Tier 2 ticket. Top 2 get coins.
Tier 2 — Regional
- Requires a Tier 2 ticket to enter.
- Run weekly (auto-scheduled when enough tickets exist).
- 8 players, single elimination, Bo3.
- Free entry (ticket consumed on entry).
- Prizes: Trophies. Winner receives a Tier 3 ticket. Top 50% receive another Tier 2 ticket. Coins for top 4.
Tier 3 — Championship
- Requires a Tier 3 ticket.
- Run when enough tickets accumulate (or monthly, whichever comes first).
- 8-16 players, single elimination, Bo3.
- Free entry (ticket consumed).
- Prizes: Premium trophies (visually distinct). Winner is “Monthly Champion” for that format. Significant coin rewards.
Tickets expire after 30 days to keep the system flowing. Top 50% of any tier getting a same-tier ticket means players who “almost made it” can try again without starting from Tier 1.
The tier count is configurable. Start with 3 tiers. If the player base grows, add Tier 4 and Tier 5.
Champion Recognition
Monthly Champions (Tier 3 winners) get:
- A special trophy variant (visually distinct, “champion” tier).
- Their name displayed on the Championships page for that month/format.
- An announcement notification to all users.
7. Economy Integration
Entry Fees
- Community tournaments: Organizer sets fee (0 = free, or coins amount).
- Platform championships: Always free (ticket-gated instead).
- Collected fees go into the tournament’s prize pool.
- On cancellation, all entry fees are refunded.
- On player drop before the tournament starts (REGISTRATION_OPEN/CHECK_IN): entry fee refunded.
- On player drop after the tournament starts (IN_PROGRESS): no refund (prevents abuse).
- Tier tickets: consumed at registration. Refunded if tournament is cancelled or player drops before start.
Prize Distribution
Triggered by TOURNAMENT_COMPLETED event. Coins distributed via the existing Activity + Currency system:
- Create an
Activityof typeTOURNAMENT_PRIZEfor each prize recipient. - Award coins via
UserService.beginUserTransaction(). - Create a notification for each recipient.
Career Integration
- Tournament games award career XP and ink through the existing
ScoreService.calculateScore()pipeline (minus the Elo/Glicko-2 updates which are removed). - Quests that track game stats (wins, damage dealt, etc.) progress in tournament games.
- Achievements can reference tournament results (future: “Win 10 tournaments”, “Reach Tier 3”).
8. Discord Integration
Webhook Events
When a tournament has a discordWebhookUrl set, the following events trigger Discord embed messages:
| Event | Discord Message |
|---|---|
| Registration opens | Tournament announcement with format, schedule, join link |
| Round starts | Round N pairings list |
| Match completed | Result: Player A defeated Player B |
| Round completed | Round N standings/bracket update |
| Tournament completed | Final standings, champion announcement |
Payload Format
Standard Discord webhook embeds with:
- Tournament name as embed title
- Format, player count, bracket type in fields
- Link back to tournament page on TCG ONE
- Color-coded by event type (green for results, blue for info, gold for champion)
Rate Limiting
Batch match-completed messages if multiple finish within a short window (e.g., 30-second debounce in sync tournaments). Send as a single embed with all results.
9. Notifications
Using the existing NotificationService infrastructure:
New NotificationTypes
TOURNAMENT_REGISTRATION_OPEN
TOURNAMENT_CHECK_IN
TOURNAMENT_ROUND_STARTED
TOURNAMENT_MATCH_PAIRED
TOURNAMENT_MATCH_RESULT
TOURNAMENT_COMPLETED
TOURNAMENT_PRIZE_AWARDED
TOURNAMENT_CANCELLED
TIER_TICKET_EARNED
TIER_TICKET_EXPIRING (3 days before expiry)
Delivery
- Persistent notifications — Stored in MongoDB, visible in notification center.
- WebSocket push — Real-time delivery via existing STOMP infrastructure.
- Toast — For time-sensitive events (match paired, check-in starting).
10. API Endpoints
Public Tournament Endpoints (/api/v1/tournaments)
GET / Browse tournaments (filters: status, format, bracketType, tier)
GET /{id} Tournament detail (includes current round, bracket)
GET /{id}/bracket Full bracket data for visualization
GET /{id}/standings Current standings
GET /{id}/rounds All rounds with matches
GET /{id}/rounds/{roundNum} Specific round with matches
GET /{id}/events Event log (audit trail)
GET /upcoming Upcoming tournaments (shortcut)
Authenticated Tournament Endpoints
POST / Create tournament (requires auth)
PUT /{id} Update tournament settings (organizer only, DRAFT/REG_OPEN only)
POST /{id}/register Register for tournament (with deck)
POST /{id}/drop Drop from tournament (self)
POST /{id}/check-in Check in for tournament
DELETE /{id} Cancel tournament (organizer only)
Organizer Endpoints (/api/v1/tournaments/{id}/manage)
POST /open-registration Open registration
POST /close-registration Close registration
POST /start-check-in Start check-in period (sync)
POST /start Start tournament
POST /advance-round Force-advance to next round
POST /matches/{matchId}/override Override match result (with reason)
POST /players/{userId}/disqualify Disqualify player (with reason)
POST /players/{userId}/drop Drop player
POST /extend-deadline Extend async round deadline
POST /cancel Cancel tournament
Trophy & Championship Endpoints
GET /api/v1/trophies User's trophy cabinet (authenticated)
GET /api/v1/trophies/{userId} Any user's trophy cabinet (public)
GET /api/v1/championships Championship tiers overview + current champions
GET /api/v1/championships/tickets User's tier tickets (authenticated)
Internal Endpoints (/api/internal/tournaments)
POST /report-game-result Called by PostGameConsumerService when a tournament game completes
POST /auto-schedule Trigger auto-scheduling of tier-1 championships (cron-called)
11. Frontend Pages
Tournament Browse (/tournaments)
- Filterable list of tournaments: upcoming, in-progress, completed.
- Filters: format, bracket type, tier, free/premium, sync/async.
- Cards showing: name, format, bracket type, player count, schedule, tier badge.
- “Create Tournament” button (if user has organizer unlock).
Tournament Detail (/tournaments/[slug])
- Header: name, format, organizer, status, schedule.
- Tabs:
- Bracket — Visual bracket tree (single/double elim). Clickable matches show game details. Live updates via WebSocket.
- Players — Registered players list with status (registered, checked in, dropped, eliminated, active).
- Schedule — Round timeline with deadlines (async) or live progress (sync).
- Rules — Organizer-defined rules text.
- Event Log — Timeline of all tournament events (for transparency).
- Action buttons (contextual):
- “Register” (if open, user not registered)
- “Check In” (if check-in period active)
- “Play Match” (if user has an active match pairing — launches game against opponent)
- “Drop” (if registered/active)
Tournament Create (/tournaments/create)
Form with:
- Name, description
- Format selector (from existing formats)
- Bracket type, match type, timing mode
- Player limits (constrained by organizer level)
- Schedule (date/time picker with timezone)
- Entry fee (coins, if enabled by organizer level)
- Prize config
- Discord webhook URL
- Visibility (public/private) + invite code
- Rules text
Organizer Dashboard (/tournaments/[slug]/manage)
- Round controls: start, advance, extend deadline.
- Match list with override buttons.
- Player management: drop, disqualify.
- Event log with admin actions highlighted.
- “Cancel Tournament” with confirmation.
Trophy Cabinet (/profile/[username]/trophies)
- Grid of trophy cards, each showing: tournament name, placement, tier, format, date, player count.
- Filters: by format, tier, placement, date range.
- Visual distinction for champion-tier trophies.
- Replaces the current Elo/ranked season section on the profile page.
Championships (/championships)
- Replaces the current
/leaderboardpage. - Tier overview: Tier 1 / 2 / 3 with current status (next scheduled, tickets available).
- Current month’s champions per format.
- Historical champions archive.
- “My Tickets” section showing unused tier tickets with expiry dates.
12. Ladder Sunset
What Gets Removed
ScoreService: Remove Elo calculation (calculateScoreElo section), Glicko-2 system (initRatingSystem,saveRating,RankedSeason,ratingProcessRewards,compensateMay2026Rewards), score interval boundary notifications.EloRatingCalculator: Delete entirely.EloRatinginCareerData: Deprecate/remove.LeaderboardController: Replace with redirect to/championships. Archive current endpoint as read-only for historical data access.- Score leaderboard (
ScoreService.rebuildScoreBoards,getBoard,ScoreBoardItem): Remove. ScoreIntervalenum: Remove.- Front3
/leaderboardpage: Redirect to/championships.
What Stays
ScoreService.calculateScore()— The career XP/ink/currency scoring from games stays. Tournament games still award career progression.- Career system (levels, quests, achievements, currency, inventory, crafting) — Unchanged.
PostGameConsumerService— Extended with tournament match reporting hook.ScoreService.calculateModifier()— Remove leaderboard-based modifier bonus (replace with tournament-based modifier if desired, or simplify).- Win count (
getWinCount) — Keep as a career stat.
Migration
- Run a one-time script to create read-only snapshots of final ranked season standings (stored as a static JSON document).
- Add a banner to the old leaderboard page: “Leaderboard has been replaced by Championships. View Championships”
- Existing
RANKED_REWARDnotifications remain in users’ history — no need to delete.
13. Phasing Plan
Phase 1: Tournament Core (Foundation)
- Tournament, Registration, Round, Match entities + repositories
- Event store (TournamentEvent entity + repository)
- TournamentCommandService and TournamentQueryService
- SingleEliminationEngine
- Tournament lifecycle (create, register, start, advance, complete)
- Match-to-game integration (tournamentMatchId on GameLog, PostGameConsumerService hook)
- Basic API endpoints (CRUD, register, bracket, standings)
- Format validation at registration
- Basic tournament browse and detail pages in front3
Phase 2: Full UX + Bo3
- Tournament create page
- Bracket visualization component (interactive bracket tree)
- Bo3 match series tracking
- Check-in flow for sync tournaments
- Organizer dashboard (basic controls)
- Tournament notifications (all types)
Phase 3: Double Elimination + Organizer Tools
- DoubleEliminationEngine (winners/losers brackets)
- Double-elimination bracket visualization
- Organizer override controls (match override, DQ, drop)
- Event log view on tournament page
- Progressive organizer unlock system
Phase 4: Trophy System + Profile
- Trophy entity and service
- Trophy creation on tournament completion
- Trophy cabinet on user profile
- Prize distribution (coins via existing economy)
- Profile page updates (replace Elo with trophies)
Phase 5: Tiered Championships
- TierTicket entity and service
- Platform championship configuration
- Auto-scheduling of Tier 1 events
- Tier ticket qualification flow
- Championships page (replaces leaderboard)
- Champion recognition (announcements, profile badges)
Phase 6: Async Mode + Discord
- Async round deadline management
- Deadline extension controls
- Auto-forfeit on deadline miss (with organizer prompt)
- Discord webhook integration
- Webhook payload formatting
- Rate-limited batch messages
Phase 7: Ladder Sunset
- Archive final ranked season data
- Remove Elo/Glicko-2 code from ScoreService
- Remove EloRatingCalculator
- Remove score leaderboard
- Redirect /leaderboard to /championships
- Update ScoreService.calculateModifier() to remove leaderboard-based bonuses
- Clean up front3 leaderboard components
Phase 8: Swiss (Future)
- SwissEngine implementation
- Swiss pairing algorithm (avoid rematches, pair by record)
- Tiebreaker calculations
- Swiss + top cut variant
- Swiss-specific UI (standings table vs bracket tree)
14. Technical Considerations
MongoDB Collections
New collections:
tournament— Tournament projectionstournament_registration— Player registrationstournament_round— Round projectionstournament_match— Match projectionstournament_event— Event store (append-only, indexed by tournamentId + sequenceNumber)trophy— Permanent trophy recordstier_ticket— Championship qualification tickets
Indexes:
tournament: status + scheduledStartTime (browse queries), organizerId, slug (unique)tournament_registration: tournamentId + userId (unique), userId (my tournaments)tournament_match: tournamentId + roundId, player1Id/player2Id (find my matches)tournament_event: tournamentId + sequenceNumber (replay), tournamentId + eventType (filtered audit)trophy: userId + date (trophy cabinet), tournamentIdtier_ticket: userId + tier + usedInTournamentId (find unused tickets)
GameLog Modification
Add field:
GameLog
+ tournamentMatchId: String (nullable)
This links a game to a tournament match. PostGameConsumerService checks this field after existing processing and delegates to TournamentService.reportGameResult() if set.
WebSocket Extensions
Add tournament-specific STOMP destinations:
/topic/tournament/{id}— Live bracket updates, round advancements, match results.- Existing
/queue/notifications— Personal tournament notifications.
Scheduled Tasks
- Auto-schedule Tier 1 championships: Cron job checks if enough players are available, creates tier-1 tournaments at regular intervals.
- Tier ticket expiry: Daily cron marks expired tickets.
- Stuck tournament cleanup: Daily cron flags tournaments that have been IN_PROGRESS for >7 days (async) or >4 hours (sync) without activity. Notifies organizer.
- Async deadline check: Hourly cron checks for rounds past deadline, notifies organizer.