Skip to main content

Draw Links

Links define the flow of participants between structures and determine finishing positions for ranking point calculations. Link definitions are participant-type independent — the same WINNER, LOSER, and POSITION propagation rules apply whether the participants flowing through the structures are individuals, doubles pairs, or teams. Each link specifies:

Try It: Topology Builder​

Use the interactive builder below to create structures and connect them with links. Click "+ Add Structure" to add draw components, then drag from output ports (green = winner, red = loser) to input ports on other cards to create links.

type Link = {
linkType: 'LOSER' | 'WINNER' | 'POSITION'; // Type of participant flow
source: {
structureId: string; // Source structure UUID
roundNumber?: number; // the round whose winners/losers cross: required out of an elimination structure (0 = placeholder)
finishingPositions?: number[]; // the group places that cross: directs every link out of a round robin
qualifyingPositions?: number; // Qualifier count for placeholder links
};
target: {
structureId: string; // Destination structure UUID
roundNumber?: number; // Optional: target round
feedProfile?: string; // Feed pattern (e.g., 'DRAW')
};
};

LOSER Links - Direct losing participants:

{
linkType: 'LOSER',
source: {
structureId: 'main-draw-id',
roundNumber: 1 // Round 1 losers
},
target: {
structureId: 'consolation-id',
roundNumber: 1,
feedProfile: 'DRAW' // Positioned by draw
}
}

WINNER Links - Direct winning participants. Out of an elimination structure, the winners of a round:

{
linkType: 'WINNER',
source: {
structureId: 'qualifying-structure-id',
roundNumber: 2 // the qualifying round whose winners are the qualifiers
},
target: {
structureId: 'main-draw-id',
roundNumber: 1,
feedProfile: 'DRAW'
}
}

Out of a round robin, the group finishers named by finishingPositions (see Rounds and finishing positions):

{
linkType: 'WINNER',
source: {
structureId: 'round-robin-container',
finishingPositions: [1] // Group winners
},
target: {
structureId: 'playoff-structure',
roundNumber: 1
}
}

POSITION Links - Feed based on finishing position:

{
linkType: 'POSITION',
source: {
structureId: 'qualifying-structure-a',
finishingPositions: [1, 2, 3, 4] // Top 4 finishers
},
target: {
structureId: 'main-draw-id',
roundNumber: 1,
feedProfile: 'DRAW'
}
}

A link says WHO CROSSES it from its source structure in one of two ways, and the source structure decides which:

source structurethe link is directed bylabelledwhy
round robin (a CONTAINER of groups)source.finishingPositionsPOSITION or WINNERa round robin decides who leaves it by tally, once its groups are complete: [1] is every group's winner, [2] every runner-up, [1, 2] the top two
eliminationsource.roundNumber (required)WINNER or LOSERan elimination structure decides who leaves it match by match: the link names the round whose winners (or losers) cross it
  • Both labels are valid out of a round robin, permanently. The factory writes POSITION; records converted from classic TMX, earlier factory versions and this page's examples use WINNER. Every engine reader decides by the source structure, so the two behave identically: qualifier progression, the qualifier position action, playoff positioning, finishing positions and the integrity scan all read finishingPositions.
  • A roundNumber on a round robin link is allowed and decides nothing. The factory records the groups' final round on a round robin qualifying link (roundNumber: 3 for groups of four) and none on a playoff link.
  • What is malformed (LINK_MISSING_SOURCE_ROUND): a link that must name a round and does not: a WINNER or LOSER link out of an elimination structure with no source.roundNumber, or a WINNER link out of a round robin that names no finishingPositions either. A draw holding one refuses every outcome until it is repaired with repairMalformedLinks, which never touches a round robin link that names finishingPositions.
  • A LOSER link out of a round robin is not a finishing-position link: round robin matchUps are never directed by links.

Placeholder Qualifying Links - Reserve qualifier positions before qualifying structures are generated:

{
linkType: 'POSITION',
source: {
structureId: 'placeholder-qualifying-id',
roundNumber: 0, // 0 = placeholder (no matchUps yet)
qualifyingPositions: 4 // Reserve 4 qualifier positions in MAIN
},
target: {
structureId: 'main-draw-id',
roundNumber: 1,
feedProfile: 'DRAW'
}
}

When roundNumber: 0 and qualifyingPositions is set, the MAIN structure reserves that many positions as qualifier placeholders during automated positioning. When the actual qualifying structure is later generated, the placeholder is replaced with a real link.

Feed Profiles​

DRAW - Position assignments determined by draw/seeding TOP_DOWN - Fill positions sequentially from top BOTTOM_UP - Fill positions sequentially from bottom WATERFALL - Distribute across available positions

A Compass draw (8 structures) uses multiple links:

drawDefinition.links = [
// Main draw losers to compass points
{
linkType: 'LOSER',
source: { structureId: 'main', roundNumber: 1 },
target: { structureId: 'east', roundNumber: 1 },
},
{
linkType: 'LOSER',
source: { structureId: 'main', roundNumber: 2 },
target: { structureId: 'west', roundNumber: 1 },
},

// East losers to southeast
{
linkType: 'LOSER',
source: { structureId: 'east', roundNumber: 1 },
target: { structureId: 'southeast', roundNumber: 1 },
},

// West losers to southwest
{
linkType: 'LOSER',
source: { structureId: 'west', roundNumber: 1 },
target: { structureId: 'southwest', roundNumber: 1 },
},

// ... more links for all 8 structures
];

Creating Custom Linked Structures​

Generate the main draw first, attach it to the event, then add a linked consolation. The convenience mutation generates the consolation and its LOSER links together; it does not require assembling UUIDs or calling a separate engine structure generator.

const { drawDefinition } = tournamentEngine.generateDrawDefinition({
eventId,
drawSize: 16,
drawType: 'SINGLE_ELIMINATION',
automated: false,
});
if (!drawDefinition) throw new Error('Draw generation failed');
const { drawId } = drawDefinition;
tournamentEngine.addDrawDefinition({ eventId, drawDefinition });
const result = tournamentEngine.addLinkedConsolationStructure({
drawId,
structureName: 'Consolation',
structureType: 'SINGLE_ELIMINATION',
drawSize: 8,
links: [{ sourceRoundNumber: 1, targetRoundNumber: 1 }],
});
if (result.error) throw new Error(result.error.message);

For more specialized topology, use attachConsolationStructures with generated structures and explicit links. Validate the resulting draw with getDrawInconsistencies before accepting play.

Qualifying Conceptual Model​

Traditional View (Incorrect in CODES)​

Main Draw (separate entity)
Qualifying Draw (separate entity)

CODES View (Correct)​

Draw:
├─ QUALIFYING Stage
│ ├─ Qualifying Structure A
│ ├─ Qualifying Structure B
│ └─ Qualifying Structure C
└─ MAIN Stage
└─ Main Structure (receives qualifiers via links)

Key Points​

  1. Qualifying is a stage, not a separate draw
  2. Multiple qualifying structures can exist in the same QUALIFYING stage
  3. Different qualifying structures can feed into different rounds of the main draw
  4. Fed participants at each round can come from different qualifying structures
  5. Links define the flow from qualifying structures to main draw positions

Real-World Example: ITF Tournament​

// 64-player main draw with 16 qualifiers
const { drawDefinition } = tournamentEngine.generateDrawDefinition({
drawSize: 64,
drawType: 'SINGLE_ELIMINATION',
qualifyingProfiles: [
{
roundTarget: 1,
structureProfiles: [
// 64 players compete for 16 qualifying spots
{ drawSize: 64, seedsCount: 16, qualifyingPositions: 16 },
],
},
],
});

// Results in:
// QUALIFYING Stage:
// - One 64-player structure producing 16 qualifiers
// MAIN Stage:
// - 64-player main draw with 16 qualifier positions in Round 1

Advanced Example: Progressive Qualifying​

Some tournaments use progressive qualifying where different levels feed into different rounds:

API Reference: generateDrawDefinition

const { drawDefinition } = tournamentEngine.generateDrawDefinition({
drawSize: 128,
qualifyingProfiles: [
{
roundTarget: 1,
structureProfiles: [
{ drawSize: 128, qualifyingPositions: 16 }, // Main qualifying
],
},
{
roundTarget: 2,
structureProfiles: [
{ drawSize: 32, qualifyingPositions: 4 }, // Lucky loser qualifying
],
},
],
});

