Skip to main content

Tournament Generation

The generateTournamentRecord method is the primary tool for creating complete tournament structures for testing. It can generate everything from simple tournaments to complex multi-event, multi-venue competitions.

Basic Syntax​

const {
tournamentRecord, // The complete tournament object
eventIds, // Array of generated event IDs
drawIds, // Array of generated draw IDs
venueIds, // Array of generated venue IDs
} = mocksEngine.generateTournamentRecord(options);

Complete Options Reference​

interface GenerateTournamentRecordOptions {
// Tournament Properties
startDate?: string; // ISO date string (default: endDate - 7 days, or today)
endDate?: string; // ISO date string (default: startDate + 7 days); before startDate is INVALID_DATE
tournamentName?: string; // Default: a random mock name
tournamentAttributes?: object; // Additional tournament properties, including tournamentId; startDate,
// endDate and tournamentName may be given here instead of at the top level (see "Dates and name" below)
tournamentExtensions?: array; // Extensions to attach to tournament

// Convenience
setState?: boolean; // Auto-load into tournamentEngine (default: false)

// Participants
participants?: Participant[]; // Pre-built participant pool (preset mode); skips synthesis when non-empty
participantsProfile?: {
// Synthesis controls (ignored when `participants` is supplied). See Participant Generation docs.
participantsCount?: number; // Default: 32
participantType?: string; // 'INDIVIDUAL', 'PAIR', 'TEAM'
sex?: string; // 'MALE', 'FEMALE'
// ... see participantsProfile section
};

// Draws
drawProfiles?: array; // Array of draw configurations
eventProfiles?: array; // Array of event configurations

// Venues
venueProfiles?: array; // Array of venue configurations

// Scheduling
schedulingProfile?: array; // Scheduling directives
autoSchedule?: boolean; // Auto-schedule using profile; results (below) are then applied after scheduling,
// so completed matchUps carry the schedule they were played to. The schedulers never place a completed
// matchUp, and `scheduleCompletedMatchUps` is accepted and ignored (CA 2026-10-23)
scenarioProfile?: object; // Anchor the schedule to a moment (see below)

// Match Completion (each may also be set on a single drawProfile, which takes precedence)
completeAllMatchUps?: boolean | string; // Complete all generated matchUps (or a score string)
randomWinningSide?: boolean; // Random winners (default: false = side 1 wins)
matchUpStatusProfile?: object; // Control matchUp status distribution

// IDs
uuids?: array; // Pre-defined UUIDs for entities

// Deterministic generation
nonRandom?: number; // Seed for deterministic PRNG (replaces Math.random)

// Policies
policyDefinitions?: object; // Attach policy definitions
}

Tournament Properties​

Sex vs Gender

In the CODES data model:

  • Persons have sex - A biological attribute (MALE/FEMALE) stored in person.sex
  • Events have gender - A competition category attribute (MALE/FEMALE/MIXED/ANY) stored in event.gender

Use participantsProfile.sex to generate participants with specific person sex attributes. Use event.gender or drawProfile.gender to categorize the competition.

Basic Properties​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
startDate: '2024-06-01',
endDate: '2024-06-07',
tournamentAttributes: {
tournamentName: 'Summer Championships',
tournamentRank: 'GOLD',
indoorOutdoor: 'OUTDOOR',
},
});

Dates and name​

startDate, endDate and tournamentName may be given at the top level or inside tournamentAttributes; either place is read. A date that is given in neither place is derived as described above (a week either side of the one given, or today and a week from today), and a missing name is a random mock name.

Giving the same field in both places with different values is refused with INVALID_VALUES before anything is generated, naming both (context: { option: 'tournamentAttributes.startDate', conflictsWith: 'startDate' }); the same value in both places is accepted. With setState: true, nothing is installed on that refusal.

// tournamentAttributes dates are the tournament's dates, whatever today is
const { tournamentRecord } = mocksEngine.generateTournamentRecord({
tournamentAttributes: { startDate: '2025-06-01', endDate: '2025-06-30' },
});
// tournamentRecord.startDate === '2025-06-01'

