Skip to main content

Lucky Draw

Overview

A Lucky Draw is an elimination-style draw that supports any participant count, not just powers of 2. Unlike standard elimination draws that use byes to pad to a power-of-2 draw size, lucky draws create rounds with non-power-of-2 matchUp counts and use a "lucky loser" mechanism to balance rounds when an odd number of winners is produced.

In the factory, this draw type is represented by the constant LUCKY_DRAW.

info

When a lucky draw is generated with a power-of-2 participant count, it falls back to a standard elimination tree since no lucky loser mechanism is needed.

Structure

A lucky draw for 11 participants produces rounds with the following matchUp counts:

Round 1: 6 matchUps (12 participants, but only 11 available -- 1 gets a bye equivalent)
Round 2: 3 matchUps
Round 3: 2 matchUps
Round 4: 1 matchUp (Final)

Key structural differences from standard elimination:

  • No connecting lines between rounds. Unlike elimination draws where specific matchUp winners feed into specific next-round matchUps, lucky draws do not have fixed advancement paths between rounds.
  • Non-power-of-2 matchUp counts. Rounds can have odd numbers of matchUps (e.g., 3, 5, 7).
  • Pre-feed rounds. When a round has an odd number of matchUps, it produces an odd number of winners. This is one too many for a standard halving to work, so one loser from that round is selected to advance -- the "lucky loser."

Example: drawSize 25

The schematic below shows a 25-participant lucky draw structure. Pre-feed rounds (where a lucky loser is selected) are highlighted — these are rounds with an odd number of matchUps where the tournament director must select one loser to advance.

RoundParticipantsMatchUpsPre-feed?Lucky loser advances?
Round 126 (25 + 1 BYE)13YesYes -- 1 of 13 losers
Round 2147YesYes -- 1 of 7 losers
Round 384NoNo
Round 442NoNo
Final21----

Pre-Feed Rounds and Lucky Loser Selection

A pre-feed round is a round with an odd number of matchUps (and thus an odd number of winners). After a pre-feed round completes, the next round needs one additional participant to fill its draw positions. That participant is selected from the losers of the pre-feed round.

Selection Criteria

The system presents the tournament director with margin-of-defeat data at multiple levels of granularity. The tournament director uses whichever level provides sufficient differentiation:

  • Set ratio -- often enough to distinguish losers (e.g., a 3-set loss vs a straight-set loss).
  • Game ratio -- useful when set ratios are tied. Computed from side1Score/side2Score.
  • Point ratio -- the most granular level, from side1PointScore/side2PointScore. Only needed when higher-level ratios don't differentiate.

When no statistical differentiation exists at any level, the tournament director makes the selection at their discretion (e.g., by coin flip).

The system ranks eligible losers by margin but does not auto-select; the tournament director always makes the final decision. Participants who lost by walkover or default have no meaningful margin and are excluded from the ranked list.

Per-Round vs Cumulative Margin

By default, margin is calculated based only on the matchUp in which the participant lost. The cumulativeMargin option considers all prior rounds' matchUps for each participant, which can be useful in formats where consistency across rounds should factor into the selection.

The getLuckyDrawRoundStatus method accepts a cumulativeMargin boolean parameter to toggle between these modes.

API

Generating a Lucky Draw

const { drawDefinition } = engine.generateDrawDefinition({
drawSize: 11,
drawType: 'LUCKY_DRAW',
});

Checking Round Status

Use getLuckyDrawRoundStatus to determine which rounds need a lucky loser selection and to get the ranked list of eligible losers:

const { rounds } = engine.getLuckyDrawRoundStatus({ drawId });
const preFeedRound = rounds.find((r) => r.needsLuckySelection);
// preFeedRound.eligibleLosers is sorted by margin (narrowest loss first)

The returned rounds array contains objects with the following properties:

PropertyTypeDescription
roundNumbernumberThe round number
matchUpsCountnumberTotal matchUps in this round
completedCountnumberNumber of completed matchUps
isCompletebooleanWhether all matchUps in the round are complete
isPreFeedRoundbooleanWhether this round has an odd number of matchUps (not final round)
needsLuckySelectionbooleanWhether this round is complete, is a pre-feed round, and the next round has an open position
nextRoundHasOpenPositionbooleanWhether the subsequent round has an unfilled draw position
eligibleLosersarrayRanked list of losers (only present when needsLuckySelection is true)

Each entry in eligibleLosers contains:

PropertyTypeDescription
participantIdstringThe losing participant's ID
participantNamestringThe losing participant's name
matchUpIdstringThe matchUp in which they lost
scoreStringstringThe score of the matchUp
marginnumberMargin of defeat (0-1, higher = closer match; excluded for walkovers/defaults)
gameDifferentialnumberDifference in games won between winner and loser
setsWonByLosernumberNumber of sets won by the losing participant

Advancing a Lucky Loser

After reviewing the eligible losers, advance the selected participant:

engine.luckyDrawAdvancement({
drawId,
participantId: preFeedRound.eligibleLosers[0].participantId,
roundNumber: preFeedRound.roundNumber,
});

The luckyDrawAdvancement method:

  • Validates that the draw is a lucky draw.
  • Verifies the specified round is a pre-feed round that needs selection.
  • Confirms the participant is an eligible loser from that round.
  • Places the participant into the open draw position in the next round.

An optional selectionBasis parameter ('MARGIN', 'RANDOM', or 'MANUAL') can be provided for telemetry purposes.

Calculating Match Margin

The calculateMatchUpMargin method returns detailed margin data for any matchUp:

const {
margin, // Combined 0-1 value (undefined for walkovers/defaults)
setRatio, // Loser sets / total sets decided
gameRatio, // Loser games / total games (undefined if no game data)
pointRatio, // Loser points / total points (undefined if no point data)
gameDifferential, // Winner games - loser games
setsWonByLoser,
setsWonByWinner,
} = engine.calculateMatchUpMargin({ matchUpId });

Complete Workflow Example

// 1. Generate a lucky draw for 11 participants
engine.generateDrawDefinition({
drawSize: 11,
drawType: 'LUCKY_DRAW',
eventId,
drawName: 'Main Draw',
});

// 2. After Round 1 completes (6 matchUps, odd count = pre-feed round),
// check which round needs a lucky selection
const { rounds } = engine.getLuckyDrawRoundStatus({ drawId });
const preFeedRound = rounds.find((r) => r.needsLuckySelection);

if (preFeedRound) {
console.log('Eligible losers (ranked by margin):');
for (const loser of preFeedRound.eligibleLosers) {
console.log(` ${loser.participantName}: margin=${loser.margin}, score=${loser.scoreString}`);
}

// 3. Tournament director selects a lucky loser (here, the one with narrowest margin)
const selected = preFeedRound.eligibleLosers[0];
engine.luckyDrawAdvancement({
drawId,
participantId: selected.participantId,
roundNumber: preFeedRound.roundNumber,
});
}