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-6SET5-S:6/TB7- Best of 5 setsSET3-S:4/TB7- Short sets to 4SET1-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
- Use matchUpFormat when testing format-specific scoring
- Control status distribution with matchUpStatusProfile for realistic scenarios
- Use generateOutcomeFromScoreString when you need specific scores
- Test edge cases like DOUBLE_WALKOVER and SUSPENDED
- Leverage outcomes in drawProfiles for complete scenario setup
- Remember winningSide is required except for no-winner statuses
- Score strings are always from winner's perspective
Next Steps
- Tournament Generation - Apply outcomes during generation
- Advanced Patterns - Complex outcome scenarios
- Participant Generation - Types and demographics