Skip to main content

Outcome Generation

The mocksEngine provides powerful tools for generating match outcomes with realistic scores, various match statuses, and controlled randomization.

generateOutcome

Generate complete match outcomes with scores:

const { outcome } = mocksEngine.generateOutcome({
matchUpFormat: 'SET3-S:6/TB7',
winningSide: 1,
});

Returns an outcome object with:

  • score: Complete score structure with sets
  • winningSide: 1 or 2 (or undefined for DOUBLE_WALKOVER, etc.)
  • matchUpStatus: 'COMPLETED', 'RETIRED', 'DEFAULTED', etc.

Basic Usage

Simple Outcome

const { outcome } = mocksEngine.generateOutcome();

// Returns:
{
score: {
sets: [
{ side1Score: 6, side2Score: 3, winningSide: 1 },
{ side1Score: 6, side2Score: 4, winningSide: 1 },
],
scoreStringSide1: '6-3 6-4',
scoreStringSide2: '3-6 4-6',
},
winningSide: 1,
matchUpStatus: 'COMPLETED',
}

Specify Winner

const { outcome } = mocksEngine.generateOutcome({
winningSide: 2, // Side 2 wins
});

With MatchUp Format

Generate scores that comply with a specific format:

const { outcome } = mocksEngine.generateOutcome({
matchUpFormat: 'SET3-S:6/TB7', // Best of 3 sets, games to 6, tiebreak at 7
winningSide: 1,
});

Common formats:

  • SET3-S:6/TB7 - Best of 3 sets, tiebreak at 6-6
  • SET5-S:6/TB7 - Best of 5 sets
  • SET3-S:4/TB7 - Short sets to 4
  • SET1-S:T20 - Timed set (20 minutes)
  • SET3-S:6/TB7-F:TB10 - Final set to 10-point tiebreak

Match Status Profiles

Control the distribution of match statuses:

All Completed

const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { COMPLETED: 100 },
});

Mixed Statuses

const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: {
COMPLETED: 70, // 70% completed normally
RETIRED: 15, // 15% retirements
WALKOVER: 10, // 10% walkovers
DEFAULTED: 5, // 5% defaults
},
});

Specific Status

// Retirement
const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { RETIRED: 100 },
winningSide: 1,
});

// Walkover
const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { WALKOVER: 100 },
winningSide: 1,
});

// Default
const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { DEFAULTED: 100 },
winningSide: 2,
});

Default with Score

By default, DEFAULTED outcomes have no score. To include scores:

const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { DEFAULTED: 100 },
defaultWithScorePercent: 100, // Always include score with defaults
winningSide: 1,
});

Double Walkover

const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { DOUBLE_WALKOVER: 100 },
});

// Note: winningSide will be undefined
console.log(outcome.winningSide); // undefined
console.log(outcome.matchUpStatus); // 'DOUBLE_WALKOVER'

Suspended/Incomplete

const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { SUSPENDED: 100 },
});

// No winningSide for suspended matches
console.log(outcome.winningSide); // undefined

Score Control

Side Weight

Control how often "deciding sets" are generated:

const { outcome } = mocksEngine.generateOutcome({
matchUpFormat: 'SET3-S:6/TB7',
sideWeight: 4, // Default - controls score variation
});

Lower sideWeight = more three-set matches Higher sideWeight = more straight-set wins

Timed Sets

Generate outcomes for timed sets:

const { outcome } = mocksEngine.generateOutcome({
matchUpFormat: 'SET1-S:T20', // 20-minute timed set
pointsPerMinute: 1.5, // Points scored per minute
});

generateOutcomeFromScoreString

Create outcomes from score strings:

const { outcome } = mocksEngine.generateOutcomeFromScoreString({
scoreString: '6-1 6-2',
winningSide: 1,
});

Score String Format

Scores are always from the winner's perspective:

// Side 1 wins
const { outcome } = mocksEngine.generateOutcomeFromScoreString({
scoreString: '6-4 3-6 7-5',
winningSide: 1,
});

// Side 2 wins the same match (flipped perspective)
const { outcome2 } = mocksEngine.generateOutcomeFromScoreString({
scoreString: '4-6 6-3 5-7', // Same match, side 2 perspective
winningSide: 2,
});

With Tiebreaks

const { outcome } = mocksEngine.generateOutcomeFromScoreString({
scoreString: '7-6(3) 6-4',
winningSide: 1,
});

// Result includes tiebreak details in sets
outcome.score.sets[0].side1TiebreakScore; // 7
outcome.score.sets[0].side2TiebreakScore; // 3

With Match Status

const { outcome } = mocksEngine.generateOutcomeFromScoreString({
scoreString: '6-3 4-2',
matchUpStatus: 'RETIRED',
winningSide: 1,
});

No Winner Specified

When winningSide is omitted, it's inferred from the score:

const { outcome } = mocksEngine.generateOutcomeFromScoreString({
scoreString: '6-4 6-3',
// winningSide inferred as 1
});

Applying Outcomes

Direct Application

import tournamentEngine from 'tods-competition-factory';

const { outcome } = mocksEngine.generateOutcome({
matchUpFormat: 'SET3-S:6/TB7',
winningSide: 1,
});

tournamentEngine.devContext(true).setMatchUpStatus({
matchUpId: 'match-123',
drawId: 'draw-456',
outcome,
});

During Tournament Generation

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

Batch Completion

const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 32 }],
completeAllMatchUps: true,
randomWinningSide: true,
matchUpStatusProfile: {
COMPLETED: 85,
RETIRED: 10,
DEFAULTED: 5,
},
});