const result = mocksEngine.generateTournamentRecord({
tournamentAttributes: { startDate: '2025-06-01' },
startDate: '2025-06-02',
});
// result.error -> INVALID_VALUES
Up to 7.20.1

Earlier versions wrote the top-level values over tournamentAttributes without saying so, and the top-level dates default to today: dates or a name given only in tournamentAttributes were silently replaced by this week and a random mock name.

Custom Tournament ID​

const tournamentId = 'my-custom-id';
const { tournamentRecord } = mocksEngine.generateTournamentRecord({
tournamentAttributes: { tournamentId }, // a top-level tournamentId is not read
drawProfiles: [{ drawSize: 16 }],
});

console.log(tournamentRecord.tournamentId); // 'my-custom-id'

Auto-Loading with setState​

For convenience, automatically load the generated tournament into the engine:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 16 }],
setState: true, // Automatically calls tournamentEngine.setState(tournamentRecord)
});

// Tournament is already loaded - no need to call setState manually
const { matchUps } = tournamentEngine.allTournamentMatchUps();

This is especially useful in test setup where you want to immediately work with the tournament:

beforeEach(() => {
mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 32 }],
venueProfiles: [{ courtsCount: 8 }],
setState: true, // Ready to use tournamentEngine immediately
});
});

test('can schedule matches', () => {
// tournamentEngine already has the tournament loaded
const { matchUps } = tournamentEngine.allTournamentMatchUps();
// ... test scheduling
});

Tournament Extensions​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
tournamentExtensions: [
{
name: 'customData',
value: { region: 'Southwest', tier: 1 },
},
],
});

The Method List​

With recordMethods: true the result also carries methods: the engine calls a client would make to build the same record, in the order it would make them. Replayed through any engine they rebuild the record; only the times each write stamps (createdAt, updatedAt, a result's scoredTime) differ.

const { tournamentRecord, methods } = mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 8 }],
completeAllMatchUps: true,
recordMethods: true,
});

// methods[0] is { method: 'newTournamentRecord', params }, an engine method; the rest are governor methods
const [header, ...rest] = methods;
tournamentEngine.newTournamentRecord(header.params);
tournamentEngine.executionQueue(rest);

Params name events, draws and matchUps by id. Steps that are random are logged as their results, so a replay is deterministic: the draw that was generated (addDrawDefinition with that draw), each outcome that was set (setMatchUpStatus), an AD_HOC round's matchUps (addAdHocMatchUps). Recording copies each call's params, so it is off unless asked for.

modifyTournamentRecord takes recordMethods too. Its list has no newTournamentRecord: it starts from the record the caller passed, and replayed onto that record it makes the same modifications. A refused modification wrote nothing (it is all or nothing), so it returns no list; with keepPartial the list holds what was kept.

const { methods } = mocksEngine.modifyTournamentRecord({
drawProfiles: [{ drawSize: 8 }],
recordMethods: true,
tournamentRecord,
});
tournamentEngine.setState(originalRecord);
tournamentEngine.executionQueue(methods);

Preset Participants (Ingest Mode)​

When the caller already has a participant list (e.g. a federation-data ingest pipeline that scraped real player IDs from upstream HTML), pass the array directly via the top-level participants field. Factory uses your pool as the entry source instead of synthesizing mocks via addTournamentParticipants / generateEventParticipants:

const participants = [
// ...Participant[] — INDIVIDUAL and/or PAIR objects with stable provider IDs
];

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
tournamentName: 'Real Federation Data',
participants,
eventProfiles: [
{
eventName: 'Singles',
eventType: 'SINGLES',
gender: 'MALE',
drawProfiles: [
{
drawSize: 32,
participantsCount: 32,
automated: false, // leave positionAssignments empty for manual placement
},
],
},
],
setState: true,
});

When participants is supplied (non-empty array):

  • Factory calls addParticipants directly with your list; the synthesis path (addTournamentParticipants / generateEventParticipants / per-draw uniqueDrawParticipants) is suppressed.
  • participantsProfile synthesis fields (participantsCount, participantType, sex, etc.) are ignored — your pool is authoritative.
  • filterConsideredParticipants still applies event-level filters: gender, eventType, participantType. Supply enough participants that satisfy those filters or the draw will run short on entries.
  • automated: false on the drawProfile leaves positionAssignments empty so you can fill them in via tournamentEngine.assignDrawPosition with your stable IDs, then walk outcomes via tournamentEngine.setMatchUpStatus.

