Tournaments Spec - ready for review

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_OVERRIDDEN event, not a modification of the original MATCH_COMPLETED event. The override event’s payload includes the corrected data and a mandatory reason field.
  • 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

  1. Organizer creates tournament (DRAFT). Sets format, bracket type, match type, schedule, player limits, prizes.
  2. Registration opens (REGISTRATION_OPEN). Players register with a format-legal deck. Deck validated and locked.
  3. 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 minPlayers check in, tournament is cancelled with refund of entry fees.
  4. Tournament starts (IN_PROGRESS). Bracket engine generates round 1 pairings. All matches in the round are created simultaneously.
  5. 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.
  6. Round advances. When all matches in a round are complete (or deadline hit in async), engine generates next round pairings.
  7. 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:

  1. Add tournamentMatchId: String field to GameLog.
  2. When a player clicks “Play Match” from the tournament page, the game server creates a game with tournamentMatchId set.
  3. PostGameConsumerService checks for tournamentMatchId after existing processing. If set, calls TournamentService.reportGameResult(tournamentMatchId, gameLogId, winnerId).
  4. 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).
  5. 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_OVERRIDDEN event with mandatory reason. The overridden match’s GameLog links remain intact for audit.
  • Disqualify player — Removes player, opponent advances. Creates PLAYER_DISQUALIFIED event with reason.
  • Drop player — Player voluntarily drops (or organizer drops for no-show). Creates PLAYER_DROPPED event.
  • Extend deadline — For async rounds. Pushes the deadline forward.
  • Cancel tournament — From any state except COMPLETED. Entry fees refunded. Creates TOURNAMENT_CANCELLED event.
  • 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:

  1. Create an Activity of type TOURNAMENT_PRIZE for each prize recipient.
  2. Award coins via UserService.beginUserTransaction().
  3. 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 /leaderboard page.
  • 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 (calculateScore Elo section), Glicko-2 system (initRatingSystem, saveRating, RankedSeason, ratingProcessRewards, compensateMay2026Rewards), score interval boundary notifications.
  • EloRatingCalculator: Delete entirely.
  • EloRating in CareerData: 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.
  • ScoreInterval enum: Remove.
  • Front3 /leaderboard page: 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_REWARD notifications 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 projections
  • tournament_registration — Player registrations
  • tournament_round — Round projections
  • tournament_match — Match projections
  • tournament_event — Event store (append-only, indexed by tournamentId + sequenceNumber)
  • trophy — Permanent trophy records
  • tier_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), tournamentId
  • tier_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.
2 Likes