Features - jegr78/ctc-manager GitHub Wiki

Features

Seasons

  • League format: Traditional league with all teams competing across matchdays
  • Swiss format: Swiss-system tournament with automatic pairing (see Swiss Pairing)
  • Each season has a pool of eligible cars and tracks
  • Season drivers are assigned to teams per season via SeasonDriver
  • A team can leave a season once it holds no place in any of the season's phases and nothing in the season references it: no match, lineup, season driver, playoff place or team replacement. Other teams do not block it. When the last sub-team leaves, its parent leaves too, unless the parent is still referenced

Matchdays

  • A season is divided into matchdays (race days)
  • Each matchday contains one or more races
  • Matchday lineups define which drivers from each team compete

Races and Results

  • Each race is linked to a track and optionally to cars
  • Race results record per driver: finishing position, qualifying position, fastest lap flag
  • Scoring is automatic based on the scoring table:
    • Race points: 20, 17, 14, 12, 10, 8, 7, 6, 5, 4, 3, 2 (positions 1-12)
    • Qualifying bonus: 3, 2, 1 (top 3)
    • Fastest lap bonus: 2 points
  • Race attachments: upload files or add links (replays, screenshots, etc.)

Teams and Sub-Teams

  • Teams can have a parent-child hierarchy (e.g., "CTC Alpha" and "CTC Bravo" under "CTC")
  • Sub-teams allow organizing larger communities into competitive units
  • Each team competes as a unit in matchdays and playoffs
  • Brand Colors: Primary, secondary, and accent color per team (Color Picker + Hex input)
  • Logo Upload: Team logo stored via FileStorageService
  • SeasonTeam: Per-season team data (rating, optional color/logo overrides)
  • Sub-teams inherit colors and logo from their parent team via getEffective*() fallback

Team Cards

  • Generate 1080x1920px team card PNGs (Instagram Story format) from the admin UI
  • Design: Gradient Fade — vertical gradient with team colors, rating circle, logo, points, and W-L-D record
  • Tool page at /admin/tools/team-cards: season selector, card gallery with thumbnails, single/batch generation, PNG download, ZIP download
  • Rendering: Thymeleaf HTML template → Playwright headless Chromium screenshot
  • Sandboxed renderer: all graphics, custom templates included, render without JavaScript and without network access. Images and fonts must be embedded as data: URIs, and team names are HTML-escaped
  • Data sources: Rating from SeasonTeam, points and record from StandingsService, colors/logo via getEffective*() fallback chain (SeasonTeam → Team → Parent Team)
  • Requires Chromium installed via Playwright (see Development Setup)

Matchday Lineups

  • Define which season drivers represent their team on a specific matchday
  • Supports roster rotation across matchdays

Drivers

  • Identified by PSN ID (PlayStation Network) and an optional nickname
  • Assigned to teams per season via SeasonDriver
  • Driver profiles track participation across seasons

Standings and Rankings

Season Standings

  • Calculated from aggregated race results per team
  • Updated automatically after each race result import
  • Deleting a race, clearing its results, removing a scored guest from a lineup or merging drivers re-sums the match from its remaining legs. A match without any scored leg counts as unplayed again, not as a 0:0 draw
  • A phase without match scoring still counts its games, wins, draws and losses but awards no match points. Without race scoring the phase accepts no race results; the results form says so

All-Time Team Standings

  • Sum every phase of every season: regular, placement and playoff
  • A playoff matchup counts once it has a winner, as one game however many legs it had. The winner gets the playoff phase's win points and the loser its loss points. Points for and against are the summed race points of all legs
  • A bye is a win with 0:0 race points. A walkover credits the regular walkover score once and gives the forfeiting team no match points
  • A running or reopened matchup does not count

All-Time Driver Rankings

  • Cross-season rankings based on cumulative driver performance
  • Managed by DriverRankingService