The full ingest pattern — generate skeleton → assign positions → apply outcomes — is documented under Pre-built participants.

Using drawProfiles​

Draw profiles define the structure of draws within events. Each profile creates a separate event with a single draw.

Scoped Participant Generation

Each drawProfile can include its own participantsProfile to generate participants specifically for that draw, overriding or extending the tournament-level participantsProfile. This is useful when you need different participant demographics for different events.

Basic Draw​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{ drawSize: 32 }, // Creates 32-draw single elimination singles event
],
});

Multiple Draws​

const { tournamentRecord, drawIds, eventIds } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{ drawSize: 32, eventType: 'SINGLES' },
{ drawSize: 16, eventType: 'DOUBLES' },
{ drawSize: 8, eventType: 'SINGLES', drawType: 'ROUND_ROBIN' },
],
});

console.log(eventIds.length); // 3 events
console.log(drawIds.length); // 3 draws

Draw Types​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{ drawSize: 16, drawType: 'SINGLE_ELIMINATION' }, // Default
{ drawSize: 16, drawType: 'ROUND_ROBIN' },
{ drawSize: 16, drawType: 'COMPASS' },
{ drawSize: 16, drawType: 'FEED_IN_CHAMPIONSHIP' },
{ drawSize: 16, drawType: 'FIRST_MATCH_LOSER_CONSOLATION' },
{ drawSize: 16, drawType: 'ROUND_ROBIN_WITH_PLAYOFF' },
],
});

Event Types​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{ drawSize: 32, eventType: 'SINGLES' }, // Default
{ drawSize: 16, eventType: 'DOUBLES' },
{ drawSize: 4, eventType: 'TEAM' },
],
});

Seeding​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 32,
seedsCount: 8, // Top 8 participants will be seeded
},
],
});

BYEs​

Create draws with fewer participants than positions:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 32,
participantsCount: 28, // Creates 4 BYEs
},
],
});

MatchUp Format​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 16,
matchUpFormat: 'SET3-S:6/TB7',
},
],
});

Unique Participants​

Force generation of unique participants for each draw:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{ drawSize: 16, uniqueParticipants: true },
{ drawSize: 16, uniqueParticipants: true },
],
});
// Each draw will have completely different participants

Draw-Specific Participant Profiles​

Each drawProfile can include its own participantsProfile to generate participants with specific demographics:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 32,
eventType: 'SINGLES',
gender: 'FEMALE', // Event gender category
participantsProfile: {
sex: 'FEMALE', // Person sex - generates female participants
category: {
categoryName: 'U18',
ageCategoryCode: 'U18',
},
scaleAllParticipants: true, // All get ratings
},
},
{
drawSize: 16,
eventType: 'DOUBLES',
gender: 'MALE', // Event gender category
participantsProfile: {
sex: 'MALE', // Person sex - generates male participants
nationalityCodes: ['USA', 'CAN', 'MEX'], // North American players
},
},
],
});

This creates two separate participant pools - one for each event. Draw-level participantsProfile properties override tournament-level settings:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
// Tournament-level (applies to all draws by default)
participantsProfile: {
participantsCount: 100,
sex: 'FEMALE',
},
drawProfiles: [
{
drawSize: 32,
// Uses tournament-level participantsProfile (FEMALE)
},
{
drawSize: 16,
participantsProfile: {
sex: 'MALE', // Overrides tournament-level to create MALE participants
},
},
],
});

Preventing Draw Generation​

Create events without generating the draw structure:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 32,
generate: false, // Creates event with entries, but no draw
},
],
});

Using eventProfiles​

Event profiles allow more complex event configurations with multiple draws per event.

Event-Level Participant Profiles

Like drawProfile, each eventProfile can include its own participantsProfile to generate participants specifically for that event. This applies to all draws within the event unless a draw overrides it with its own participantsProfile.

