This is a Layer 2 document. See CLAUDE.md for project overview.
Match Rotation Engine Specification
Status: Active
Overview
Match rotation is TwoMore's core differentiator. Every club has different preferences for how players are paired and rotated across rounds. The engine accepts a RotationConfig that the admin (총무) customizes per club or per session.
RotationConfig Interface
ts
interface RotationConfig {
// Format
format: 'doubles' | 'singles' | 'mixed'; // default: 'doubles'
// Partner/opponent mixing
partnerMode:
| 'rotate_all' // maximize unique partners (default)
| 'fixed_pairs' // pre-set partner pairs
| 'skill_balanced' // pair high+low to balance teams
| 'random'; // pure random each round
avoidRepeatPartners: boolean; // true = never repeat partner within session
avoidRepeatOpponents: boolean; // true = try to avoid repeat opponents
// Skill balancing
balanceByElo: boolean; // balance team Elo totals (default: true)
eloTolerance: number; // max acceptable Elo gap between teams (default: 200)
// Bench/rest
benchMode:
| 'fair_rotation' // rotate bench so everyone plays equally (default)
| 'volunteer' // let players choose to sit out
| 'none'; // no bench (error if odd players)
// Round settings
roundDurationMinutes: number; // default: 30
maxRounds: number | null; // null = unlimited (play until session ends)
// Gender constraints (for mixed doubles)
requireMixedGender: boolean; // force one male + one female per team (default: false)
// Custom constraints (future extensibility)
customRules: Record<string, unknown>;
}Default Presets
Presets are named configs the admin can pick from and tweak. Organized by format.
Doubles (복식)
| Preset | partnerMode | balanceByElo | benchMode | Use case |
|---|---|---|---|---|
| 소셜 (Social) | rotate_all | false | fair_rotation | Casual meetups, maximize mixing |
| 실력별 (Competitive) | skill_balanced | true | fair_rotation | Ranked play, balanced matches |
| 고정 복식 (Fixed Doubles) | fixed_pairs | true | volunteer | Pre-arranged partner sessions |
| 랜덤 (Random) | random | false | fair_rotation | Fun/variety-focused |
Singles (단식)
| Preset | balanceByElo | avoidRepeatOpponents | Use case |
|---|---|---|---|
| 소셜 단식 (Social Singles) | false | true | Casual, play everyone once |
| 실력별 단식 (Competitive Singles) | true | true | Elo-matched opponents |
| 랜덤 단식 (Random Singles) | false | false | Pure random assignment |
UI shows the relevant preset category based on the selected format.
Architecture
RotationConfiglives indomain/entities/rotation-config.entity.tswith Zod schemagenerateRound()andgenerateAllRounds()indomain/rules/rotation.rules.tsacceptRotationConfigas a parameter- Clubs store a default
RotationConfig(JSON field onclubstable) - Sessions can override the club default with a session-specific config
- Preset templates are constants in
domain/rules/rotation-presets.ts - UI flow: admin picks a preset, then fine-tunes via a settings form