Pickleball League Manager
Rotation Varieties
How each rotation mode calculates court assignments, player selection, and team pairings.

Overview

Every rotation — regardless of mode — passes through three stages:

1
Player Selection — decide who plays and who sits this round.
2
Court Assignment — group the selected players into courts of 4.
3
Team Pairing — within each court of 4, decide Team A vs Team B.

The rotation mode controls when these stages run and which selection algorithm is used. The shuffle strength setting only affects stage 2 and only in Per Round mode.

PER ROUND
Trigger: all courts done

All courts rotate together. Nobody gets a new game until every active court has submitted a result.

PER COURT
Trigger: single court done

Each court rotates independently the moment its result is confirmed. No waiting for other courts.

TIME-BASED
Trigger: all courts done

Like Per Round, but player selection prioritizes equal game time over sit fairness.

Per Round

All courts rotate simultaneously. Nobody gets a new game until every active court has submitted a result. The admin sees a ROTATE ALL COURTS button (or auto-rotate fires if enabled).

When a round ends

1
All court results are confirmed.
2
_assign_round() runs once for the entire session.
3
New court assignments replace all courts at the same time.
4
The queue is rebuilt from whoever didn't make it onto a court.
Selection: Sit-fairness priority score  |  Assignment: Elo banding with shuffle strength

Per Court

Each court rotates independently the moment its result is confirmed. There is no global rotate — each court shows CONFIRM & ROTATE individually. Ideal for courts that finish at different speeds.

When a single court confirms a result

1
The 4 players who just played (winners + losers) are pooled with all active players in the queue (excluding anyone sitting out).
2
The combined pool is sorted by sit-fairness priority score, highest first.
3
The top 4 from the pool stay on the court for the next game.
4
Everyone else goes back into the queue.
5
The 4 staying players go through the pairing optimizer to form two new teams.
Selection: Sit-fairness priority score  |  Assignment: Priority sort top-4 (Elo banding is skipped)

Time-Based

Structurally identical to Per Round (all courts rotate together), but player selection prioritizes equal game time rather than sit fairness. Best for ensuring nobody is left out disproportionately.

Selection: Equal-time score (games played)  |  Assignment: Random shuffle — Elo banding and shuffle strength are ignored entirely.

The random assignment in this mode is intentional: when the goal is equal time, skill-balancing court assignments would work against variety and the fairness goal.

Player Selection

The first job of every rotation is deciding who plays. The number of playing spots is always a multiple of 4 (full courts only):

n_play = floor( min(courts × 4, active_players) / 4 ) × 4

Players marked as sitting out are excluded from both the count and selection entirely.

Sit-Fairness Score

Used by Per Round and Per Court. Players with higher scores get picked to play first.

score = 100 × consecutive_sits + 20 × total_sits - 10 × session_games + 15 (if consecutive_sits > 0, i.e. currently sitting) + 5 (if session_games < average session games across present players) - 5 (if player has 3 or more recent results recorded)
consecutive_sits × 100Rounds sat in a row without playing. Dominates the score — someone who sat 2 rounds gets +200, almost guaranteeing they play next.
total_sits × 20Total sits this session. Prevents one player from always sitting while another never does.
session_games × −10Players who have played more are slightly penalized.
+15 if sittingExtra push for anyone actively in the sit queue.
+5 if below averageSmall bonus for players with fewer games than the group average.
−5 if 3 recent resultsSlight deprioritization for players on a continuous hot streak.

Consecutive-Sit Safety Swap

After the initial sort, any sitting player with consecutive_sits ≥ 2 triggers a forced swap:

1
Find all playing players who have consecutive_sits == 0 (have not sat recently).
2
The lowest-scoring player from that group is swapped out.
3
The long-sitting player takes their court spot.

This prevents anyone from sitting three or more rounds in a row regardless of score.

Time-Based Score

Used by Time-Based mode only. Equal game time is the sole priority.

score = -10 × session_games + 3 × novelty + jitter (random value between −0.5 and +0.5)
session_games × −10Heavily weighted. Players with fewer games get dramatically higher priority. This is the dominant term.
novelty × 3Number of present players this person has not recently played with or against. A small bonus for variety among players with equal game counts.
jitterTiny random nudge so ties don't always resolve the same way.

The same consecutive-sit safety swap applies. When choosing the swap candidate in this mode, the playing player with the most session games is swapped out (rather than the lowest priority score).

Court Assignment

After selection, the playing players are divided into groups of 4 — one group per court. The method depends on mode.