Basic Event Profile​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
eventProfiles: [
{
eventName: 'U18 Singles',
eventType: 'SINGLES',
category: { ageCategoryCode: 'U18' },
drawProfiles: [{ drawSize: 32 }],
},
],
});

Draw with Qualifying Structure​

Qualifying as a Stage

In CODES, qualifying is a stage within a draw, not a separate draw. A drawDefinition describes how participants move between structures (QUALIFYING → MAIN). Use qualifyingProfiles to create qualifying structures within a single draw.

Do NOT create separate draws with stage: 'QUALIFYING' - this violates the CODES structure where draws describe participant progression between stages.

Use qualifyingProfiles to create qualifying structures within a single draw:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 32,
drawName: 'Singles Championship',
qualifiersCount: 8, // 8 positions for qualifiers in main draw
qualifyingProfiles: [
{
roundTarget: 1, // Which round of main draw qualifiers enter
structureProfiles: [
{
stageSequence: 1,
drawSize: 16, // 16 players compete for 8 qualifying spots
seedsCount: 4,
},
],
},
],
},
],
});

Multiple Draws in One Event​

For truly separate competitions (not qualifying):

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
eventProfiles: [
{
eventName: 'Championships',
eventType: 'SINGLES',
drawProfiles: [
{ drawSize: 32, drawName: 'U18 Draw' },
{ drawSize: 16, drawName: 'U16 Draw' },
],
},
],
});

Event Properties​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
eventProfiles: [
{
eventName: "Women's Doubles",
eventType: 'DOUBLES',
gender: 'FEMALE',
category: {
ageCategoryCode: 'OPEN',
categoryName: 'Open',
},
matchUpFormat: 'SET3-S:6/TB7-F:TB10',
surfaceCategory: 'CLAY',
ballType: 'REGULAR_DUTY',
drawProfiles: [{ drawSize: 16 }],
},
],
});

Event Extensions​

Extensions carry data CODES has no first-class home for. This example used to demonstrate a prizeMoney extension, which is no longer the right shape — Event.prizeMoney is a first-class PrizeMoney[] field, and a monetary amount states its unit (see Sanctioning):

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
eventProfiles: [
{
eventName: 'Singles',
eventExtensions: [
{
name: 'sponsorTier',
value: { tier: 'PLATINUM', activationDeadline: '2026-05-01' },
},
],
drawProfiles: [{ drawSize: 32 }],
},
],
});
tip

Reach for an extension only when nothing in CODES models the thing. Prize money is the cautionary example: because Event had no prize-money field, an ad-hoc { currency, total } extension grew up beside PrizeMoney's { amount, currencyCode } — two shapes for one concept, neither of which said whether the number was dollars or cents.

Event-Level Participant Profiles​

Each eventProfile can include a participantsProfile that applies to all draws within that event:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
eventProfiles: [
{
eventName: "Women's Championship",
eventType: 'SINGLES',
gender: 'FEMALE',
// Event-level participantsProfile
participantsProfile: {
sex: 'FEMALE',
participantsCount: 128,
category: {
categoryName: 'Open',
ratingType: 'WTN',
},
scaleAllParticipants: true,
},
drawProfiles: [
{
drawSize: 64,
drawName: 'Singles Championship',
qualifiersCount: 16, // 16 qualifying positions
qualifyingProfiles: [
{
roundTarget: 1,
structureProfiles: [
{
stageSequence: 1,
drawSize: 32, // 32 players compete for 16 spots
seedsCount: 8,
},
],
},
],
},
],
},
],
});

Participant Profile Hierarchy​

The participant generation follows this priority order:

  1. Draw-level participantsProfile (highest priority)
  2. Event-level participantsProfile
  3. Tournament-level participantsProfile (lowest priority)
const { tournamentRecord } = mocksEngine.generateTournamentRecord({
// Tournament-level (default for all)
participantsProfile: {
participantsCount: 200,
nationalityCodes: ['USA', 'CAN'],
},
eventProfiles: [
{
eventName: 'International Singles',
// Event-level (overrides tournament-level for this event)
participantsProfile: {
nationalityCodes: ['GBR', 'FRA', 'ESP'], // Overrides tournament-level
sex: 'FEMALE', // Adds to tournament-level
},
drawProfiles: [
{
drawSize: 32,
// Uses event-level participantsProfile
},
{
drawSize: 16,
// Draw-level (overrides both)
participantsProfile: {
nationalityCodes: ['JPN', 'CHN', 'KOR'], // Asian players only
},
},
],
},
],
});

