Skip to content

bug: leaderboard sources, tournament winner reassignment, no-show penalties - #388

Merged
lukepolo merged 2 commits into
mainfrom
fork-triage-bugs
Aug 12, 2026
Merged

bug: leaderboard sources, tournament winner reassignment, no-show penalties#388
lukepolo merged 2 commits into
mainfrom
fork-triage-bugs

Conversation

@lukepolo

@lukepolo lukepolo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Rebased onto main now that #387 has merged. The veto and match-options work from #387 is not duplicated here; the changes below sit on top of it.

Leaderboard

The board had a HAVING SUM(rounds_played) >= 50 floor on it, which meant a new player was invisible until their fiftieth round. Removed.

Alongside that, leaderboards can now be scoped to a source — matchmaking, tournament, league, or the combined overall — rather than only ever showing the total. Every leaderboard function takes _source TEXT DEFAULT 'overall', so existing callers are unaffected, and two classifier helpers decide which bucket a given match or tournament falls into.

Two correctness fixes came out of writing tests for it:

  • Rolling window standings flattened mid-window elo resets. starting_elo was taken from the first in-window row, so a reset that happened inside the window was invisible — the player's delta was computed against the wrong baseline.
  • Peak elo now tracks a tournament-excluded peak alongside the overall one, so a tournament run does not permanently distort a player's matchmaking peak.

Also surfaces player_custom_avatar_url on every producer.

Note for review: the file has a DO block that drops and recreates these functions by name and arity. Adding the _source argument changed the arity, so both the name list and the pronargs check had to move in step — otherwise the block drops the very functions the file just created.

Tournaments

can_reassign_winner looked the bracket up backwards. It walked from bracket to match instead of match to bracket, so it refused reassignment on brackets that were perfectly valid. Now resolves the parent and loser-parent brackets from the match, and a match with no bracket row is treated as reassignable.

Roster swaps tripped the minimum-player guard. Refreshing a tournament match lineup deleted the old rows before inserting the new ones, which briefly dropped the lineup below the minimum and fired the delete guard. refresh_tournament_match_lineup_teams now does it in place — reuse the existing rows, insert any surplus, and only delete surplus rows last.

Match cancellation and no-shows

This is the bigger behavioural change, so it is worth stating the rule plainly:

If you were connected when the match was cancelled, you are not penalised. If you were not, you are.

Per player, not per team. Disconnects after the match goes live are already handled elsewhere — this is strictly about a match that never got going.

Three parts:

  1. Force start. If all expected players are connected, the match force-starts one minute before the cancellation deadline. Without the lead time a fully-populated lobby could get cancelled in the window between the last player readying and the job running.
  2. Connection state. The lineup now tracks whether each player is connected, set and cleared on the connect/disconnect events, so at cancellation time the job knows who was actually there.
  3. Penalties. Only the not-connected players are recorded as no-shows.

The cancel job also carries a 180-minute hung-match net, which is a different thing entirely — isAwaitingWarmup() distinguishes the two so the force-start path does not fire on a hung match.

Separately, the leaver elo penalty is now applied at change finalisation for abandoned matches, reading leaver_elo_penalty (default 150) from settings.

Everything else

  • Best-of-three veto with a five-map pool used Ban, Pick, Pick, Ban. The documented order is two bans, then the picks. Fixed to Ban, Ban, Pick, Pick — the docs were right and the code was wrong.
  • Team rank averages counted coaches. Now excluded. Added wingman and duel averages.
  • New computed fields: banned_until, is_admin_sanctioned, is_registered.
  • Player search indexes registration state instead of filtering unregistered players out of the index entirely, so the UI can show them and badge them rather than pretending they do not exist.
  • Forfeit on a live ban fired whenever any player was banned. It now only forfeits when no unbanned players remain.
  • demo_processing_started_at is stamped when a map moves to WaitingForTV.

Tests

  • tournament-winner-reassignment.spec.ts — new, 4 tests
  • CancelExpiredMatches.spec.ts — 15 tests covering force-start, no-show recording, and the hung-match path
  • views.spec.ts — +357 lines
  • elo.spec.ts — abandoned-match penalty

Each fix was verified by reverting it and confirming the corresponding test fails.

Migrations

Four, numbered 1876000000400 through 1876000000700.

Base automatically changed from bug/veto-orphan-trpoies to main August 12, 2026 14:39
…alties

Leaderboard
- Drop the 50 round floor that hid every new player from the board
- Add a source filter so a board can be scoped to matchmaking, scrims or
  tournaments instead of only the combined total
- Fix rolling window standings so a mid window elo reset is reflected
  rather than being flattened by the first in window row
- Track peak elo excluding tournaments alongside the overall peak
- Surface the player custom avatar on every leaderboard producer

Tournaments
- can_reassign_winner looked the bracket up backwards and refused
  reassignment on brackets that were perfectly valid. It now resolves the
  parent and loser parent brackets from the match and treats a match with
  no bracket row as reassignable
- Add refresh_tournament_match_lineup_teams so a roster swap reuses the
  existing lineup rows and only deletes surplus rows last, which keeps the
  swap from tripping the minimum player delete guard

Matches
- Cancel expired matches now force starts a minute before the deadline
  when every expected player is connected, so a lobby that is ready is not
  cancelled out from under it
- When a match is cancelled without starting, penalise only the players who
  were not connected at cancellation rather than the whole lineup
- Track connection state on the lineup so the cancel job knows who was
  actually present
- Apply the leaver elo penalty at change finalisation for abandoned matches
- Stamp demo_processing_started_at when a map moves to WaitingForTV

Other
- Fix the best of three veto pattern for a five map pool so the two extra
  bans happen before the picks
- Exclude coaches from team rank averages and add wingman and duel averages
- Add banned_until, is_admin_sanctioned and is_registered computed fields
- Index registration state into player search instead of filtering
  unregistered players out entirely
- Only forfeit on a live ban when no unbanned players remain
@lukepolo
lukepolo merged commit 9dbc5c7 into main Aug 12, 2026
2 checks passed
@lukepolo
lukepolo deleted the fork-triage-bugs branch August 12, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant