Seeding Policy
Overview
The Seeding Policy controls how seeds are positioned in draw structures, how many seeds are allowed based on draw size and participant count, and how seeds behave across different draw types. This policy is critical for ensuring fair tournament structures and is used by professional federations like USTA and ITF.
Reaching the built-in policies
The policy fixtures are reached through the fixtures namespace. There are no root-level
POLICY_* exports, and no default export:
import { drawDefinitionConstants, fixtures, policyConstants, tournamentEngine } from 'tods-competition-factory';
const { POLICY_SEEDING_DEFAULT, POLICY_SEEDING_ITF, POLICY_SEEDING_BYES } = fixtures.policies;
const { POLICY_TYPE_SEEDING } = policyConstants;
const { CLUSTER, SEPARATE, WATERFALL } = drawDefinitionConstants;
// These do NOT work — each resolves to `undefined`:
import { fixtures } from 'tods-competition-factory';
const { POLICY_SEEDING_ITF } = fixtures.policies; // ✗
import { policyConstants } from 'tods-competition-factory';
const { POLICY_TYPE_SEEDING } = policyConstants; // ✗
import { drawDefinitionConstants } from 'tods-competition-factory';
const { CLUSTER } = drawDefinitionConstants; // ✗
import { tournamentEngine } from 'tods-competition-factory'; // ✗ no default export
An undefined policy fails silently: policyDefinitions: undefined reads as "no policy
supplied", so generation falls back to its own defaults and nothing reports an error. A draw that
looks fine can be seeded by the wrong rules.
Policy Structure
{
seeding: {
policyName?: string;
seedingProfile?: {
positioning?: 'CLUSTER' | 'ADJACENT' | 'SEPARATE' | 'WATERFALL';
drawTypes?: {
[drawType: string]: {
positioning: 'CLUSTER' | 'ADJACENT' | 'SEPARATE' | 'WATERFALL';
};
};
groupSeedingThreshold?: number;
nonRandom?: boolean;
};
validSeedPositions?: {
ignore?: boolean;
strict?: boolean;
};
duplicateSeedNumbers?: boolean;
drawSizeProgression?: boolean;
containerByesIgnoreSeeding?: boolean;
seedsCountThresholds: Array<{
drawSize: number;
minimumParticipantCount: number;
seedsCount: number;
}>;
additionalSeeds?: {
maxCount: number;
bases?: Array<'ORGANISER_DISCRETION' | 'PROTECTED_RANKING' | 'RANKING' | 'RATING'>;
};
}
}
Attributes
policyName
Type: string (optional)
Purpose: Human-readable name for the policy
Provides a descriptive name for the seeding policy, useful for logging, debugging, and policy selection in administrative interfaces.
Example:
{
policyName: 'USTA SEEDING';
}
Notes:
- Used in built-in policies:
'USTA SEEDING','ITF SEEDING','SEED_BYES' - Purely informational - does not affect behavior
seedingProfile
Type: object (optional)
Purpose: Controls how seeds are positioned in draw structures
The seeding profile determines the positioning pattern for seeds throughout the draw. Different positioning strategies are used by different federations and for different draw types.
seedingProfile.positioning
Type: 'CLUSTER' | 'ADJACENT' | 'SEPARATE' | 'WATERFALL'
Default: 'SEPARATE'
Controls the default seed positioning pattern for elimination draws:
-
SEPARATE(USTA style): Seeds are separated to opposite ends of position groups- Top half: seeds placed at TOP of groups
- Bottom half: seeds placed at BOTTOM of groups
- Seeds 3-4 placed at positions to maximize separation
- Seeds 5-8 placed to maximize separation within quarters
-
CLUSTER(ITF style): Seeds alternate between top and bottom of groups- Creates clustered seed positions
- Seeds may be adjacent to each other
- Pattern alternates every other seed block
-
ADJACENT: Synonym forCLUSTER- Exactly the same as CLUSTER positioning
- Useful for clearer semantic meaning in some contexts
-
WATERFALL(Round Robin): Seeds distributed sequentially across groups- Seed 1 in Group 1, Seed 2 in Group 2, etc.
- Used primarily for Round Robin structures
- Ensures even distribution of strength
Examples:
// USTA/SEPARATE positioning (default)
{
seedingProfile: {
positioning: 'SEPARATE';
}
}
// ITF/CLUSTER positioning
{
seedingProfile: {
positioning: 'CLUSTER';
}
}
// ADJACENT positioning (same as CLUSTER)
{
seedingProfile: {
positioning: 'ADJACENT';
}
}
// WATERFALL positioning (for Round Robin)
{
seedingProfile: {
positioning: 'WATERFALL';
}
}
Visual Comparison (32 draw):
SEPARATE (USTA): CLUSTER/ADJACENT (ITF):
Seed 1: Position 1 Seed 1: Position 1
Seed 2: Position 32 Seed 2: Position 32
Seed 3: Position 9 Seed 3: Position 16
Seed 4: Position 24 Seed 4: Position 17
Seed 5: Position 5 Seed 5: Position 8
Seed 6: Position 28 Seed 6: Position 9
Seed 7: Position 13 Seed 7: Position 24
Seed 8: Position 20 Seed 8: Position 25
Notes:
- SEPARATE: Seeds never adjacent, maximizes separation
- CLUSTER/ADJACENT: Seeds may be adjacent, alternating pattern
- WATERFALL: Only for Round Robin structures
- Positioning affects competitive balance and viewer experience
seedingProfile.drawTypes
Type: object (optional)
Purpose: Override positioning for specific draw types
Allows different positioning strategies for different draw types within the same tournament.
Structure:
drawTypes: {
[drawType: string]: {
positioning: 'CLUSTER' | 'ADJACENT' | 'SEPARATE' | 'WATERFALL';
}
}
Example:
{
seedingProfile: {
positioning: 'SEPARATE', // Default for elimination
drawTypes: {
ROUND_ROBIN_WITH_PLAYOFF: { positioning: 'WATERFALL' },
ROUND_ROBIN: { positioning: 'WATERFALL' }
}
}
}
Common Use Cases:
// USTA style: SEPARATE for elimination, WATERFALL for Round Robin
import { drawDefinitionConstants } from 'tods-competition-factory';
const { ROUND_ROBIN, ROUND_ROBIN_WITH_PLAYOFF, SEPARATE, WATERFALL } = drawDefinitionConstants;
const seedingPolicy = {
seeding: {
seedingProfile: {
positioning: SEPARATE,
drawTypes: {
[ROUND_ROBIN_WITH_PLAYOFF]: { positioning: WATERFALL },
[ROUND_ROBIN]: { positioning: WATERFALL },
},
},
},
};
// ITF style: CLUSTER for all draw types
const itfSeeding = {
seeding: {
seedingProfile: {
positioning: 'CLUSTER',
},
},
};
// Mixed positioning
const mixedSeeding = {
seeding: {
seedingProfile: {
positioning: 'SEPARATE',
drawTypes: {
ROUND_ROBIN: { positioning: 'WATERFALL' },
FEED_IN_CHAMPIONSHIP: { positioning: 'CLUSTER' },
},
},
},
};
Notes:
- Draw type specific positioning overrides default positioning
- Round Robin draws typically use WATERFALL positioning
- Elimination draws typically use SEPARATE or CLUSTER
- If no override specified, default positioning is used
seedingProfile.groupSeedingThreshold
Type: number (optional)
Purpose: Controls seed value handling in Round Robin groups
When generating Round Robin draws, this threshold determines how seed values are processed for group seeding calculations.
Example:
{
seedingProfile: {
groupSeedingThreshold: 1000;
}
}
Notes:
- Advanced feature - rarely needed in standard tournament operations
- Used internally for Round Robin seed distribution calculations
- Leave undefined for default behavior
seedingProfile.nonRandom
Type: boolean (optional)
Default: false
Controls whether seed positioning uses deterministic (non-random) placement within seed blocks.
Example:
{
seedingProfile: {
positioning: 'CLUSTER',
nonRandom: true
}
}
Use Cases:
- Testing and validation (deterministic results)
- Qualification structures where positions must be predictable
- Audit requirements for reproducibility
Notes:
- When
false(default): Seeds randomized within their block - When
true: Seeds placed deterministically - Does not affect which positions are valid for seeds, only their assignment order
validSeedPositions
Type: object (optional)
Purpose: How much latitude an operator has to place a seed by hand
This does not decide where automated positioning puts seeds. That is
seedingProfile.positioning, and both the USTA and ITF policies specify it —
differently (SEPARATE vs CLUSTER). validSeedPositions is consulted only by
isValidSeedPosition, which is called when assigning a seeded participant to a drawPosition
(rejecting with INVALID_DRAW_POSITION_FOR_SEEDING) and when deciding whether the SEED_VALUE and
REMOVE_SEED position actions are offered at a position.
There are three states, not two:
| setting | a seed may be placed | used by |
|---|---|---|
{ ignore: true } | in any draw position | POLICY_SEEDING_DEFAULT (USTA), POLICY_SEEDING_ITF, POLICY_SEEDING_BYES |
| absent | in any position belonging to some valid seed block | the default |
{ strict: true } | only in positions of that seed's own block | — |
The middle row is the one that is easy to get wrong. With the key absent, seed 1 is not pinned to
position 1 — it may sit anywhere in the union of all seed-block positions. Only strict: true
confines a seed to its own block.
Examples:
import { fixtures } from 'tods-competition-factory';
const { POLICY_SEEDING_ITF, POLICY_SEEDING_DEFAULT } = fixtures.policies;
// Both carry validSeedPositions: { ignore: true } — hand placement anywhere.
// Confine hand placement to seed-block positions by OMITTING the key (see
// "Composing a variant" below — do not hand-copy a policy to drop one key).
// Confine each seed to its own block:
const pinned = { seeding: { validSeedPositions: { strict: true } } };
Use Cases:
// Pre-seeded tournament (manual placement)
const manualSeeding = {
seeding: {
validSeedPositions: { ignore: true },
seedingProfile: { positioning: 'SEPARATE' },
},
};
// Standard automated seeding (strict validation)
const autoSeeding = {
seeding: {
validSeedPositions: { ignore: false },
seedingProfile: { positioning: 'CLUSTER' },
},
};
Notes:
- Most professional federations use
ignore: truefor flexibility - Does not affect which seeds are assigned, nor where automated positioning places them — only whether a given hand placement is accepted, and whether the seed actions are offered
duplicateSeedNumbers
Type: boolean (optional)
Default: false
Allows multiple participants to share the same seed number.
When true:
- Multiple participants can have same seed value (e.g., three "Seed 5s")
- Useful when player rankings are tied
- Common in ITF and USTA tournaments
- Seeds still placed in appropriate seed blocks
When false:
- Each seed number must be unique
- Traditional seeding approach
Examples:
// Allow duplicate seeds (ITF/USTA standard)
{
duplicateSeedNumbers: true;
}
// Traditional unique seeds
{
duplicateSeedNumbers: false;
}
Real-World Scenario:
// ITF Junior Tournament - players with same ranking
const participants = [
{ participantId: '1', seedValue: 1 }, // Rank 1
{ participantId: '2', seedValue: 2 }, // Rank 2
{ participantId: '3', seedValue: 3 }, // Rank 3 (tied)
{ participantId: '4', seedValue: 3 }, // Rank 3 (tied)
{ participantId: '5', seedValue: 3 }, // Rank 3 (tied)
{ participantId: '6', seedValue: 6 }, // Rank 6
];
const policy = {
seeding: {
duplicateSeedNumbers: true,
seedingProfile: { positioning: 'CLUSTER' },
},
};
// All three participants with seedValue 3 are placed in seed block 3-4-5-6
Notes:
- Required for tournaments using rating-based seeding where ties occur
- Both POLICY_SEEDING_ITF and POLICY_SEEDING_DEFAULT set
true - Absent means
true, notfalse. The engine readstypeof duplicateSeedNumbers === 'boolean' ? duplicateSeedNumbers : true, so omitting the key is identical to setting ittrue; only an explicitfalserequires unique seed numbers. The built-ins set it explicitly to document intent, not to change behavior - Engine handles randomization of duplicate seeds within their blocks
drawSizeProgression
Type: boolean (optional)
Default: false
Automatically adjusts seeds count based on the minimum draw size that accommodates the participant count, rather than the actual participant count.
When true:
- Seeds count determined by next power-of-2 draw size
- Example: 25 participants → uses 32 draw size for seed calculation
- Results in more seeds for draws with many BYEs
When false:
- Seeds count determined by actual participant count
- More conservative seeding approach
Examples:
// Progressive seeding (more seeds with BYEs)
{
drawSizeProgression: true,
seedsCountThresholds: [
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 }
]
}
// Conservative seeding
{
drawSizeProgression: false,
seedsCountThresholds: [
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 }
]
}
Behavior Comparison:
// Scenario: 25 participants in draw
// seedsCountThresholds: { drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 }
// With drawSizeProgression: true
// → Draw size becomes 32 (next power of 2)
// → 25 >= 24 (threshold met for 32 draw)
// → 8 seeds assigned
// With drawSizeProgression: false
// → Uses 25 participants directly
// → 25 >= 24 (threshold met)
// → 8 seeds assigned
// Scenario: 23 participants in draw
// With drawSizeProgression: true
// → Draw size becomes 32
// → 23 < 24 (threshold NOT met for 32 draw)
// → Falls back to threshold for drawSize: 16 (if exists)
// With drawSizeProgression: false
// → Uses 23 participants directly
// → 23 < 24 (threshold NOT met)
// → Falls back to previous threshold
Notes:
- Used by both ITF and USTA policies (
true) - Affects tournaments with significant BYE count
- More seeds = better competitive balance but more complex draws
containerByesIgnoreSeeding
Type: boolean (optional)
Default: false
Controls whether BYEs in container structures (Round Robin with Playoff) respect seed positions.
When true:
- BYEs are placed randomly or by other criteria
- Seed positions do not influence BYE placement
- Used in POLICY_SEEDING_BYES
When false (default):
- BYEs are placed to avoid seeded positions where possible
- Protects seeds from BYE positions
- Standard behavior for most tournaments
Examples:
// BYEs ignore seeding (BYES policy)
{
containerByesIgnoreSeeding: true,
seedingProfile: { positioning: 'CLUSTER' }
}
// BYEs respect seeding (default)
{
containerByesIgnoreSeeding: false,
seedingProfile: { positioning: 'SEPARATE' }
}
Use Cases:
// Round Robin with Playoff - BYEs ignore seeds
import { drawDefinitionConstants } from 'tods-competition-factory';
const { ROUND_ROBIN_WITH_PLAYOFF } = drawDefinitionConstants;
const policy = {
seeding: {
containerByesIgnoreSeeding: true,
seedingProfile: {
positioning: 'SEPARATE',
drawTypes: {
[ROUND_ROBIN_WITH_PLAYOFF]: { positioning: 'WATERFALL' },
},
},
},
};
tournamentEngine.generateDrawDefinition({
drawType: ROUND_ROBIN_WITH_PLAYOFF,
policyDefinitions: policy,
drawSize: 16,
// ...
});
Notes:
- Only affects CONTAINER and ITEM structure types
- Primarily used in Round Robin with Playoff draws
- Standard elimination draws use different BYE placement logic
- POLICY_SEEDING_BYES is the only built-in policy using this
seedsCountThresholds
Type: Array<{ drawSize: number; minimumParticipantCount: number; seedsCount: number }>
Required: Yes
Defines how many seeds are allowed for each draw size based on participant count.
Structure:
seedsCountThresholds: [
{
drawSize: number; // Target draw size (power of 2)
minimumParticipantCount: number; // Minimum participants needed
seedsCount: number; // Number of seeds allowed
},
// ...
]
Logic:
- Engine determines draw size from participant count (next power of 2)
- Finds matching
drawSizein thresholds - Checks if participant count meets
minimumParticipantCount - If yes: allows
seedsCountseeds - If no: checks next lower threshold
Standard USTA Thresholds:
seedsCountThresholds: [
{ drawSize: 4, minimumParticipantCount: 3, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 48, seedsCount: 16 },
{ drawSize: 128, minimumParticipantCount: 96, seedsCount: 32 },
{ drawSize: 256, minimumParticipantCount: 192, seedsCount: 64 },
];
Standard ITF Thresholds:
seedsCountThresholds: [
{ drawSize: 4, minimumParticipantCount: 3, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 48, seedsCount: 16 },
{ drawSize: 128, minimumParticipantCount: 97, seedsCount: 32 }, // Lower threshold
{ drawSize: 256, minimumParticipantCount: 192, seedsCount: 64 },
];
Examples:
// Conservative seeding (more participants required)
const conservativePolicy = {
seeding: {
seedsCountThresholds: [
{ drawSize: 32, minimumParticipantCount: 28, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 56, seedsCount: 16 },
],
},
};
// Progressive seeding (fewer participants required)
const progressivePolicy = {
seeding: {
seedsCountThresholds: [
{ drawSize: 32, minimumParticipantCount: 20, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 40, seedsCount: 16 },
],
},
};
// Club tournament (simpler)
const clubPolicy = {
seeding: {
seedsCountThresholds: [
{ drawSize: 8, minimumParticipantCount: 6, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
],
},
};
How It Works:
// Scenario: 50 participants
// Engine calculates: drawSize = 64 (next power of 2)
// With USTA policy:
// Checks: drawSize: 64, minimumParticipantCount: 48
// 50 >= 48 ✓
// Result: 16 seeds allowed
// Scenario: 45 participants
// Engine calculates: drawSize = 64
// With USTA policy:
// Checks: drawSize: 64, minimumParticipantCount: 48
// 45 < 48 ✗
// Falls back to: drawSize: 32, minimumParticipantCount: 24
// 45 >= 24 ✓
// Result: 8 seeds allowed
Notes:
- Thresholds must be sorted by drawSize (ascending)
- Draw sizes must be powers of 2
- Engine automatically finds appropriate threshold
- If no threshold met, minimum seeding is used (typically 2 seeds)
additionalSeeds
Type: { maxCount: number; bases?: SeedingBasisUnion[] }
Default: none — no seeds above the threshold count
Seeds permitted above the count seedsCountThresholds yields, for bodies that award a seeding
to a player whose ranking does not reach the seed count on its own — most commonly a protected or
frozen ranking after a long absence.
The distinction from simply raising seedsCountThresholds is the whole point. An additional seed
is added alongside the existing seeds, so nobody is displaced from a seeding slot they earned.
A 128-draw with 32 seeds becomes one with 32 seeds plus one.
const protectedSeeding = policyComposer(POLICY_TYPE_SEEDING)
.extend(POLICY_SEEDING_ITF)
.set('policyName', 'PROTECTED RANKING SEEDING')
.set('additionalSeeds', { maxCount: 4, bases: ['PROTECTED_RANKING'] })
.build();
The effective ceiling becomes thresholdSeedsCount + maxCount. A seedsCount above the threshold
is honoured up to that ceiling with enforcePolicyLimits left at its default true, and
clamped above it. drawSize and the stage's entry count still cap the count either way.
bases narrows what may claim an additional seed and is reported by
getAdditionalSeedsAllowance so a client can build the right control. It is not a write-time
refusal — the count is the binding limit.
The allowance is 0 whenever no threshold matched: it is expressed relative to a count, so there is nothing for an additional seed to be additional to.
See Additional Seeds for seedingBasis, the
addAdditionalSeed mutation, and what the factory deliberately leaves to the caller.
Built-in Seeding Policies
The factory ships three pre-configured seeding policies. A federation whose rules differ from all three should compose a variant rather than hand-copy one — see Composing a variant.
Comparison Table
| Attribute | USTA (DEFAULT) | ITF | BYES |
|---|---|---|---|
| policyName | 'USTA SEEDING' | 'ITF SEEDING' | 'SEED_BYES' |
| positioning | SEPARATE | CLUSTER | CLUSTER |
| validSeedPositions.ignore | true | true | true |
| duplicateSeedNumbers | true | true | true |
| drawSizeProgression | true | true | true |
| containerByesIgnoreSeeding | (absent) | (absent) | true |
| drawTypes | WATERFALL for RR | (none) | (none) |
| 128-draw threshold | 96 participants | 97 participants | 97 participants |
All three seed to the same depth: drawSize / 4 at every threshold. That matters when a national
rule seeds more deeply — none of the three expresses it, which is what seedsCountThresholds and a
composed variant are for.
POLICY_SEEDING_DEFAULT (USTA)
Purpose: United States Tennis Association standard seeding
Positioning: SEPARATE (seeds maximally separated)
Use Case: USTA tournaments, US Open, most US-based tournaments
Full Policy:
import { fixtures } from 'tods-competition-factory';
const { POLICY_SEEDING_DEFAULT } = fixtures.policies;
// Policy structure:
{
seeding: {
policyName: 'USTA SEEDING',
seedingProfile: {
positioning: 'SEPARATE',
drawTypes: {
ROUND_ROBIN_WITH_PLAYOFF: { positioning: 'WATERFALL' },
ROUND_ROBIN: { positioning: 'WATERFALL' }
}
},
validSeedPositions: { ignore: true },
duplicateSeedNumbers: true,
drawSizeProgression: true,
seedsCountThresholds: [
{ drawSize: 4, minimumParticipantCount: 3, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 48, seedsCount: 16 },
{ drawSize: 128, minimumParticipantCount: 96, seedsCount: 32 },
{ drawSize: 256, minimumParticipantCount: 192, seedsCount: 64 }
]
}
}
Key Features:
- SEPARATE positioning for elimination draws
- WATERFALL positioning for Round Robin draws
- Requires 96 participants for 32 seeds in 128 draw
- Supports duplicate seed numbers (tied rankings)
POLICY_SEEDING_ITF
Purpose: International Tennis Federation standard seeding
Positioning: CLUSTER (seeds may be adjacent)
Use Case: ITF tournaments, international play, Davis Cup, Fed Cup
Full Policy:
import { fixtures } from 'tods-competition-factory';
const { POLICY_SEEDING_ITF } = fixtures.policies;
// Policy structure:
{
seeding: {
policyName: 'ITF SEEDING',
seedingProfile: { positioning: 'CLUSTER' },
validSeedPositions: { ignore: true },
duplicateSeedNumbers: true,
drawSizeProgression: true,
seedsCountThresholds: [
{ drawSize: 4, minimumParticipantCount: 3, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 48, seedsCount: 16 },
{ drawSize: 128, minimumParticipantCount: 97, seedsCount: 32 },
{ drawSize: 256, minimumParticipantCount: 192, seedsCount: 64 }
]
}
}
Key Features:
- CLUSTER positioning (alternating pattern)
- Only requires 97 participants for 32 seeds in 128 draw (vs USTA's 96)
- Supports duplicate seed numbers
Difference from USTA:
- Main difference is CLUSTER vs SEPARATE positioning
- Slightly lower threshold for 128 draw (97 vs 96)
- No draw type specific overrides
POLICY_SEEDING_BYES
Purpose: Seeding policy that ignores seed positions for BYE placement
Positioning: CLUSTER
Use Case: Tournaments where BYEs should be distributed independently of seeding
Full Policy:
import { fixtures } from 'tods-competition-factory';
const { POLICY_SEEDING_BYES } = fixtures.policies;
// Policy structure:
{
seeding: {
policyName: 'SEED_BYES',
seedingProfile: { positioning: 'CLUSTER' },
validSeedPositions: { ignore: true },
containerByesIgnoreSeeding: true, // KEY DIFFERENCE
duplicateSeedNumbers: true,
drawSizeProgression: true,
seedsCountThresholds: [
{ drawSize: 4, minimumParticipantCount: 3, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 48, seedsCount: 16 },
{ drawSize: 128, minimumParticipantCount: 97, seedsCount: 32 },
{ drawSize: 256, minimumParticipantCount: 192, seedsCount: 64 }
]
}
}
Key Features:
containerByesIgnoreSeeding: true- BYEs placed independently- CLUSTER positioning
- Primarily for Round Robin with Playoff structures
Use Case:
import { drawDefinitionConstants, fixtures } from 'tods-competition-factory';
const { ROUND_ROBIN_WITH_PLAYOFF } = drawDefinitionConstants;
const { POLICY_SEEDING_BYES } = fixtures.policies;
tournamentEngine.generateDrawDefinition({
drawType: ROUND_ROBIN_WITH_PLAYOFF,
policyDefinitions: POLICY_SEEDING_BYES,
participants: myParticipants,
drawSize: 16,
seedsCount: 4,
});
// BYEs will be distributed without regard to seed positions
Composing a variant
POLICY_SEEDING_NATIONAL used to sit here. It was removed in 7.x: it was POLICY_SEEDING_ITF with
two keys omitted, one of which (duplicateSeedNumbers) does nothing when omitted, so its entire
distinguishing content was a single absent key. It was never exported, so nothing could import it.
Express that — or any federation's rules — with policyComposer, which
is a root export:
import { fixtures, policyComposer, policyConstants } from 'tods-competition-factory';
const { POLICY_TYPE_SEEDING } = policyConstants;
const { POLICY_SEEDING_ITF } = fixtures.policies;
// ITF rules, but a seed may not be hand-placed outside a seed block.
const positionsEnforced = policyComposer(POLICY_TYPE_SEEDING)
.extend(POLICY_SEEDING_ITF)
.unset('validSeedPositions')
.set('policyName', 'ITF SEEDING, POSITIONS ENFORCED')
.build();
Seeding more deeply than the built-ins
The reason most federations need a variant is depth — how many seeds a draw of a given size
gets. All three built-ins stop at drawSize / 4:
// 16 seeds in a 32 draw, where the built-ins allow 8.
const deepSeeding = policyComposer(POLICY_TYPE_SEEDING)
.extend(POLICY_SEEDING_ITF)
.set('policyName', 'DEEP SEEDING')
.set('seedsCountThresholds', [
{ drawSize: 4, minimumParticipantCount: 3, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 8 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 16 },
])
.build();
Two things to know when a draw is generated with a deeper policy:
seedsCountThresholdsis a maximum, gated onminimumParticipantCount. A 32 draw with 20 entries gets the count from the highest threshold whose participant minimum is met.- Passing an explicit
seedsCountabove the policy's maximum is clamped back down unlessenforcePolicyLimits: falseis also passed.drawSizeand the stage's entry count still cap it either way. See generateDrawDefinition.
Deeper seeding and additional seeding are different things, and reaching for the wrong one is
easy. Raising seedsCountThresholds gives the draw more seeds — the 9th-ranked entrant becomes a
seed and takes a seeded position. additionalSeeds adds a seed beside the
existing ones so that no entrant loses a seeding they earned, which is what a protected-ranking
rule requires. Use the thresholds to change how deeply a draw seeds; use the allowance to let a
named few sit above that depth.
policyComposer is immutable — extend never mutates the fixture you pass it — so one base composer
can safely seed several federation variants. .register({ name, version }) builds and records the
result in policyRegistry in one step.
Usage Examples
Basic Usage
import { tournamentEngine } from 'tods-competition-factory';
// Using built-in USTA policy
import { fixtures } from 'tods-competition-factory';
const { POLICY_SEEDING_DEFAULT } = fixtures.policies;
tournamentEngine.generateDrawDefinition({
policyDefinitions: POLICY_SEEDING_DEFAULT,
drawSize: 32,
seedsCount: 8,
// ...other parameters
});
Custom Seeding Policy
import { drawDefinitionConstants, policyConstants } from 'tods-competition-factory';
const { SEPARATE } = drawDefinitionConstants;
const { POLICY_TYPE_SEEDING } = policyConstants;
const customSeedingPolicy = {
[POLICY_TYPE_SEEDING]: {
policyName: 'Custom Club Policy',
seedingProfile: {
positioning: SEPARATE,
},
duplicateSeedNumbers: false,
drawSizeProgression: true,
validSeedPositions: { ignore: false },
seedsCountThresholds: [
{ drawSize: 8, minimumParticipantCount: 6, seedsCount: 2 },
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
],
},
};
tournamentEngine.generateDrawDefinition({
policyDefinitions: customSeedingPolicy,
drawSize: 16,
seedsCount: 4,
// ...
});
Mixed Draw Type Seeding
import { drawDefinitionConstants, policyConstants } from 'tods-competition-factory';
const { ROUND_ROBIN, ROUND_ROBIN_WITH_PLAYOFF, SEPARATE, WATERFALL } = drawDefinitionConstants;
const { POLICY_TYPE_SEEDING } = policyConstants;
const mixedPolicy = {
[POLICY_TYPE_SEEDING]: {
seedingProfile: {
positioning: SEPARATE, // Default for elimination
drawTypes: {
[ROUND_ROBIN]: { positioning: WATERFALL },
[ROUND_ROBIN_WITH_PLAYOFF]: { positioning: WATERFALL },
},
},
duplicateSeedNumbers: true,
drawSizeProgression: true,
validSeedPositions: { ignore: true },
seedsCountThresholds: [
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
],
},
};
// Elimination draw uses SEPARATE
tournamentEngine.generateDrawDefinition({
drawType: 'SINGLE_ELIMINATION',
policyDefinitions: mixedPolicy,
drawSize: 32,
});
// Round Robin uses WATERFALL
tournamentEngine.generateDrawDefinition({
drawType: ROUND_ROBIN,
policyDefinitions: mixedPolicy,
drawSize: 16,
});
Using ADJACENT (Synonym for CLUSTER)
import { drawDefinitionConstants, policyConstants } from 'tods-competition-factory';
const { ADJACENT } = drawDefinitionConstants;
const { POLICY_TYPE_SEEDING } = policyConstants;
const adjacentSeeding = {
[POLICY_TYPE_SEEDING]: {
policyName: 'Adjacent Seeding',
seedingProfile: {
positioning: ADJACENT, // Same as CLUSTER
},
duplicateSeedNumbers: true,
validSeedPositions: { ignore: true },
drawSizeProgression: true,
seedsCountThresholds: [{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 }],
},
};
tournamentEngine.generateDrawDefinition({
policyDefinitions: adjacentSeeding,
drawSize: 32,
});
Dynamic Seeds Count Based on Participants
// Automatic seeds count determination
const participants = getMyParticipants(); // 28 participants
tournamentEngine.generateDrawDefinition({
policyDefinitions: POLICY_SEEDING_DEFAULT,
participants,
// seedsCount automatically calculated:
// 28 participants → 32 draw → meets threshold (24) → 8 seeds
});
// Override automatic calculation
tournamentEngine.generateDrawDefinition({
policyDefinitions: POLICY_SEEDING_DEFAULT,
participants,
seedsCount: 4, // Manual override
});
Qualification Draw Seeding
import { drawDefinitionConstants, policyConstants } from 'tods-competition-factory';
const { CLUSTER, QUALIFYING } = drawDefinitionConstants;
const { POLICY_TYPE_SEEDING } = policyConstants;
const qualifyingSeeding = {
[POLICY_TYPE_SEEDING]: {
seedingProfile: {
positioning: CLUSTER,
nonRandom: true, // Deterministic for qualifying
},
validSeedPositions: { ignore: true },
drawSizeProgression: true,
seedsCountThresholds: [
{ drawSize: 16, minimumParticipantCount: 12, seedsCount: 4 },
{ drawSize: 32, minimumParticipantCount: 24, seedsCount: 8 },
],
},
};
tournamentEngine.generateDrawDefinition({
stage: QUALIFYING,
policyDefinitions: qualifyingSeeding,
drawSize: 16,
seedsCount: 4,
});
Progressive vs Conservative Seeding
// Progressive: More seeds with fewer participants
const progressivePolicy = {
[POLICY_TYPE_SEEDING]: {
drawSizeProgression: true, // Key setting
seedsCountThresholds: [
{ drawSize: 32, minimumParticipantCount: 20, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 40, seedsCount: 16 },
],
},
};
// Conservative: Require more participants for seeds
const conservativePolicy = {
[POLICY_TYPE_SEEDING]: {
drawSizeProgression: false,
seedsCountThresholds: [
{ drawSize: 32, minimumParticipantCount: 28, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 56, seedsCount: 16 },
],
},
};
// 25 participants:
// Progressive: 32 draw, 8 seeds (25 >= 20)
// Conservative: 32 draw, likely fewer seeds
Real-World Scenarios
Scenario 1: USTA Junior Tournament
import { tournamentEngine } from 'tods-competition-factory';
import { fixtures } from 'tods-competition-factory';
const { POLICY_SEEDING_DEFAULT } = fixtures.policies;
// Setup
const players = [
// 28 players with various rankings
{ participantId: '1', seedValue: 1 }, // Top seed
{ participantId: '2', seedValue: 2 }, // Second seed
{ participantId: '3', seedValue: 3 }, // Tied at rank 3
{ participantId: '4', seedValue: 3 }, // Tied at rank 3
{ participantId: '5', seedValue: 5 },
{ participantId: '6', seedValue: 6 },
{ participantId: '7', seedValue: 7 },
{ participantId: '8', seedValue: 8 },
// ... 20 more unseeded players
];
// Create tournament
tournamentEngine.newTournamentRecord({
tournamentName: 'USTA Junior Championships',
});
const eventId = tournamentEngine.addEvent({
eventName: 'Boys 18 Singles',
}).eventId;
tournamentEngine.addEventEntries({
eventId,
participantIds: players.map((p) => p.participantId),
});
// Generate draw with USTA seeding
const { drawId } = tournamentEngine.generateDrawDefinition({
eventId,
policyDefinitions: POLICY_SEEDING_DEFAULT,
participants: players,
drawSize: 32,
// seedsCount automatically: 28 participants, 32 draw, 28 >= 24 → 8 seeds
});
// Result:
// - 32 draw size (next power of 2 from 28)
// - 8 seeds (threshold met)
// - 4 BYEs (32 - 28)
// - SEPARATE positioning (USTA style)
// - Two players with seedValue 3 both placed in seed block 3-4
Scenario 2: ITF World Tour Event
import { drawDefinitionConstants, fixtures } from 'tods-competition-factory';
const { SINGLE_ELIMINATION } = drawDefinitionConstants;
const { POLICY_SEEDING_ITF } = fixtures.policies;
// 98 players for main draw
const mainDrawPlayers = [...]; // 98 players
// 48 players for qualifying
const qualifyingPlayers = [...]; // 48 players
// Main Draw
tournamentEngine.newTournamentRecord({
tournamentName: 'ITF World Tennis Tour M25'
});
const eventId = tournamentEngine.addEvent({
eventName: 'Men Singles'
}).eventId;
// Generate qualifying draw
const { drawId: qualifyingDrawId } = tournamentEngine.generateDrawDefinition({
eventId,
stage: 'QUALIFYING',
policyDefinitions: POLICY_SEEDING_ITF,
participants: qualifyingPlayers,
drawSize: 64,
seedsCount: 16 // 48 participants >= 48 threshold
});
// Generate main draw
const { drawId: mainDrawId } = tournamentEngine.generateDrawDefinition({
eventId,
stage: 'MAIN',
policyDefinitions: POLICY_SEEDING_ITF,
participants: mainDrawPlayers,
drawSize: 128,
seedsCount: 32 // 98 participants >= 97 threshold (ITF lower than USTA)
});
// Result:
// - Qualifying: 64 draw, 16 seeds, CLUSTER positioning
// - Main: 128 draw, 32 seeds, CLUSTER positioning
// - ITF threshold (97) allows 32 seeds with 98 players
// - USTA threshold (96) would also allow 32 seeds but philosophy differs
Scenario 3: Club Round Robin with Playoff
import { drawDefinitionConstants, fixtures } from 'tods-competition-factory';
const { ROUND_ROBIN_WITH_PLAYOFF } = drawDefinitionConstants;
const { POLICY_SEEDING_BYES } = fixtures.policies;
// 14 players for club tournament
const clubPlayers = [
{ participantId: '1', seedValue: 1 },
{ participantId: '2', seedValue: 2 },
{ participantId: '3', seedValue: 3 },
{ participantId: '4', seedValue: 4 },
// ... 10 more players
];
tournamentEngine.newTournamentRecord({
tournamentName: 'Club Championships',
});
const eventId = tournamentEngine.addEvent({
eventName: 'Club Singles',
}).eventId;
// Generate Round Robin with Playoff
const { drawId } = tournamentEngine.generateDrawDefinition({
eventId,
drawType: ROUND_ROBIN_WITH_PLAYOFF,
policyDefinitions: POLICY_SEEDING_BYES, // Use BYES policy
participants: clubPlayers,
drawSize: 16,
seedsCount: 4,
});
// Result:
// - 4 groups of 4 players (Round Robin)
// - Top 2 from each group advance to playoff (SINGLE_ELIMINATION)
// - WATERFALL seeding in groups (Seed 1 Group A, Seed 2 Group B, etc.)
// - containerByesIgnoreSeeding: true means BYEs distributed evenly
// - 2 BYEs distributed across groups independently of seeds
Scenario 4: Professional Tournament with Custom Thresholds
import { drawDefinitionConstants, policyConstants } from 'tods-competition-factory';
const { SEPARATE } = drawDefinitionConstants;
const { POLICY_TYPE_SEEDING } = policyConstants;
// ATP 250 style tournament
const proTournamentPolicy = {
[POLICY_TYPE_SEEDING]: {
policyName: 'ATP 250 Style',
seedingProfile: {
positioning: SEPARATE
},
validSeedPositions: { ignore: true },
duplicateSeedNumbers: false, // Professional - no ties
drawSizeProgression: true,
seedsCountThresholds: [
{ drawSize: 32, minimumParticipantCount: 28, seedsCount: 8 },
{ drawSize: 64, minimumParticipantCount: 56, seedsCount: 16 }
]
}
};
// 32 player field
const proPlayers = [...]; // 32 ranked players
tournamentEngine.newTournamentRecord({
tournamentName: 'ATP 250 Event'
});
const eventId = tournamentEngine.addEvent({
eventName: 'Men Singles'
}).eventId;
const { drawId } = tournamentEngine.generateDrawDefinition({
eventId,
policyDefinitions: proTournamentPolicy,
participants: proPlayers,
drawSize: 32,
seedsCount: 8
});
// Result:
// - 32 draw, 8 seeds
// - SEPARATE positioning (USTA style)
// - No BYEs (32 participants)
// - Unique seed numbers (duplicateSeedNumbers: false)
// - High threshold (28) ensures quality field needed for 8 seeds
Notes
Seed Position Calculations
Seed positions are calculated using seed blocks. The algorithm:
- First seed block:
[1]- Seed 1 always at position 1 - Second seed block:
[drawSize]- Seed 2 always at final position - Third seed block: Two positions (seeds 3-4)
- Fourth seed block: Four positions (seeds 5-8)
- Pattern continues: Each block doubles in size
SEPARATE positioning: Seeds at extremes of blocks
CLUSTER/ADJACENT positioning: Seeds alternate within blocks
WATERFALL positioning: Sequential distribution (Round Robin)
Seeds Count Determination
The engine determines seeds count automatically:
- Calculate draw size:
nextPowerOf2(participantCount) - If
drawSizeProgression: true, use draw size; else use participant count - Find matching threshold in
seedsCountThresholdsarray - Check if participant/draw count meets
minimumParticipantCount - If yes: use
seedsCountfrom threshold - If no: check next lower threshold
- Repeat until threshold met or minimum reached (typically 2 seeds)
BYE Placement with Seeds
BYEs are placed differently based on policy:
Standard (containerByesIgnoreSeeding: false):
- BYEs avoid seed positions
- BYEs placed to protect seeds
- Unseeded positions preferred for BYEs
BYES Policy (containerByesIgnoreSeeding: true):
- BYEs distributed independently
- Container structures only (Round Robin with Playoff)
- Even distribution across groups
Draw Type Overrides
When seedingProfile.drawTypes is defined:
- Engine checks draw type being generated
- Looks for matching key in
drawTypesobject - If found: uses override positioning
- If not found: uses default
seedingProfile.positioning
Common overrides:
- Round Robin: WATERFALL
- Elimination: SEPARATE or CLUSTER
- Feed-In: CLUSTER
Duplicate Seeds Behavior
When duplicateSeedNumbers: true:
// Input:
const participants = [
{ seedValue: 1 }, // Seed 1
{ seedValue: 2 }, // Seed 2
{ seedValue: 3 }, // All three tied at seed 3
{ seedValue: 3 },
{ seedValue: 3 },
{ seedValue: 6 }, // Seed 6
];
// Behavior:
// - Seed block 1: [position 1] → Seed 1 placed
// - Seed block 2: [position 32] → Seed 2 placed
// - Seed block 3-4: [positions 9, 24] → Two players with seed 3 randomly assigned
// - Seed block 5-6-7-8: Includes third player with seed 3, plus seed 6
Engine randomly distributes duplicate seeds within their appropriate blocks.
Performance Considerations
Large Draws:
- 256+ draws with 64 seeds: complex calculations
- Recommendation: Use
nonRandom: false(default) for faster generation
Many Participants:
- 200+ participants: threshold checking is O(n)
- Sorted thresholds allow early exit
Draw Generation Time:
- SEPARATE: Fastest (straightforward algorithm)
- CLUSTER/ADJACENT: Slightly slower (alternating logic)
- WATERFALL: Moderate (sequential assignment)
Validation Rules
The engine validates:
-
seedsCountThresholds:
- Must have at least one entry
- drawSize must be power of 2
- minimumParticipantCount must be ≤ drawSize
-
positioning:
- Must be valid value: CLUSTER, ADJACENT, SEPARATE, or WATERFALL
-
seedsCount:
- Cannot exceed drawSize / 2
- Must be ≥ 2 (minimum seeding)
-
validSeedPositions:
- When
ignore: false, enforces standard seed blocks - When
ignore: true, allows manual placement
- When
Integration with Other Policies
Seeding policy works with:
- Avoidance Policy: Seeds from same country/club separated
- Progression Policy: Seeded players' advancement rules
- Scoring Policy: No direct interaction
- Position Actions Policy: Seeds restrict position modifications
- MatchUp Actions Policy: Seeds affect matchUp constraints
Example:
import { fixtures, policyConstants } from 'tods-competition-factory';
const { POLICY_TYPE_AVOIDANCE } = policyConstants;
const { POLICY_SEEDING_ITF } = fixtures.policies;
const combinedPolicies = {
...POLICY_SEEDING_ITF,
[POLICY_TYPE_AVOIDANCE]: {
// Avoidance rules
},
};
tournamentEngine.generateDrawDefinition({
policyDefinitions: combinedPolicies,
// ...
});
Related Methods
Query Methods
isValidSeedPosition is public; the seed-block helpers are notisValidSeedPosition is on the engine surface as of 7.x — see
below. getSeedBlocks and getValidSeedBlocks remain internal:
reachable by module path inside this repository, but exported neither as root names nor through a
governor. They are described here because they explain how seed blocks are derived, not as an API.
isValidSeedPosition()
Answers whether a seed may be placed at a given drawPosition — the same question the engine asks
itself in positionAssignment before rejecting a hand placement, and in positionActions before
offering SEED_VALUE / REMOVE_SEED. A client doing manual seed placement should ask this rather
than re-deriving seed blocks, so that its rules and the engine's cannot drift apart.
import { tournamentEngine } from 'tods-competition-factory';
const valid = tournamentEngine.isValidSeedPosition({
appliedPolicies, // optional — read from the drawDefinition when omitted
drawDefinition,
drawPosition,
structureId,
seedNumber,
});
The answer follows the three states of validSeedPositions: { ignore: true }
accepts any position, absence restricts to the union of valid seed blocks, and { strict: true }
restricts to that seed's own block.
getSeedBlocks() (internal)
Derives seed blocks for a participant count.
// Shape only — not importable from the package.
const { seedBlocks } = getSeedBlocks({
participantsCount: 32,
cluster: true, // CLUSTER/ADJACENT positioning
});
// Returns: [[1], [32], [16, 17], [8, 9, 24, 25], ...]
getValidSeedBlocks() (internal)
Seed blocks for a specific structure with the policy applied.
// Shape only — not importable from the package.
const { validSeedBlocks } = getValidSeedBlocks({
structure,
drawDefinition,
appliedPolicies: POLICY_SEEDING_ITF,
});
getStructureSeedAssignments()
Retrieves current seed assignments in a structure.
tournamentEngine.getStructureSeedAssignments({
drawId,
structureId,
});
Mutation Methods
assignSeed()
Assigns a seed to a specific participant.
tournamentEngine.assignSeed({
drawId,
participantId: 'player-123',
seedNumber: 1,
seedValue: 1,
});
clearDrawSeeding()
Removes all seed assignments from a draw.
tournamentEngine.clearDrawSeeding({ drawId });
generateDrawDefinition()
Primary method for draw generation with seeding policy.
tournamentEngine.generateDrawDefinition({
drawSize: 32,
seedsCount: 8,
policyDefinitions: POLICY_SEEDING_ITF,
// ...
});
Seed Assignments
Every draw structure uses seedAssignments to associate unique participantIds with unique seedNumbers.
Structure
type SeedAssignment = {
seedNumber: number; // Unique seed number (1, 2, 3, ...)
seedValue: string; // Display value (can be same for multiple seeds)
participantId: string; // Unique participant identifier
};
// Example:
const seedAssignments = [
{
seedNumber: 1,
seedValue: '1',
participantId: '772C5CA9-C092-418C-AC6F-A6B584BD2D37',
},
{
seedNumber: 2,
seedValue: '2',
participantId: '267BAA81-5A38-4AAF-9EA3-E434A1ED63AD',
},
{
seedNumber: 3,
seedValue: '3-4', // Can have custom display value
participantId: 'ABC123...',
},
{
seedNumber: 4,
seedValue: '3-4', // Same display value as seed 3
participantId: 'DEF456...',
},
];
Key Points
Unique Seed Numbers:
- Only one
participantIdmay be assigned to eachseedNumber - Seed numbers are always unique within a draw structure
- Seed numbers determine seed block placement
Custom Seed Values:
- Each seed assignment may have a custom
seedValuefor display - Multiple seeds can share the same
seedValue(e.g., "5-8" for seeds 5, 6, 7, 8) - Useful for showing seed ranges on printed draws
Examples:
// Standard seeding (unique values)
const standardSeeds = [
{ seedNumber: 1, seedValue: '1', participantId: 'p1' },
{ seedNumber: 2, seedValue: '2', participantId: 'p2' },
{ seedNumber: 3, seedValue: '3', participantId: 'p3' },
{ seedNumber: 4, seedValue: '4', participantId: 'p4' },
];
// Grouped seeding (same display value)
const groupedSeeds = [
{ seedNumber: 5, seedValue: '5-8', participantId: 'p5' },
{ seedNumber: 6, seedValue: '5-8', participantId: 'p6' },
{ seedNumber: 7, seedValue: '5-8', participantId: 'p7' },
{ seedNumber: 8, seedValue: '5-8', participantId: 'p8' },
];
// Duplicate seed numbers scenario (when duplicateSeedNumbers: true)
// Five participants with equivalent rankings all appear as seed "4"
const duplicateSeeds = [
{ seedNumber: 1, seedValue: '1', participantId: 'p1' },
{ seedNumber: 2, seedValue: '2', participantId: 'p2' },
{ seedNumber: 3, seedValue: '3', participantId: 'p3' },
{ seedNumber: 4, seedValue: '4', participantId: 'p4' }, // From 3-4 block
{ seedNumber: 5, seedValue: '4', participantId: 'p5' }, // From 5-8 block
{ seedNumber: 6, seedValue: '4', participantId: 'p6' }, // From 5-8 block
{ seedNumber: 7, seedValue: '4', participantId: 'p7' }, // From 5-8 block
{ seedNumber: 8, seedValue: '4', participantId: 'p8' }, // From 5-8 block
];
Retrieving Seed Assignments
// Get seed assignments for a structure
tournamentEngine.getStructureSeedAssignments({
drawId,
structureId,
});
// Returns:
// {
// seedAssignments: [
// { seedNumber: 1, seedValue: "1", participantId: "..." },
// { seedNumber: 2, seedValue: "2", participantId: "..." },
// // ...
// ]
// }
// Get participant with seeding information
tournamentEngine.getParticipants({
withSeeding: true,
withEvents: true,
});
// Returns participants with events[].seedValue populated
Important Notes
Seed Block Placement:
- Seed number determines which seed block the participant is placed in
- Seed value is for display only - does not affect placement
- Even with same seed value, seed numbers must be unique
Display Scenarios:
- Some providers display seeds 5-8 all with value "5-8"
- Some providers display all tied participants with same seed number on draw
- ITF often shows "3/4" for seeds in the 3-4 block
- USTA typically shows individual seed numbers
With duplicateSeedNumbers: true:
- Engine allows multiple participants to share the same
seedValue - Useful when player rankings are tied
- All participants in a seed block can show same display value
- Internal seed numbers still unique for placement
Related Concepts
- Seed Blocks - How seed positions are calculated using block patterns
- Draw Types - Different draw structures (SINGLE_ELIMINATION, ROUND_ROBIN, etc.)
- Position Assignment - How participants are placed in draw positions
- Avoidance Policy - Keeping seeds from same country/club apart
- BYE Positioning - How BYEs are placed to protect seeds
Summary
The Seeding Policy is one of the most critical tournament policies, controlling:
- Seed positioning patterns (SEPARATE, CLUSTER/ADJACENT, WATERFALL)
- Seeds count based on draw size and participant count
- Draw type specific overrides (different positioning per draw type)
- Duplicate seeds for tied rankings
- BYE placement interaction with seeds
- Flexible vs strict seed position validation
Four built-in policies provided:
- POLICY_SEEDING_DEFAULT - USTA style (SEPARATE positioning)
- POLICY_SEEDING_ITF - ITF style (CLUSTER positioning)
- POLICY_SEEDING_BYES - BYE placement ignores seeding
The policy ensures fair competitive balance by strategically placing top players throughout the draw, preventing early meetings between the strongest competitors.
Key recommendation: Use built-in policies when possible (USTA or ITF), customize only when specific tournament rules require it.