Event with Playoff Structures​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 16,
drawType: 'SINGLE_ELIMINATION',
withPlayoffs: {
roundProfiles: [{ 3: 1 }, { 4: 1 }], // Create playoffs from rounds 3 and 4
playoffPositions: [3, 4], // For 3rd/4th place
playoffAttributes: {
'0-3': { name: 'Bronze', abbreviation: 'B' },
'0-4': { name: 'Gold', abbreviation: 'G' },
},
},
},
],
});

Completing MatchUps​

Complete All Matches​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 16 }],
completeAllMatchUps: true,
randomWinningSide: true, // Random winners, otherwise side 1 always wins
});

Complete One Draw​

completeAllMatchUps, randomWinningSide and matchUpStatusProfile can also be set on an individual draw profile — in drawProfiles, in an eventProfile's drawProfiles, and in both for modifyTournamentRecord. The value applies to that draw only and takes precedence over the generation-wide value; a draw profile that leaves an option undefined inherits the generation-wide value (for an existing event in modifyTournamentRecord, the eventProfile value).

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{ drawSize: 16, completeAllMatchUps: true, randomWinningSide: true }, // completed
{ drawSize: 16 }, // inherits: not completed
{ drawSize: 8, drawType: 'ROUND_ROBIN', completeAllMatchUps: true }, // completed
],
});

// generation-wide completion with one draw opting out
mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 16 }, { drawSize: 16, completeAllMatchUps: false }],
completeAllMatchUps: true,
});

Complete Specific Matches​

Using outcomes in draw profiles:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 16,
outcomes: [
{
roundNumber: 1,
roundPosition: 1,
scoreString: '6-4 6-2',
winningSide: 1,
},
{
roundNumber: 1,
roundPosition: 2,
scoreString: '7-6(3) 3-6 6-3',
winningSide: 2,
},
],
},
],
});

Complete by Draw Positions​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 16,
outcomes: [
{
drawPositions: [1, 2], // Match between positions 1 and 2
scoreString: '6-4 6-2',
winningSide: 1,
},
],
},
],
});

Match Status Variations​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 16,
outcomes: [
{ roundNumber: 1, roundPosition: 1, matchUpStatus: 'COMPLETED', winningSide: 1 },
{ roundNumber: 1, roundPosition: 2, matchUpStatus: 'RETIRED', winningSide: 1 },
{ roundNumber: 1, roundPosition: 3, matchUpStatus: 'WALKOVER', winningSide: 2 },
{ roundNumber: 1, roundPosition: 4, matchUpStatus: 'DEFAULTED', winningSide: 1 },
],
},
],
});

Completion Goal​

Complete a specific number of matches:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 32,
completionGoal: 10, // Complete first 10 matchUps
},
],
});

Match Status Distribution​

Control the distribution of match statuses across completed matches:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 16 }],
completeAllMatchUps: true,
matchUpStatusProfile: {
COMPLETED: 80, // 80% completed normally
RETIRED: 10, // 10% retirements
WALKOVER: 5, // 5% walkovers
DEFAULTED: 5, // 5% defaults
},
});

ID Prefixes​

Match ID Prefixes​

Create readable matchUp IDs for easier debugging:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 8,
idPrefix: 'match',
},
],
});

// MatchUp IDs will be: match-1-1, match-1-2, match-1-3, match-1-4
// match-2-1, match-2-2
// match-3-1

Participant ID Prefixes​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
participantsProfile: {
participantsCount: 32,
idPrefix: 'player',
},
drawProfiles: [{ drawSize: 16 }],
});

// Participant IDs will be: player-I-0, player-I-1, etc. (I = Individual)

Policy Definitions​

Attach policies to tournaments, events, or specific draws. Like participantsProfile, policies can be scoped at multiple levels.

Policy Definition Hierarchy