Common Patterns

Testing Score Parsing

test('parses various score formats', () => {
const testCases = ['6-4 6-3', '7-6(5) 3-6 6-4', '6-0 6-0', '4-6 6-4 7-6(3)'];

testCases.forEach((scoreString) => {
const { outcome } = mocksEngine.generateOutcomeFromScoreString({
scoreString,
winningSide: 1,
});

expect(outcome.score).toBeDefined();
expect(outcome.winningSide).toBe(1);
});
});

Testing Match Completion

test('completes match with retirement', () => {
const { tournamentRecord, drawIds } = mocksEngine.generateTournamentRecord({
drawProfiles: [{ drawSize: 8 }],
});

tournamentEngine.setState(tournamentRecord);
const { matchUps } = tournamentEngine.allTournamentMatchUps();
const matchUp = matchUps[0];

const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { RETIRED: 100 },
winningSide: 1,
});

const result = tournamentEngine.setMatchUpStatus({
matchUpId: matchUp.matchUpId,
drawId: drawIds[0],
outcome,
});

expect(result.success).toBe(true);

const { matchUps: updated } = tournamentEngine.allTournamentMatchUps();
const completed = updated.find((m) => m.matchUpId === matchUp.matchUpId);
expect(completed.matchUpStatus).toBe('RETIRED');
});

Testing Score Progression

test('generates progressive match completion', () => {
const { tournamentRecord } = mocksEngine.generateTournamentRecord({
drawProfiles: [
{
drawSize: 8,
outcomes: [
// Quarter-finals
{ roundNumber: 1, roundPosition: 1, scoreString: '6-4 6-2', winningSide: 1 },
{ roundNumber: 1, roundPosition: 2, scoreString: '7-6(3) 6-4', winningSide: 1 },
{ roundNumber: 1, roundPosition: 3, scoreString: '6-3 6-4', winningSide: 2 },
{ roundNumber: 1, roundPosition: 4, scoreString: '6-2 6-3', winningSide: 1 },
// Semi-finals
{ roundNumber: 2, roundPosition: 1, scoreString: '6-4 3-6 7-5', winningSide: 1 },
{ roundNumber: 2, roundPosition: 2, scoreString: '7-5 6-4', winningSide: 2 },
// Final
{ roundNumber: 3, roundPosition: 1, scoreString: '6-4 6-4', winningSide: 1 },
],
},
],
});

tournamentEngine.setState(tournamentRecord);
const { completedMatchUps } = tournamentEngine.tournamentMatchUps();

expect(completedMatchUps.length).toBe(7);
});

Testing Edge Cases

test('handles special match statuses', () => {
const statuses = ['WALKOVER', 'DEFAULTED', 'DOUBLE_WALKOVER', 'RETIRED', 'SUSPENDED'];

statuses.forEach((status) => {
const { outcome } = mocksEngine.generateOutcome({
matchUpStatusProfile: { [status]: 100 },
winningSide: status === 'DOUBLE_WALKOVER' ? undefined : 1,
});

expect(outcome.matchUpStatus).toBe(status);

if (['DOUBLE_WALKOVER', 'SUSPENDED', 'INCOMPLETE'].includes(status)) {
expect(outcome.winningSide).toBeUndefined();
} else {
expect(outcome.winningSide).toBeDefined();
}
});
});

Generating Match Scenarios

test('creates specific match scenario', () => {
// Close 3-setter with multiple tiebreaks
const { outcome } = mocksEngine.generateOutcomeFromScoreString({
scoreString: '7-6(5) 6-7(3) 7-6(10)',
winningSide: 1,
matchUpStatus: 'COMPLETED',
});

expect(outcome.score.sets.length).toBe(3);
expect(outcome.score.sets[0].side1TiebreakScore).toBe(7);
expect(outcome.score.sets[2].side1TiebreakScore).toBe(12);
});

Outcome Object Structure

Completed Match

{
matchUpStatus: 'COMPLETED',
winningSide: 1,
score: {
sets: [
{
setNumber: 1,
side1Score: 6,
side2Score: 4,
winningSide: 1,
},
{
setNumber: 2,
side1Score: 6,
side2Score: 3,
winningSide: 1,
},
],
scoreStringSide1: '6-4 6-3',
scoreStringSide2: '4-6 3-6',
},
}

With Tiebreak

{
matchUpStatus: 'COMPLETED',
winningSide: 1,
score: {
sets: [
{
setNumber: 1,
side1Score: 7,
side2Score: 6,
side1TiebreakScore: 7,
side2TiebreakScore: 3,
winningSide: 1,
},
// ... more sets
],
scoreStringSide1: '7-6(3) 6-4',
scoreStringSide2: '6-7(3) 4-6',
},
}

Retired

{
matchUpStatus: 'RETIRED',
winningSide: 1,
score: {
sets: [], // May be empty or partial
scoreStringSide1: '',
scoreStringSide2: '',
},
}

Validation

The generated outcomes are automatically validated against:

  • MatchUp format constraints (set count, games per set, tiebreak rules)
  • TODS schema compliance
  • Score string parsability
  • Logical consistency (winner matches final score)

Tips

  1. Use matchUpFormat when testing format-specific scoring
  2. Control status distribution with matchUpStatusProfile for realistic scenarios
  3. Use generateOutcomeFromScoreString when you need specific scores
  4. Test edge cases like DOUBLE_WALKOVER and SUSPENDED
  5. Leverage outcomes in drawProfiles for complete scenario setup
  6. Remember winningSide is required except for no-winner statuses
  7. Score strings are always from winner's perspective

Next Steps