// Creates:
// - 128-player qualifying for 16 spots in Main Round 1
// - 32-player lucky loser qualifying for 4 spots in Main Round 2

Custom Playoff Topologies with withPlayoffs​

The withPlayoffs parameter on generateDrawDefinition supports recursive nesting via its roundPlayoffs field. This enables building arbitrary COMPASS-like topologies — or any custom tree of playoff structures — in a single call, without manually chaining addPlayoffStructures.

How Recursive Playoffs Work​

Each level of withPlayoffs creates PLAY_OFF structures from the specified source rounds via LOSER links. The roundPlayoffs field maps a source round number to a child WithPlayoffsArgs, creating sub-playoffs from that child structure's losers.

withPlayoffs tree: Resulting structures:

East (MAIN) East (MAIN, drawSize 32)
├── R1 losers → West ├── West (PLAY_OFF, 16)
│ ├── R1 losers → South │ ├── South (PLAY_OFF, 8)
│ │ └── R1 losers → Southeast │ │ └── Southeast (PLAY_OFF, 4)
│ └── R2 losers → Southwest │ └── Southwest (PLAY_OFF, 4)
├── R2 losers → North ├── North (PLAY_OFF, 8)
│ └── R1 losers → Northwest │ └── Northwest (PLAY_OFF, 4)
└── R3 losers → Northeast └── Northeast (PLAY_OFF, 4)

Full COMPASS Example​

const { drawDefinition } = engine.generateDrawDefinition({
drawSize: 32,
drawName: 'East',
withPlayoffs: {
roundProfiles: [{ 1: 1 }, { 2: 1 }, { 3: 1 }],
playoffAttributes: {
'0-1': { name: 'West', abbreviation: 'W' },
'0-2': { name: 'North', abbreviation: 'N' },
'0-3': { name: 'Northeast', abbreviation: 'NE' },
},
roundPlayoffs: {
1: {
roundProfiles: [{ 1: 1 }, { 2: 1 }],
playoffAttributes: {
'0-1': { name: 'South', abbreviation: 'S' },
'0-2': { name: 'Southwest', abbreviation: 'SW' },
},
roundPlayoffs: {
1: {
roundProfiles: [{ 1: 1 }],
playoffAttributes: {
'0-1': { name: 'Southeast', abbreviation: 'SE' },
},
},
},
},
2: {
roundProfiles: [{ 1: 1 }],
playoffAttributes: {
'0-1': { name: 'Northwest', abbreviation: 'NW' },
},
},
},
},
});

This produces the same 8-structure, 7-link, 72-matchUp topology as the built-in COMPASS draw type, but with full control over structure naming, depth, and which branches exist.

Partial Topologies​

You can omit branches to create partial COMPASS draws. For example, a 6-structure variant (no Southwest or Southeast) simply omits those roundPlayoffs entries:

withPlayoffs: {
roundProfiles: [{ 1: 1 }, { 2: 1 }, { 3: 1 }],
roundPlayoffs: {
1: { roundProfiles: [{ 1: 1 }] }, // West → South only
2: { roundProfiles: [{ 1: 1 }] }, // North → Northwest only
},
}

Using with mocksEngine​

Recursive withPlayoffs works directly in drawProfiles for both mocksEngine.generateTournamentRecord() patterns:

// Via top-level drawProfiles
const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 32,
drawName: 'East',
withPlayoffs: {/* nested roundPlayoffs tree */},
},
],
});

// Via eventProfiles
const { tournamentRecord } = mocksEngine.generateTournamentRecord({
eventProfiles: [
{
drawProfiles: [
{
drawSize: 32,
withPlayoffs: {/* nested roundPlayoffs tree */},
},
],
},
],
});

See withPlayoffs API reference for the full WithPlayoffsArgs type and more examples.

note

Additional Playoff Structures

  • getAvailablePlayoffProfiles() - Valid attributes for playoff structures generation
  • generateAndPopulatePlayoffStructures() - Generates playoff structures
  • attachPlayoffStructures() - Attaches playoff structures to target drawDefinition
  • addPlayoffStructures() - Combines generation and attachment of playoff structures

Voluntary Consolation Structure

  • getEligibleVoluntaryConsolationParticipants() - Configurable method for determining eligibility
  • generateVoluntaryConsolation() - Generates matchUps for consolation structure

Feed-In Configuration