policyDefinitions can be specified at three levels with the following priority:

  1. Draw-level (within drawProfile) - highest priority, applies only to that draw
  2. Event-level (within eventProfile) - applies to all draws in that event
  3. Tournament-level (root level) - applies to all draws unless overridden

This allows you to specify different policies for different events or draws within the same tournament.

Tournament-Level Policies​

Policies applied at the tournament level affect all events and draws:

import POLICY_SCHEDULING_DEFAULT from '@Fixtures/policies/POLICY_SCHEDULING_DEFAULT';

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
policyDefinitions: POLICY_SCHEDULING_DEFAULT,
drawProfiles: [{ drawSize: 16 }],
});

Event-Level Policies​

Policies applied to an event affect all draws within that event:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
eventProfiles: [
{
eventName: 'Singles',
policyDefinitions: {
scoring: {/* scoring policy */},
avoidance: {/* avoidance policy */},
},
drawProfiles: [{ drawSize: 32 }],
},
],
});

Draw-Level Policies​

Policies applied to a specific draw override event and tournament-level policies:

import { POLICY_SEEDING_ITF, POLICY_SEEDING_USTA } from '@Fixtures/policies/seeding';

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
// Tournament-level default
policyDefinitions: POLICY_SEEDING_USTA,

drawProfiles: [
{
drawSize: 32,
drawName: 'Professional Draw',
// Draw-level override
policyDefinitions: POLICY_SEEDING_ITF, // This draw uses ITF seeding
},
{
drawSize: 16,
drawName: 'Amateur Draw',
// Uses tournament-level USTA seeding
},
],
});

Complex Examples​

Multi-Event Tournament with Qualifying​

const { tournamentRecord, eventIds } = mocksEngine.generateTournamentRecord({
startDate: '2024-06-01',
endDate: '2024-06-14',
participantsProfile: {
participantsCount: 128,
sex: 'MALE',
},
eventProfiles: [
{
eventName: "Men's Singles",
eventType: 'SINGLES',
gender: 'MALE',
drawProfiles: [
{
drawSize: 64,
drawName: 'Singles Championship',
seedsCount: 16,
qualifiersCount: 8, // 8 qualifier positions in main draw
qualifyingProfiles: [
{
roundTarget: 1,
structureProfiles: [
{
stageSequence: 1,
drawSize: 16, // 16 players for 8 qualifying spots
seedsCount: 4,
},
],
},
],
},
],
},
{
eventName: "Men's Doubles",
eventType: 'DOUBLES',
gender: 'MALE',
drawProfiles: [
{
drawSize: 32,
seedsCount: 8,
},
],
},
],
venueProfiles: [
{
venueName: 'Championship Courts',
courtsCount: 12,
},
],
});

Round Robin with Playoffs​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 16,
drawType: 'ROUND_ROBIN_WITH_PLAYOFF',
seedsCount: 4,
},
],
completeAllMatchUps: true,
});

Team Event with Tie Format​

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 8,
eventType: 'TEAM',
tieFormat: {
collectionDefinitions: [
{
collectionName: 'Singles',
matchUpType: 'SINGLES',
matchUpCount: 3,
matchUpFormat: 'SET3-S:6/TB7',
},
{
collectionName: 'Doubles',
matchUpType: 'DOUBLES',
matchUpCount: 1,
matchUpFormat: 'SET3-S:6/TB7',
},
],
},
},
],
});

Team Gender Composition Override​