ModeMethod
Per RoundElo banding with shuffle strength
Per CourtPriority score sort — top 4 stay on the court, rest back to queue
Time-BasedRandom shuffle, then sliced into groups of 4

Shuffle Strength

Applies to Per Round mode only. Players are first sorted by adjusted seed (highest to lowest), then the strength setting controls how much that order is disrupted before slicing into courts.

0 — Pure Elo

No shuffle. Players are sliced in strict seed order: positions 1–4 go to court 1, 5–8 to court 2, and so on. The strongest players always play each other.

1–99 — Partial Shuffle

A controlled random displacement is applied to each player's position before slicing:

max_shift = max(1, floor(n_players × (strength / 100) × 0.5))

Each player is randomly swapped with another player within ±max_shift positions of their sorted rank. Higher strength = wider possible displacement.

Example — 16 players, strength 25:
max_shift = max(1, floor(16 × 0.25 × 0.5)) = 2
A player ranked 5th can end up anywhere between positions 3 and 7.

Example — 16 players, strength 75:
max_shift = max(1, floor(16 × 0.75 × 0.5)) = 6
A player ranked 5th can land anywhere between positions 1 and 11.

100 — Full Random

The sorted list is completely shuffled before slicing. Elo has no effect on which court a player ends up on.

Performance-Adjusted Seed

The sort key used in Elo banding is not raw Elo but a value adjusted for recent performance:

adjusted_seed = elo + 20 (won last game) - 20 (lost last game) + 10 (won 2 or more of last 3 games) - 10 (lost 2 or more of last 3 games)

A player on a winning streak ranks slightly higher than their Elo alone suggests; a player on a losing streak ranks slightly lower. The maximum effect is ±30 points — subtle enough not to override Elo significantly, but enough to keep hot and cold players in more appropriate courts between Elo updates.

Team Pairing Optimizer

Once 4 players are assigned to a court, they must be split into two teams. Given players A, B, C, D there are exactly three possible pairings:

[A,B] vs [C,D] [A,C] vs [B,D] [A,D] vs [B,C]

All three are evaluated and the one with the lowest total penalty is chosen.

Penalty Formula

penalty = balance + partner_penalty + opponent_penalty
Balance
Absolute difference in combined team Elo:
|elo(A) + elo(B) − elo(C) − elo(D)|

The primary driver of skill-balanced games. A perfectly balanced court scores 0 here.
Partner Penalty
Applied per team for repeated partnerships:
+40 — same pairing as the immediately previous game
+25 — same pairing as two games ago

A player can incur up to +40 points if they would be paired with the same partner they just played with. Applied for both teams.
Opponent Penalty
Applied for repeated opponents (all 4 cross-team combinations checked):
+15 — this opponent was faced in one of the last 2 games
+8 — this opponent was faced in games 3 or 4 games ago

Prevents the same two people from facing off every round.

A pairing with balanced teams and fresh partners and opponents scores near zero. A repeated, lopsided pairing can exceed 100 points and will never be chosen if any alternative exists.

Elo Ratings

After every confirmed game, Elo ratings update using the standard formula. Team Elo is the average of the two players' ratings.

expected = 1 / (1 + 10 ^ ((opponent_avg_elo − team_avg_elo) / 400)) elo_change = K × (actual − expected) where actual = 1 for a win, 0 for a loss

Each player on the winning team gains the same number of points; each player on the losing team loses the same number. Elo cannot fall below the configured floor (default 1000).

K-Factor (sensitivity by experience)

32
Fewer than 10 career games
24
10–29 career games
16
30+ career games

New players' ratings move quickly so they reach their correct level fast. Experienced players' ratings are more stable.

Player Metadata (Per Session)

The following values are tracked per player throughout a session and reset when a new session starts.

FieldDescription
consecutive_sitsRounds sat in a row without playing. Resets to 0 on any game played.
total_sitsTotal rounds sat this session.
session_gamesGames played this session. Used in both selection score formulas.
recent_partnersLast 2 partners. Used for partner penalty and novelty score.
recent_opponentsLast 4 opponents. Used for opponent penalty and novelty score.
recent_resultsLast 3 results (win/loss). Used for streak bonus in adjusted seed.

Quick Reference

Per RoundPer CourtTime-Based
Rotation triggerAll courts doneSingle court doneAll courts done
Selection algorithmSit-fairness scoreSit-fairness scoreEqual-time score
Court assignmentElo banding + shufflePriority sort top-4Random
Shuffle strength appliesYesNoNo
Pairing optimizerYesYesYes
Consecutive-sit swapYesYesYes