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
In the CODES data model:
- Persons have
sex- A biological attribute (MALE/FEMALE) stored inperson.sex - Events have
gender- A competition category attribute (MALE/FEMALE/MIXED/ANY) stored inevent.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
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
addParticipantsdirectly with your list; the synthesis path (addTournamentParticipants/generateEventParticipants/ per-drawuniqueDrawParticipants) is suppressed. participantsProfilesynthesis fields (participantsCount,participantType,sex, etc.) are ignored — your pool is authoritative.filterConsideredParticipantsstill applies event-level filters:gender,eventType,participantType. Supply enough participants that satisfy those filters or the draw will run short on entries.automated: falseon the drawProfile leaves positionAssignments empty so you can fill them in viatournamentEngine.assignDrawPositionwith your stable IDs, then walk outcomes viatournamentEngine.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.
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.
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
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 }],
},
],
});
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:
- Draw-level
participantsProfile(highest priority) - Event-level
participantsProfile - 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.
policyDefinitions can be specified at three levels with the following priority:
- Draw-level (within drawProfile) - highest priority, applies only to that draw
- Event-level (within eventProfile) - applies to all draws in that event
- 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);
});
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
- Start with drawProfiles for simple scenarios, use eventProfiles for complex multi-draw events
- Use idPrefix during development for easier debugging
- Set completeAllMatchUps when testing post-match logic
- Use unique participants when testing cross-event conflicts
- Specify matchUpFormat when testing format-specific logic
- Leverage outcomes to set up specific match scenarios
- Use completionGoal to test partial tournament completion
Next Steps
- Participant Generation - Individual, pairs and teams
- Outcome Generation - Generating match results
- Advanced Patterns - Best practices and common patterns
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
},
});
| field | meaning |
|---|---|
anchor | 'NOW' (default) reads the clock at call time; an ISO datetime makes the result deterministic |
minutesBeforeAnchor | how far before the anchor the first scheduled matchUp starts; everything else keeps its spacing |
assignCourts | additionally 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.
minutesBeforeAnchoris 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
schedulingProfilewithautoSchedule). With nothing scheduled it is a no-op, not an error —scenarioResult.shiftedCountis0. - Pass an ISO
anchorin tests.'NOW'is for demos and fixtures meant to stay current. - With
autoSchedule, the order is: schedule, anchor, assign courts, then apply the results, thencalled/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 invalidcalled,checkedInorconflictsis 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:
type | placed as |
|---|---|
CONFLICT_COURT_TIME_ORDER | a later matchUp on a court moved to the time of the one before it |
CONFLICT_COURT_DOUBLE_BOOKING | a matchUp moved onto another's court and order of play |
CONFLICT_MATCHUP_ORDER | a 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.
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.