By default, processTieFormat computes per-team gender counts from the maximum gendered collection requirements. When a tieFormat requires more gendered players than the MAX-per-collection heuristic produces (e.g. collections needing 3 men and 3 women total, but the MAX only detects 2 of each), use teamGenders as a floor override:

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 2,
eventType: 'TEAM',
teamNames: ['Team Alpha', 'Team Beta'],
teamGenders: { MALE: 3, FEMALE: 3 }, // Override: at least 3M + 3F per team
tieFormat: {
tieFormatName: 'INTENNSE',
winCriteria: { aggregateValue: true },
collectionDefinitions: [
{
collectionName: "Men's Singles",
matchUpType: 'SINGLES',
matchUpCount: 2,
matchUpFormat: 'SET2XA-S:T10',
gender: 'MALE',
},
{
collectionName: "Women's Singles",
matchUpType: 'SINGLES',
matchUpCount: 2,
matchUpFormat: 'SET2XA-S:T10',
gender: 'FEMALE',
},
{
collectionName: "Men's Doubles",
matchUpType: 'DOUBLES',
matchUpCount: 1,
matchUpFormat: 'SET1A-S:T10',
gender: 'MALE',
},
{
collectionName: "Women's Doubles",
matchUpType: 'DOUBLES',
matchUpCount: 1,
matchUpFormat: 'SET1A-S:T10',
gender: 'FEMALE',
},
{
collectionName: 'Mixed Doubles',
matchUpType: 'DOUBLES',
matchUpCount: 1,
matchUpFormat: 'SET1A-S:T10',
gender: 'MIXED',
},
],
},
},
],
completeAllMatchUps: true,
});

teamGenders acts as a floor — it only increases gender counts, never decreases them. If processTieFormat already computes a higher value for a gender, that value is preserved. The teamSize is also adjusted upward if the overridden gender totals exceed it.

Deterministic Generation​

The nonRandom parameter seeds a deterministic PRNG that replaces Math.random throughout the entire generation chain. This makes every random decision — participant names, IDs, nationalities, addresses, ratings, draw position assignments, and match outcomes — fully reproducible.

What a seed promises: given the same nonRandom, the same profile, the same clock and the same factory version, the record is identical byte for byte. It does not depend on the process's random source (setRandomSource) or on anything generated earlier in the process, and the call draws nothing from that source. modifyTournamentRecord gives the same promise for what it adds. To pin the clock as well, use setClock. See Randomness.

Same Seed = Same Results​

const result1 = mocksEngine.generateTournamentRecord({
nonRandom: 42,
drawProfiles: [{ drawSize: 16, seedsCount: 4 }],
});

const result2 = mocksEngine.generateTournamentRecord({
nonRandom: 42,
drawProfiles: [{ drawSize: 16, seedsCount: 4 }],
});

// Identical participants, draw positions, seedings
expect(result1.tournamentRecord.participants.map((p) => p.participantName)).toEqual(
result2.tournamentRecord.participants.map((p) => p.participantName),
);

Different Seeds = Different Results​

const result1 = mocksEngine.generateTournamentRecord({
nonRandom: 42,
drawProfiles: [{ drawSize: 16 }],
});

const result2 = mocksEngine.generateTournamentRecord({
nonRandom: 99,
drawProfiles: [{ drawSize: 16 }],
});

// Different participants and draw positions
expect(result1.tournamentRecord.participants[0].participantName).not.toEqual(
result2.tournamentRecord.participants[0].participantName,
);

Deterministic Scores​

When combined with completeAllMatchUps, scores are also deterministic:

const result1 = mocksEngine.generateTournamentRecord({
nonRandom: 42,
drawProfiles: [{ drawSize: 8 }],
completeAllMatchUps: true,
});

const result2 = mocksEngine.generateTournamentRecord({
nonRandom: 42,
drawProfiles: [{ drawSize: 8 }],
completeAllMatchUps: true,
});

tournamentEngine.setState(result1.tournamentRecord);
const { matchUps: matchUps1 } = tournamentEngine.allTournamentMatchUps();

tournamentEngine.setState(result2.tournamentRecord);
const { matchUps: matchUps2 } = tournamentEngine.allTournamentMatchUps();

// Same scores, same winners
matchUps1.forEach((m, i) => {
expect(m.score?.scoreStringSide1).toEqual(matchUps2[i].score?.scoreStringSide1);
expect(m.winningSide).toEqual(matchUps2[i].winningSide);
});
Use nonRandom for snapshot and regression testing

nonRandom is ideal for snapshot tests where you need stable output, regression tests where you want to detect behavioral changes, and debugging where you need to reproduce exact tournament state.