Buchholz Tiebreaker (Swiss Format)

  • When teams are tied on points in Swiss format seasons, the Buchholz score (sum of opponents' points) is used as a tiebreaker
  • Only opponents from the same phase count, and in a group view only opponents from that group's matchdays. A placement or playoff match never changes a regular phase's Buchholz

Playoffs

  • Support for 4-team or 8-team brackets
  • Best-of-legs format: each matchup consists of multiple legs (races)
  • Bracket structure:
    • 8 teams: Round of 8 -> Quarterfinals -> Semifinals -> Final
    • 4 teams: Semifinals -> Final
  • Bracket visualization in the admin UI
  • Playoff results can be imported via the data import flow
  • Winner rules: auto-determine needs results for every scheduled leg. A manual winner who does not lead on points after every leg (early completion, tie, adjudication) needs a reason, which the matchup shows. Race points stay unchanged
  • Decided matchups are locked: results, lineups, legs and imports of a matchup with a winner are rejected until it is reopened
  • Reopening needs a reason and is blocked while a later matchup has results or a winner. It removes the advancement into the next round and the revoked team's lineups there, and keeps schedules and results. Every decision and reopening is kept in the matchup's decision history
  • Seeding freezes once any matchup has results or a winner, and the seeding page then becomes read-only. Before that, an empty slot is cleared on save, and an all-empty save removes the seed numbers
  • Bye: a matchup with a single team and no legs can advance that team on a bye. It is rejected while an earlier matchup still feeds the open slot
  • Walkover: a matchup with both teams and no results can be decided for the opponent of the team that forfeits. Reopening clears the bye or walkover

Swiss Pairing

  • Automatic round generation via SwissPairingService
  • Pairs teams with similar point totals
  • Rematch avoidance: teams that already faced each other are not paired again
  • Each pairing gets the phase's configured number of legs, every second leg with home and away reversed. A bye stays a single race
  • The next round needs an outcome for every pairing of the current one: a bye, a walkover, results or a score. A walkover pairing shows w/o and takes no quick score
  • Generates balanced matchups for fair competition

Cars and Tracks

  • Master data: Full GT7 car and track catalog (importable via GT7 Sync)
  • Season pools: Each season defines which cars and tracks are eligible
  • Image upload: Car and track images stored locally
  • Cars linked to race results, tracks linked to races

Data Audit

Detailed guide: Data Audit.

  • Read-only check at /admin/data-audit for stale aggregates, duplicate or reversed pairings, phase and group inconsistencies, succession problems, playoff decisions and advancement, and ambiguous public URLs
  • Every finding carries its evidence and a proposed correction, marked as reconstructible, needing a decision, or not inferable

Race Attachments

  • Attach files (screenshots, replays) or links (YouTube, Twitch) to any race
  • Two attachment types: FILE (uploaded) and LINK (URL reference)
  • Managed via FileStorageService

Backup & Restore

Available since v1.10. Detailed guide: Backup & Restore.

  • Full database backup/restore at /admin/backup — captures all 27 entity tables and the uploads/ tree into a single ZIP
  • Export: streaming ZIP download (ctc-backup-<ISO-instant>.zip) with manifest.json (schema version + per-table row counts) + data/<entity>.json + uploads/
  • Import: preview screen with row-count diff and schema-version check; replace-all transaction — database is only touched after explicit confirmation
  • Schema-version gate: accepts schema versions 1, 2 and 3 and rejects any other version with HTTP 400 before any DB write
  • Auto-backup-before-import: every import is preceded by an automatic ZIP at <import-backups-dir>/<timestamp>/auto-backup-before-import.zip, retained for 24 hours along with the previous uploads-old/ tree
  • Import lock: while an import runs, every admin page shows a yellow read-only banner; concurrent writes are rejected with HTTP 503
  • Audit log: every import is recorded in the data_import_audit table
  • Operator runbook for manual recovery: docs/operations/import-runbook.md
⚠️ **GitHub.com Fallback** ⚠️