LATEST
-
▸
Every tournament now publishes a stable 8-character invite URL: /tournaments/join?code=ABCDEFGH - sharable, never expires. Codes are 32 chars from the URL-safe alphabet (no 0/O/1/I/L) so a screenshot is enough to type it back.
-
▸
Visibility toggle on the create form: public - shown on /tournaments browse, anyone can join, no password. private - HIDDEN from /tournaments browse; the invite URL still resolves on /tournaments/join?code=... but POST .../join requires the join password (hashed with the same PBKDF2 envelope used on /teams since v0.137). Switching visibility after creation is a creator edit surfaced as a small "Visibility: Public | Private" badge on the detail page.
-
▸
Team-first joining (the v0.130 teams.id becomes a tournament entrant): Players first create a team on /teams - that team is the thing that registers for a tournament. The team owner becomes the tournament team captain; subsequent team members fill roster slots up to the format ceiling (3/6/9/13).
-
▸
Steam auth required at every join gate: /tournaments/join?code=... redirects to Steam OpenID if no session cookie, POST .../join is 401 without one.
-
▸
Schema additions (this migration): tournaments.invite_code TEXT UNIQUE tournaments.visibility TEXT NOT NULL DEFAULT public CHECK (visibility IN (public,private)) tournaments.join_password_hash TEXT Plus idx_tournaments_invite_code and idx_tournaments_visibility_invite. Existing rows are backfilled with deterministic LEG##### invite codes.
-
▸
The existing /tournaments/create wizard (v0.141) gained a Step-1.5 "Visibility" card before the schedule step, and the create API now accepts {visibility, join_password}. The XMLHTTP wire format is JSON; legacy flat-form fields (estimated_duration_min, max_participants, …) are still accepted for back-compat but ignored.
-
▸
Every team roster is now capped at exactly 13 slots, full stop. The owner-tunable MAX ROSTER knob is gone from the create-team form so new captains no longer have to decide what "max roster" means.
-
▸
Schema: teams.member_capacity is rebuilt from a 2-64 range (v0.136, migration 0130) into a CHECK (member_capacity = 13) equality constraint with DEFAULT 13. SQLite has no ALTER COLUMN so this migration does a table rebuild: shadow rename -> CREATE TABLE -> INSERT … SELECT with explicit 13 coercion -> drop shadow -> re-attach indexes. Wrapped in a transaction so a failure rolls back cleanly.
-
▸
SQLite FK-side repair in the same migration: ALTER TABLE teams RENAME rewrote the auto-managed FOREIGN KEY in team_members to point at "teams__legacy_capacity", and the subsequent DROP TABLE on the shadow would have left team_members unable to insert rows. The migration duplicates the team_members rebuild so the FK rebinds to the new teams id instead. Identical columns + indexes, no data shape change.
-
▸
Every existing row is backfilled to 13 (defensive - all v0.136-0133 rows sat at the v0.136 default of 6 today, but the rebuild INSERT explicitly coerces every row to 13 instead of trusting the column). The redundant "WHERE member_capacity != 13" UPDATE is purely a sanity assertion that fails loud if anything drifted off 13.
-
▸
No more "X / 6 members" or "X / 9 members" badges - every list card and every detail page now shows "X / 13 members". The detail-page ROSTER header now says "ROSTER (X / 13)" identically across teams.
-
▸
/api/teams/:id/join still returns the same team_full error (capacity: 13, current: N) once the 13th member is in place. /api/teams/ POST requests that hand an explicit member_capacity other than 13 are rejected by the server-side validator (CAPACITY_LOCKED error, 422), so legacy clients that still ship the field get a typed error rather than a silent fallback.
-
▸
/api/teams/:id/* POST routes keep bare-integer :id - no URL change. /teams/<id> and /teams/<slug> page routes are unchanged. Migration is purely data-shape + UX cleanup.
-
▸
Team #1 (Envision Crew) now resolves at the canonical pretty URL https://play.tfcleague.com/teams/151-envision-crew instead of the migration-backfill /teams/1-legacy placeholder.
-
▸
Hand-picked the disjoint prefix "151" to match the spec example brief (1-3 random digits, name-slugified).
-
▸
The canonicalization 301 in /teams/:id will automatically redirect visitors who still have the /teams/1-legacy bookmark to the new URL - no cache flush needed because canonical redirects are honoured by all major browsers and search engines.
-
▸
Team URLs no longer expose the raw AUTOINCREMENT id - they now route via /teams/<slug> instead of /teams/<id>.
-
▸
The slug format is "<random-digits>-<name-slugified>", matching the brief example: team_id=1, name="Envision Crew" -> /teams/115-envision-crew.
-
▸
The old /teams/<id> route continues to work as a backward-compat alias so existing bookmarks and search-engine cache entries do not 404.
-
▸
Slugs are generated by the application layer (teamSlug() helper in src/lib/team-slug.ts) with retry-on-collision against the new UNIQUE slug index.
-
▸
Existing teams are backfilled with a "<id>-legacy" slug on migration and regenerated on next edit/create from the team name.
-
▸
Team owners can now optionally set a join password when creating a team or from the team detail page (v0.137 only exposes it at creation time - a settings UI for changing it later is on the v0.138 roadmap).
-
▸
When a password is set, the team card shows a 🔒 badge so the rest of the league can tell at a glance that the team is gated.
-
▸
Joining a password-protected team now opens a small inline modal that asks for the password; the join request POSTs {password} alongside the usual flow.
-
▸
The API returns typed errors: "password_required" when the team is gated and no password was sent, and "wrong_password" when the supplied password does not match. Both are 409.
-
▸
Passwords are hashed at rest with PBKDF2-SHA-256 + 16-byte random salt + 100,000 iterations (OWASP 2023 baseline). The raw password is never written to disk. The hashed blob is "pbkdf2_sha256$100000$<saltHex>$<hashHex>" so we can reroll the iteration count later without breaking stored teams.
-
▸
Creating a team without a password keeps the v0.136 experience (open join).
v0.129
2026-07-09
Supporter Pack: Visual Overhaul
- Supporter Pack now grants an animated gold glow ring on your avatar and an animated "Supporter" tag next to your name everywhere - profile page, profile card popup, lobby slots, lobby chat, leaderboard, and match detail pages - The previously-unused light-gold chat colour perk is now live: Supporters without a purchased Name Colour will see their name tinted gold in lobby chat and the points leaderboard - Dropped the "prioritised lobby matchmaking" line from the reward description - it never had a working mechanic behind it in the current draft-based lobby system - Existing Supporters have been backfilled with the light-gold chat colour so this applies retroactively, no need to re-purchase
-
▸
New sidebar entry "Create Team" in the Quick Actions panel.
-
▸
Authenticated users can create a team with a name (3-40 chars), tag (2-5 chars, uppercase), and optional avatar URL.
-
▸
Both team name and tag are unique (case-insensitive) - first come, first served.
-
▸
The creator is auto-enrolled as the team owner in team_members. v0.137+ will let owners add/remove members.
-
▸
/teams lists all active teams, /teams/me lists teams you own or belong to, /teams/:id is the public detail page.
-
▸
Teams do NOT yet integrate with tournament rosters or PUGs - this release is the minimum viable surface so the sidebar entry has real backing. The tournament_teams table (per-bracket rosters) is untouched by this migration.
-
▸
The shop now only lists rewards you can still buy. Once you own a perk, it moves out of the catalogue and into My Inventory.
-
▸
My Inventory shows every redeemed reward with chips: ACTIVE (perk is currently applied to your profile), OWNED (you have it on record but your profile is using something else), and UNLOCKED (perk has no toggle, e.g. Profile Makeover).
-
▸
Cancellations are surfaced as a footnote under the inventory list.
-
▸
One perk, one purchase: the API rejects re-buying a reward you already own; the catalogue hides it. (Repeatable rewards can opt in - none today.)
-
▸
Migrations 0120/0122 (crash cleanup for the original v0.121 bug) ran after Koi and .glitch had already legitimately re-bought the Supporter Pack, and incorrectly cancelled that new purchase because the cleanup query matched on user+reward+status instead of a specific row.
-
▸
Restored: both players' Supporter Pack redemption is now marked fulfilled and the Supporter badge/tag is active again. No further action needed on their end - refresh the page to see it.
-
▸
Root cause fixed going forward: future cleanup/refund migrations must pin the exact redemption/transaction id, never just (user_id, reward_id, status).
-
▸
The /points page now has a My Inventory panel that lists every reward you have already redeemed. Each item shows whether it is currently active on your profile, owned but a different perk is active, or otherwise.
-
▸
Status chips: ACTIVE (gold) when the perk is applied right now, OWNED (green) when you have it on record but a different perk is active, UNTOGGLED (gray) for perks without an active toggle.
-
▸
Cancelled or voided redemptions (such as the v0.128.2 supporter-pack refunds) appear in a quiet footnote so you can still see the audit context without it cluttering the main list.
-
▸
Buying the Supporter Pack reward on /points now completes end to end. Click Confirm, and your points are deducted, your Supporter status is set, and the modal closes cleanly.
-
▸
The error message "user is not defined" that briefly flashed in red during a redemption attempt has been removed. The cause was a leftover defensive block in our redemption code that was missing a data fetch, not anything you did.
-
▸
If you try to redeem a reward you have already bought, the modal now says "You've already redeemed <reward name>. Refresh to see your perks." instead of showing a raw database error.
-
▸
Two players (Koi and .glitch) were refunded their 1,500 stranded points from the earliest Supporter Pack attempts that hit the bug. Each transaction history now shows the refund paired with the original deduction. .glitch also has a brief "reversal" entry in their history that pairs with an internal audit adjustment we caught during testing - net effect is one refund. Both players can now re-purchase the Supporter Pack normally - the "already redeemed" modal that briefly blocked them after the refund has been fixed.
-
▸
Buying the Supporter Pack reward on /points now completes end to end. Click Confirm, and your points are deducted, your Supporter status is set, and the modal closes cleanly.
-
▸
The error message "user is not defined" that briefly flashed in red during a redemption attempt has been removed. The cause was a leftover defensive block in our redemption code that was missing a data fetch, not anything you did.
-
▸
If you try to redeem a reward you have already bought, the modal now says "You've already redeemed <reward name>. Refresh to see your perks." instead of showing a raw database error.
-
▸
Two players (Koi and .glitch) were refunded their 1,500 stranded points from the earliest Supporter Pack attempts that hit the bug. Each transaction history now shows the refund paired with the original deduction. .glitch also has a brief "reversal" entry in their history that pairs with an internal audit adjustment we caught during testing - net effect is one refund. Both players can now re-purchase the Supporter Pack normally - the "already redeemed" modal that briefly blocked them after the refund has been fixed.
-
▸
Buying points on /points now works end to end. Click Buy, pay through PayPal, and your points land on your account.
-
▸
The PayPal error message that used to say "Payment failed. Please try again." on a successful PayPal order has been removed; the cause was a missing column on our side, not anything you did.
-
▸
The /servers region list now sorts the regions you actually use first. The top three regions get a small yellow POPULAR badge so you can spot them at a glance.
-
▸
Each region card now also shows how long that region takes to be ready on average (in seconds), so you know what to expect before clicking Reserve.
-
▸
The Tournament create-form on /tournaments/create reuses the same ordering, so the regions you pick there also surface popular + fast ones first.
-
▸
Fixed a long-standing bug where lobbies would randomly reset back to gathering once they reached the ready check. That does not happen anymore, ever.
-
▸
Each player in a lobby now gets their own 20-minute countdown. It starts when the lobby enters the ready check, or when you late-join after. A small mm:ss badge next to your slot shows time remaining.
-
▸
When 5 minutes are left on your timer, a yellow popup asks "Are you still playing?" with YES and NO buttons.
-
▸
YES keeps you in the lobby and resets your timer to a fresh 20 minutes. Other players' timers are not touched.
-
▸
NO drops just you back to the player pool. No one else moves, no shuffle, no class reset, no lobby status change.
-
▸
Players sitting in the pool without a slot or class for over an hour get cleaned up. Drafted players only get dropped after their personal timer runs out and they did not answer the yellow prompt.
-
▸
You can now buy a TFCL Premium plan as a gift for someone else. Same prices as a self-purchase: $5 for 1 month, $20 for 6 months, $35 for 1 year.
-
▸
After checkout you receive a one-time code. Send it however you want: Discord DM, email, gift card, hand them a note. The code stays valid for 2 years until redeemed.
-
▸
The redeemer logs into their own account, opens /redeem, pastes the code, and the grant stacks on top of any premium time they already have. Buying early does not waste their unused time.
-
▸
Admins can mint free codes directly from /admin for promotions, support credits, staff compensation, or tournament prizes.
-
▸
Codes are single-shot: once redeemed they are done. Admins can revoke unused codes.
-
▸
New "Buy as a gift" section on /premium with the same PayPal flow as a self-purchase, plus a new public /redeem page with a clean paste-and-go flow.
-
▸
You can now buy a TFCL Premium plan as a gift for someone else. Same prices as a self-purchase: $5 for 1 month, $20 for 6 months, $35 for 1 year.
-
▸
After checkout you receive a one-time code. Send it however you want: Discord DM, email, gift card, hand them a note. The code stays valid for 2 years until redeemed.
-
▸
The redeemer logs into their own account, opens /redeem, pastes the code, and the grant stacks on top of any premium time they already have. Buying early does not waste their unused time.
-
▸
Admins can mint free codes directly from /admin for promotions, support credits, staff compensation, or tournament prizes.
-
▸
Codes are single-shot: once redeemed they are done. Admins can revoke unused codes.
-
▸
New "Buy as a gift" section on /premium with the same PayPal flow as a self-purchase, plus a new public /redeem page with a clean paste-and-go flow.
-
▸
Tournaments are now organized around teams, not individuals. Each team has a captain (the first person to register under that team name) and a roster.
-
▸
We split the cap into two clearer numbers: a max number of teams (2 to 64) and a per-team roster size set by your format. Standard roster sizes: Ultiduo = 3, 4v4 = 6, Sixes = 9, Highlander = 13.
-
▸
Browse cards now show "X of Y teams" and "Z of W roster slots filled" so you can see at a glance how full the tournament is.
-
▸
When you join, you pick a team name. The first person with that name becomes captain. The next people with the same name fill the open slots until the roster is full. The tournament still accepts more teams up to the max.
-
▸
The old "MAX PARTICIPANTS" label is gone from the form. If a third-party tool was using the old name, the API still accepts it for back-compat.
-
▸
Tournaments are now organized around teams, not individuals. Each team has a captain (the first person to register under that team name) and a roster.
-
▸
We split the cap into two clearer numbers: a max number of teams (2 to 64) and a per-team roster size set by your format. Standard roster sizes: Ultiduo = 3, 4v4 = 6, Sixes = 9, Highlander = 13.
-
▸
Browse cards now show "X of Y teams" and "Z of W roster slots filled" so you can see at a glance how full the tournament is.
-
▸
When you join, you pick a team name. The first person with that name becomes captain. The next people with the same name fill the open slots until the roster is full. The tournament still accepts more teams up to the max.
-
▸
The old "MAX PARTICIPANTS" label is gone from the form. If a third-party tool was using the old name, the API still accepts it for back-compat.
-
▸
Tournament servers now spin up automatically. 30 minutes before tournament day starts, we bring up enough servers across the regions you picked.
-
▸
Those servers are reused across every match that day. We do not tear them down until every match on that day has been reported with a logs.tf link.
-
▸
Reporting a match now asks for a logs.tf URL alongside the score. The tournament creator reports and verifies results, and there is a clear note on the create form so you know up front.
-
▸
The bracket keeps a match-day count so multi-day tournaments know which day each match falls on. We track the per-day, per-region server pool behind the scenes.
-
▸
The old "EST. DURATION (MIN)" field on the create form is gone. It was a v0.122-era proxy for "how long will this run" and stopped making sense once tournaments got team schedules.
-
▸
New: HOW MANY DAYS. Pick 1 to 7 days. Defaults to 1. If you pick more than 1, the next question appears below.
-
▸
New: MATCHES PER DAY. Only shown when HOW MANY DAYS is greater than 1. We suggest a reasonable number, but you can pick uneven splits like 10 matches Saturday, 3 matches Sunday.
-
▸
New: SERVER LOCATIONS. Pick one or more regions. The form shows a live preview of how many servers we will spin up (about 1 server per 4 matches per day, per region).
-
▸
The legacy "minutes" estimate is preserved on the back end so older tournament views still see a sensible value.
-
▸
We save the schedule details with your tournament so future match provisioning knows what to expect.
-
▸
The create form no longer asks for "MAX PARTICIPANTS". That label was inherited from the old individual-signup days and is misleading now that tournaments are team-based.
-
▸
New: MAX TEAMS. Hard cap on numbered teams in the bracket, from 2 to 64.
-
▸
New: ROSTER SIZE. How many players per team. We pre-fill the standard for your format (Ultiduo = 3, 4v4 = 6, Sixes = 9, Highlander = 13). You can lower it for unusual rule sets but cannot go above the cap.
-
▸
Roster minimum is 1 (a captain-only team). In practice the form still defaults to the format standard so the common case needs no override.
-
▸
The back end rejects any oversize roster at create time, so a 14-player Highlander team fails to save rather than slipping through.
-
▸
Tournaments are now organized around teams, not individuals. Each team has a captain (the first person to register under that team name) and a roster.
-
▸
We split the cap into two clearer numbers: a max number of teams (2 to 64) and a per-team roster size set by your format. Standard roster sizes: Ultiduo = 3, 4v4 = 6, Sixes = 9, Highlander = 13.
-
▸
Browse cards now show "X of Y teams" and "Z of W roster slots filled" so you can see at a glance how full the tournament is.
-
▸
When you join, you pick a team name. The first person with that name becomes captain. The next people with the same name fill the open slots until the roster is full. The tournament still accepts more teams up to the max.
-
▸
The old "MAX PARTICIPANTS" label is gone from the form. If a third-party tool was using the old name, the API still accepts it for back-compat.
-
▸
Brackets are now real: standing of a tournament renders an actual match tree (single-elim by default; round-robin for ultra-small ones; double-elim opt-in for admins).
-
▸
Match results can be reported one at a time by the creator/admin via a new endpoint.
-
▸
Check-in countdown widget on the detail page counts down the live window and shows X / N players checked in.
-
▸
Optional auto-provision: when the creator toggles "auto provision a TFCL server", flipping to in_progress spins up a Vultr TFCL server from the same snapshot the Servers page uses; ip/password/rcon land on the tournament row.
-
▸
Full-text search + format filter chips on /tournaments browse (use ?format=sixes&search=friday in the URL).
-
▸
New /tournaments/me page lists every tournament you created or joined and shows aggregate stats (created/played/wins/podiums) at the top.
-
▸
Profile stats card now includes TOURNAMENTS alongside MATCHES/WINS/LOSSES/WIN RATE.
-
▸
New "All-Class Frame Bundle" reward (1,800 pts): unlocks all 9 class profile frames at a 33% saving
-
▸
New "Supporter Pack" reward (1,500 pts): persistent Supporter tag, light-gold chat colour, and +5% bonus on top of Premium's 2× earning (effective 2.1×)
-
▸
Buy Points: a PayPal-funded $10 = 10,000 points top-up is now available on the Points Store page, gated to authenticated users
-
▸
Premium re-priced: $5 / 1 month, $20 / 6 months (was $15), $35 / 1 year (was $25). 6m and 1y plans save 33% / 41%
-
▸
TFCL Premium now grants 2× points on every gameplay reward (matches, logs, top scorer, map votes) and lets members schedule on-demand servers up to 48h ahead
-
▸
New Tournaments section at /tournaments - community-run brackets in 6s/4v4/Ultiduo formats
-
▸
Free only at launch: every tournament created in v0.122 uses "free_open" mode (no entry fees, no prize pools, no PayPal involved)
-
▸
Tournament creators publish a draft -> admin approves -> participants join -> check-in window -> bracket runs
-
▸
"Coming soon" tag on the page makes it explicit that prize pools and entry fees are intentionally deferred
-
▸
Tournament schema is forward-compatible: the same table will accept paid modes in a later release without a schema rebuild
v0.120
2026-07-05
Admins can now manage/destroy on-demand Servers reserved by other players
- Admins can now destroy any on-demand Server from the admin panel's Servers tab, not just the ones they personally reserved - Adds a DESTROY button next to every active/provisioning reservation in that list - Destroying a server this way actually terminates the underlying game server instance, just like a player destroying their own server early - No change for regular players - you can still only manage your own reservations from the Servers page
v0.119
2026-07-05
Fixed logs.tf and demos.tf reporting "No API Key" on on-demand Servers
- The in-game LogsTF and demos.tf plugins on our on-demand Servers need their own API keys to auto-upload a match's log/demo when it ends - neither had ever been configured, so both reported "No API Key" on every match - Both keys are now written into every newly-provisioned server automatically - Already-running servers self-heal the fix the next time their config is applied - no reboot needed - Scoped only to TFCL's own on-demand Servers - PUG lobby servers (rented from serveme.tf) already had their own working logs.tf/demos.tf integration and are unaffected
v0.117
2026-07-05
IP addresses are now hashed - not even admins can see a real IP
- Previously, activity logs stored your raw IP address in plaintext, visible to site admins - Your IP is now immediately converted into a one-way cryptographic hash the moment it's received, and only that hash is ever stored - the real IP is never written to our database - This is irreversible: nobody, including our own admins with full database access, can recover your real IP from what's stored - The hash is still consistent per-IP, so we can still catch the same address being used across multiple accounts for abuse/ban-evasion detection, without ever seeing what that address actually is - Purged the small number of previously-stored raw IPs, since they predate this change - Updated the Privacy Policy's "Technical Logs" section to reflect this
v0.116
2026-07-05
Updated the Privacy Policy for TFCL Premium, on-demand Servers, and Map Uploads
- The Privacy Policy hadn't been updated since these features shipped and was missing coverage of them - Added disclosure sections for TFCL Premium payment data (PayPal order info - we never receive your card or PayPal login details) - Added disclosure for on-demand Server data (region, map, config, and the connect info/RCON password needed for you to use your own server) - Added disclosure for custom map upload metadata - Clarified what data is and isn't shared publicly, and how long different types of data are retained
v0.115
2026-07-05
Added an admin log of who reserves on-demand TFCL Servers
- New admin-only Servers tab shows every on-demand server reservation: who reserved it, tier, region, status, connect info, and timestamps - Filterable by tier and status, and searchable by username, SteamID, or IP - Shows a live summary of currently-active Free/Premium servers and the all-time reservation count - This is for admin abuse/capacity investigation only - not visible to regular users
v0.114
2026-07-05
Adjusted Servers page capacity again
- Free tier capacity set to 20 concurrent servers - Premium tier capacity set to 80 concurrent servers
v0.113
2026-07-05
Raised on-demand Servers page capacity
- Free tier capacity raised from 15 to 30 concurrent servers - Premium tier capacity raised from 80 to 535 concurrent servers - Pure capacity change - reservation length, extension rules, and expiry behavior are unaffected
v0.112
2026-07-05
Fixed the whitelist still not sticking, added Highlander presets, fixed server name display
- Found a second, deeper bug causing the item whitelist to silently revert back to the broken placeholder on repeat use of the same config - Fixed by removing a redundant line in TFCL's config files that was fighting with the whitelist system - Verified live: the correct whitelist now sticks even after re-selecting the same config multiple times - Added 5 new Highlander (9v9) config presets to the Servers wizard (Scrim, Match, KOTH Match, KOTH Bo5, Stopwatch), all using the real RGL.gg Highlander whitelist - Fixed on-demand servers always showing "Team Fortress" as their server name in the in-game server browser instead of a proper label
v0.111
2026-07-04
Fixed the real cause of "item servers don't work" - a broken item whitelist
- After the item-drop fix in v0.110, item restrictions still weren't being enforced on any TFCL config (6v6, Highlander, or Ultiduo) - Found that TFCL's configs were pointing at whitelist IDs that didn't exist, so the whitelist plugin was silently failing and falling back to a placeholder with zero restrictions - Fixed 6v6/KOTH to use the real RGL.gg 6v6 whitelist, and Ultiduo to use TFCL's own custom whitelist - Verified live: item restrictions are now actually being enforced as intended
v0.110
2026-07-04
Actually fixed the "Missing map, disconnecting" error, plus fixed broken item drops
- Root cause was the map host silently compressing map files in a way that broke Source engine's downloader - fixed by disabling compression specifically for map files - Verified with a real TF2 client-style request: downloads now report a proper size and complete correctly - Separately fixed in-game item servers (backpack/item drops) not working on on-demand TFCL Servers by giving them a proper server login, instead of connecting to Steam anonymously
v0.109
2026-07-04
Fixed maps still not downloading for connecting players after v0.108
- v0.108 fixed the server's own map fetch, but a separate engine setting that tells connecting players where to download from was still empty - Also fixed a folder-structure mismatch that caused "file not found" even when the download setting was correct - Migrated all existing custom maps into the corrected folder structure and verified byte-for-byte via checksum - Confirmed fixed with a live server test - map download now works end-to-end for connecting players
v0.108
2026-07-04
Fixed custom maps failing to auto-download on TFCL Servers
- Found the real cause of "missing map" disconnect errors on custom maps like koth_ashville_final2 - The plugin responsible for auto-downloading maps was defaulting to the wrong download source instead of TFCL's own map host - Server now always points at the correct TFCL map host regardless of the underlying server image state, so this fixes itself automatically on every new server - Scoped only to TFCL's own on-demand Servers - PUG lobby servers are unaffected
v0.107
2026-07-04
Redesigned the Servers page as a 3-step guided creator (Location / Map / Config + Whitelist)
- The Servers page provisioning flow has been completely rebuilt as a guided, step-by-step wizard matching the look and feel of the Create-a-PUG wizard, instead of one long flat form - Step 1 (Location): pick Free or Premium tier, then pick a region from a photo grid showing a real photo of each region's city, filterable by continent - Step 2 (Map): pick a map from the curated RGL Season 20 Sixes pool or the Ultiduo pool, or type a custom map name - Step 3 (Config + Whitelist): pick a server config preset (TFCL league configs or RGL/UGC/Custom), with a clear badge on each option showing whether that config enforces a whitelist.tf whitelist - A sticky summary bar shows your chosen tier/region/map/config before you provision - The existing My Servers list, live status polling, and destroy/copy-connect-info actions below the wizard are unchanged
v0.103
2026-07-03
Fixed on-demand TF2 servers getting permanently stuck on "Configuring..."
- Found and fixed the real root cause of on-demand TF2 servers never applying their map/config/RCON password and getting stuck on "Configuring..." forever: the server's boot script was silently aborting on its very first real step (writing the server config file) whenever an internal assumption about the file's location did not hold, before the password/config could ever be applied - Rewrote the boot script so every step is independently best-effort instead of the whole thing aborting on the first failure, and so it dynamically discovers the running TF2 process instead of guessing a fixed path - Confirmed fixed via two independent live test servers with different map/config combinations - both came online correctly with the right map, config, and RCON password applied - Removed the temporary diagnostic logging that was added while investigating this issue, now that the real fix is confirmed working
v0.101
2026-07-03
Renamed "Restoring snapshot..." to "Configuring..." on the Servers page; fixed a capacity leak from permanently-stuck servers
- The Servers page now shows "Configuring..." instead of "Restoring snapshot..." while a provisioned server is waiting for its map/config to finish applying over RCON (display-only rename, no backend/API change) - Found and fixed a real bug while reviewing server status updates: the lazy cleanup that frees up capacity for expired reservations only looked at servers with expires_at set, but expires_at is intentionally left null until a server is confirmed ready - so a server that got permanently stuck before ever becoming ready (TFCL Server instance hangs, TF2 process never starts, RCON blocked, etc) was invisible to cleanup and would sit there forever, permanently eating one of the tier's limited capacity slots - Added a second cleanup pass that catches servers stuck without ever reaching ready for more than 60 minutes (well above the normal worst case) and marks them as errored instead, freeing the slot
v0.102
2026-07-03
Fixed servers leaving "Configuring..." and showing "Ready" while the snapshot was still restoring
- Found the actual root cause: the TFCL Server starts listening on the TF2 server's port within seconds of boot, long before the server has actually finished restoring and the TF2 process is up - our RCON code was treating a timed-out response (no reply at all) the exact same as a real successful reply, so the config-apply step was reporting success purely because the port accepted a connection, not because the server actually answered anything - Rewrote the RCON session logic so a server is only marked ready once it has demonstrably responded to a real command over RCON, not just accepted a TCP connection - This is what was actually causing the "Configuring..." status to end (and full connect info to appear) far too early while the server was still unusable
v0.100
2026-07-03
Actually fixed !extend showing a false failure message (previous v0.099 fix was not the real cause)
- v0.099 shipped a defensive fix (disabling response compression) for the !extend false-failure bug, but live in-game testing showed it still happened afterward - Added temporary debug logging to the plugin and traced the real cause: the plugin was asking the game's HTTP library to copy the server's response into a buffer 1 byte larger than expected, which caused that copy to silently fail every single time - so the plugin was always working with an empty response, even when the extension had genuinely succeeded on our end - Fixed by requesting the exact size the library expects; verified with a real successful !extend in-game showing the correct "Reservation extended by 60 minutes" message
v0.099
2026-07-03
Fixed !extend showing "Couldn't extend the reservation" even when it actually worked
- Live-tested !extend/!who for the first time on a real TF2 server this release, and caught two real bugs in the process - Fixed: !who could print a spurious "You do not have access to this command" line before the real answer, caused by our command name colliding with SourceMod's built-in admin-only sm_who command - Fixed: !extend could report a generic failure even when the extension genuinely succeeded (the reservation really was extended in our database) - Cloudflare was gzip-compressing the response, which the in-game plugin's simple parser could not read
v0.098
2026-07-02
In-game !extend and !who commands for on-demand servers; Premium tier now has an auto-expiry
- Added !extend: type it in TF2 chat to add 60 minutes to your server's reservation (max 3 extensions per server, only usable when 20 minutes or less remain on the current reservation) - If multiple players type !extend at nearly the same moment, only the first one counts - the rest are silently dropped, no error shown - Added !who: shows which Steam user reserved the server - Added in-game chat warnings at 20/15/10/5/1 minute(s) before a reservation ends - Premium tier servers now auto-expire for the first time: 3.5 hours by default, extendable up to 6.5 hours total via !extend (previously premium had no expiry at all) - Free tier unchanged: 2 hours by default, extendable up to 5 hours total via !extend - Server cards on the Servers page now show extension count and the correct base duration per tier
v0.097
2026-07-02
Free server 2-hour timer now starts when the server is ready, not when you click Provision
- Previously the free-tier 2-hour auto-expiry clock started the instant you provisioned a server, which meant slow-restoring regions could eat 10-30+ minutes of your window before you could even connect - expires_at is no longer set at creation time - it now gets stamped at the same moment the server is confirmed ready (the first successful RCON check), so free-tier users get the full 2 hours from when the server is actually usable - Server cards for a free-tier server that is still restoring now show "2h timer starts once ready" instead of a countdown, since the timer genuinely has not started yet
v0.096
2026-07-02
Accurate "Ready" detection + live average provisioning time per region
- Servers page no longer shows connect info the moment the TFCL Server reports as active - it now waits for a real, successful RCON check against the TF2 process itself, which only succeeds once the server has actually finished restoring and is listening - New "Restoring snapshot..." status shown while a server is active but not yet actually connectable, with a live ETA when we have data for that region - Each region in the picker now shows its own live rolling average provisioning time (create -> ready), which updates automatically as more people provision servers there
v0.095
2026-07-02
Custom Maps upload for admins & TFCL Plus
- New Maps page lets anyone browse the community's custom .bsp maps, hosted at maps.tfcleague.com - Uploading is restricted to admins and TFCL Plus (premium) members - browsing and downloading remain open to everyone - Uploads are capped at 95MB and go straight to the map server over FTP, no manual steps required - Map uploaders (or admins) can remove their own maps from the list - Admins can now grant/revoke TFCL Plus from the ADMIN PANEL -> USERS tab
v0.094
2026-07-02
Map + config selection for on-demand Servers
- Provisioning a server now lets you pick a map (RGL Season 20 Sixes pool or the dedicated Ultiduo pool) and a server config preset (RGL Scrim/Match, RGL KOTH Scrim/Match, UGC Ultiduo, or Custom) - Picking a KOTH or Ultiduo map auto-switches the config preset to match, mirroring the lobby-creation wizard - The chosen map + config are applied automatically via RCON once your server finishes booting - no manual setup required - Server cards show the selected map and config, plus an "applying..." / "applied" status while RCON configuration is in progress
v0.093
2026-07-02
On-demand Servers page
- New Servers page lets any logged-in player provision their own TF2 server on demand - Free tier: North America regions only, capped at 15 concurrently-running servers platform-wide, auto-destroyed exactly 2 hours after creation - Premium tier: any region worldwide, capped at 80 concurrently-running servers platform-wide (capacity reserved for the upcoming TFCL Plus service) - Live capacity meter shows current usage against each tier's cap - Server cards show connect info (IP:port, password, rcon) once provisioning completes, plus a countdown for free-tier servers
v0.092
2026-07-02
Captain Draft mode for Sixes PUGs
- Create-PUG wizard now lets you choose the gathering mode for Sixes PUGs: Pick-Class (join the pool, claim any open class/team slot yourself) or Captain Draft (top 2 highest-ELO players are auto-assigned as captains and alternate picking players onto their team) - Ultiduo PUGs always use Pick-Class, since captain draft is built around 6-player teams - Captain Draft was previously only usable on legacy lobbies; it is now fully available again when creating a new Sixes PUG
v0.091
2026-07-02
PUG branding + simplified nav
- Site-wide copy updated from "Lobby/Lobbies" to "PUG/Pick Up Games" terminology (page headings, buttons, empty states, chat panel, etc.) - Nav bar link updated from "Lobbies" to "PUG" - Nav bar simplified: Community, Roadmap, and Rules now live under a "More" dropdown, leaving PUG, Leaderboard, and Matches as primary links
v0.090
2026-07-02
Lobby pages moved to /pug and /pugs
- The lobby detail page moved from /lobby/:code to /pug/:code - The browse page moved from /lobbies to /pugs - The create page moved from /lobbies/create to /pugs/create - Old links still work: visiting any old /lobby or /lobbies URL automatically redirects to the new address
v0.089
2026-07-02
Choose your format and voice requirement when creating a lobby
- Lobby creation now lets you pick Sixes (6v6) or Ultiduo (2v2 Soldier+Medic) right on the create page, with a matching map pool and config preset for each format - Added a voice/Discord requirement selector for new lobbies: Required (Discord mandatory, voice-check waiting room enforced - the previous default behavior), Optional (Discord still offered and encouraged, but not required and the waiting room is skipped), or Off (no Discord requirement at all) - Redesigned the create-lobby page with angled, house-style panels for the format toggle and voice mode selector - User-created Ultiduo lobbies use the same pick-a-slot flow as the evergreen queues (no captain draft)
v0.088
2026-07-01
Redesigned the home page with a full TF2 makeover
- Rebuilt the entire home page from scratch: angled, notched shapes throughout instead of boxy rounded cards - Added an animated headline, count up stat numbers, scroll reveal animations, and a scrolling ticker banner - Added diagonal team colored format panels and killfeed style match rows - Added 8 original TF2 themed illustrations across the page: hero banner, Sixes team lineup, Ultiduo duo, class feature portraits, and an about section banner - Made multiple accuracy passes on the artwork so it matches real TF2 class designs, including a RED team accurate Soldier and Engineer - Cleaned up a few smaller visual details on the hero section
v0.087
2026-07-01
Discord voice-check waiting room + ready-up sound
- Added a Voice Check phase: once everyone readies up, players must join a temporary per-lobby Discord waiting room voice channel before the lobby goes active (2-minute join window with a live countdown and a Discord deep-link) - As soon as every rostered player has joined the waiting room, the bot automatically sorts everyone into their RED/BLU team voice channels and the lobby goes active - no more manually finding your teammates in Discord - Anyone who does not join the waiting room in time is returned to the gathering pool and the lobby reopens for new players - Added a ready-up sound effect that plays when a lobby enters the Ready Check phase - Fixed two underlying Discord bot bugs found while shipping this: a missing permission-overwrite type that could block waiting-room channel creation, and the bot itself lacking the Connect permission it needed to grant to players
v0.086
2026-07-01
Fixed logs.tf auto-reporting - it was never actually finding logs
- Found and fixed a bug where the logs.tf search API's response was misread: the `results` field is a count, not the array of logs (the array is `logs`), so automatic log discovery silently returned nothing on every single match since this feature launched - Fixed a second bug where the auto-report call after manually reporting a match result was not properly kept alive in the background, risking it being cut off before it finished - Logs.tf auto-attach, auto score verification, and per-player stat enrichment should now actually work going forward
v0.085
2026-07-01
Fixed lobbies not kicking inactive players
- Idle-player eviction now runs during drafting and ready-check, not just while gathering, so an AFK draftee or someone who refuses to ready up no longer stalls a lobby forever - Idle eviction now also runs on the permanent main (6v6) and ultiduo (2v2) queues, matching what the HOW IT WORKS panel already promised - Ready-check now enforces its deadline: players who never ready up are automatically removed and the lobby reopens for new players - Added a "you will be kicked in X minutes for inactivity" warning box that now also appears in drafting/ready-check and on the main/ultiduo queues, with a one-click "confirm you're still active" button - Added a toast notification when you get removed from a lobby for inactivity
v0.084
2026-07-01
Server provisioning note, bagel image fix
- HOW IT WORKS sections on all lobby pages now note that server provisioning can take a few minutes to fully start up - Fixed incorrect background image for koth_bagel_rc12 (was showing tc_hydro) - Map background images now display correctly for all 10 maps in the sixes and ultiduo pools
-
▸
Ultiduo lobby at /lobby/ultiduo is now fully functional
-
▸
Fixed routing bug - /lobby/ultiduo was returning 404 due to incorrect code normalisation
-
▸
Lobby detail page now detects Ultiduo format and renders 2 slots per team (Soldier + Medic) instead of 6
-
▸
Player pool counter shows /4 players and fill bar scales to 4 correctly
-
▸
RED/BLU team slot badges show 0/2 for Ultiduo
-
▸
"How it works" panel updated with Ultiduo-specific instructions
-
▸
Admin fill-bots capped at 4 for Ultiduo lobbies (backend + UI)
-
▸
Lobby cards now show a format badge: purple ⚡ULTIDUO or blue 6S
-
▸
Lobby header shows ULTIDUO 2v2 instead of 6v6 SIXES for Ultiduo
-
▸
Page title uses lobby title for better SEO
-
▸
Version bump: v0.082 → v0.083
-
▸
Added the evergreen Ultiduo lobby at /lobby/ultiduo (2v2 Soldier+Medic)
-
▸
Uses the UGC Ultiduo config preset and Ultiduo map pool
-
▸
Version bump: v0.081 → v0.082
-
▸
Added a dedicated /api page documenting the public TFCL Play endpoints
-
▸
Linked the API docs page in the global footer
-
▸
Bumped site version badge to v0.081