Tips and Best Practices​

  1. Start with drawProfiles for simple scenarios, use eventProfiles for complex multi-draw events
  2. Use idPrefix during development for easier debugging
  3. Set completeAllMatchUps when testing post-match logic
  4. Use unique participants when testing cross-event conflicts
  5. Specify matchUpFormat when testing format-specific logic
  6. Leverage outcomes to set up specific match scenarios
  7. Use completionGoal to test partial tournament completion

Next Steps​

Anchoring a schedule to "now" (scenarioProfile)​

startDate: today makes the date current but leaves the clock fixed: matchUps are placed from the venue's opening time, so a tournament generated at 16:00 has every matchUp hours in the past. Anything that reads the wall clock — a "now" strip, due-match calls, running-late badges, recovery countdowns — then has nothing live to act on.

scenarioProfile shifts the generated schedule so it straddles a chosen moment.

const { tournamentRecord, scenarioResult } = mocksEngine.generateTournamentRecord({
startDate: today,
drawProfiles: [{ drawId: 'd1', drawSize: 16 }],
venueProfiles: [{ venueId: 'v1', courtsCount: 6, startTime: '08:00', endTime: '20:00' }],
schedulingProfile: [{ scheduleDate: today, venues: [{ venueId: 'v1', rounds: [{ drawId: 'd1', roundNumber: 1 }] }] }],
autoSchedule: true,

scenarioProfile: {
anchor: 'NOW', // or an ISO datetime, e.g. '2026-09-23T15:00'
minutesBeforeAnchor: 120, // the FIRST matchUp starts this long before the anchor
assignCourts: true, // also place matchUps on courts
},
});
fieldmeaning
anchor'NOW' (default) reads the clock at call time; an ISO datetime makes the result deterministic
minutesBeforeAnchorhow far before the anchor the first scheduled matchUp starts; everything else keeps its spacing
assignCourtsadditionally assign courtId / courtOrder, preserving the scheduled times
called{ withinMinutes }: the matchUps due that soon after the anchor get calledAt (the anchor)
checkedIn{ withinMinutes }: the matchUps due that soon have both sides' individuals checked in
conflicts[{ type, count }]: deliberate schedule conflicts (needs assignCourts); see below

What it does and does not do​

  • It shifts, it does not re-derive. The spacing the scheduler produced — which honours matchUp average and recovery minutes — is preserved exactly. Only the times move.
  • minutesBeforeAnchor is what puts matchUps on both sides of the anchor: some finished, some live, some upcoming. That is what makes a wall-clock surface demonstrable rather than empty.
  • It requires matchUps that are already scheduled (a schedulingProfile with autoSchedule). With nothing scheduled it is a no-op, not an error — scenarioResult.shiftedCount is 0.
  • Pass an ISO anchor in tests. 'NOW' is for demos and fixtures meant to stay current.
  • With autoSchedule, the order is: schedule, anchor, assign courts, then apply the results, then called / checkedIn / conflicts. So completed matchUps are shifted and courted with the rest, and the operational state reads the played tournament.
  • The shift is all or nothing. It is refused, before any matchUp moves, when it would leave the tournament's dates (INVALID_DATE) or leave a matchUp out of played order (ANACHRONISM, judged on the whole shift: a uniform shift of a schedule that is in order never is). An invalid called, checkedIn or conflicts is refused before the shift as well, and a court grid that refuses is the call's refusal.

scenarioResult reports anchoredTo, shiftMinutes and shiftedCount, and calledMatchUpIds, checkedInMatchUpIds and conflicts (each with its type and matchUpIds).

Deliberate conflicts​

A conflict is asked for by the name proConflicts reports it under, so a fixture keeps exercising the detector whatever the scheduler does next:

typeplaced as
CONFLICT_COURT_TIME_ORDERa later matchUp on a court moved to the time of the one before it
CONFLICT_COURT_DOUBLE_BOOKINGa matchUp moved onto another's court and order of play
CONFLICT_MATCHUP_ORDERa matchUp moved into the same order of play as a matchUp it depends on

Each is placed on matchUps that carry no issue yet, because proConflicts reports only a matchUp's most severe issue. A type it cannot place, or one outside these, is refused with INVALID_VALUES.

note

Ensure the venue's hours contain the anchored window. Shifting a schedule far outside dateAvailability will place matchUps at times the courts are not open.