Skip to main content

Overview

Both AutoRef implementations use finite state machines (FSMs) to manage tournament match flow. This page provides comprehensive documentation on state transitions, event triggers, and command effects.

Elimination Stage State Machine

Visual Diagram

Elimination State Machine

State Transition Table

Current StateEvent/TriggerConditionNext StateAction
Idle>startfirstPick and firstBan are setBanPhaseStartEngage automation
Idle>startPreviously stoppedPrevious stateResume automation
BanPhaseStartAutofirstBan == RedWaitingForBanRedRequest red ban
BanPhaseStartAutofirstBan == BlueWaitingForBanBlueRequest blue ban
WaitingForBanRedPlayer messageValid map slotWaitingForBanBlueRecord ban, switch turn
WaitingForBanRedPlayer message2 bans completePickPhaseStartEnd ban phase
WaitingForBanBluePlayer messageValid map slotWaitingForBanRedRecord ban, switch turn
WaitingForBanBluePlayer message2 bans completePickPhaseStartEnd ban phase
PickPhaseStartAutofirstPick == RedWaitingForPickRedRequest red pick
PickPhaseStartAutofirstPick == BlueWaitingForPickBlueRequest blue pick
WaitingForPickRedPlayer messageValid map slotWaitingForStartLoad map, start timer
WaitingForPickRedTimer expires90 seconds elapsedWaitingForPickBlueStolen pick
WaitingForPickBluePlayer messageValid map slotWaitingForStartLoad map, start timer
WaitingForPickBlueTimer expires90 seconds elapsedWaitingForPickRedStolen pick
WaitingForStartBanchoBot”All players are ready”PlayingStart match
WaitingForStartBanchoBot”Countdown finished”PlayingStart match
PlayingBanchoBot”Match finished” + 4 maps played + 2 ban roundsSecondBanPhaseStartTrigger second ban phase
PlayingBanchoBot”Match finished” + both teams at match pointWaitingForStartAuto-pick tiebreaker
PlayingBanchoBot”Match finished” + win condition metMatchFinishedAnnounce winner
PlayingBanchoBot”Match finished” + red picked lastWaitingForPickBlueNext pick to blue
PlayingBanchoBot”Match finished” + blue picked lastWaitingForPickRedNext pick to red
SecondBanPhaseStartAutofirstBan == RedWaitingForBanBlueRequest blue ban (opposite)
SecondBanPhaseStartAutofirstBan == BlueWaitingForBanRedRequest red ban (opposite)
Any waiting/playing state!panicAnyoneMatchOnHoldAbort timers, ping referees
MatchOnHold>panic_overReferee onlyWaitingForStartResume with 10s timer
Any pick/ban/waiting state!timeoutRed team + timeout availableOnTimeoutStart 120s timeout
Any pick/ban/waiting state!timeoutBlue team + timeout availableOnTimeoutStart 120s timeout
OnTimeoutBanchoBot”Countdown finished”Previous stateResume match
Any non-Idle state>stopReferee onlyIdlePause automation

Event Triggers

BanchoBot Events

Message PatternEffect
Created the tournament matchExtract MP link ID, join lobby
finished playing (Score: X,Record player score (regex)
The match has finished!Process scores, check win condition
All players are readyStart match from WaitingForStart
Countdown finishedStart match or end timeout
Changed beatmapConfirmation (awaited by commands)

Player Events

InputState RequiredEffect
NM1, HD2, etc.WaitingForPickRed/BluePick map if valid
NM1, HD2, etc.WaitingForBanRed/BlueBan map if valid
!timeoutPick/Ban/WaitingForStartRequest timeout (once per team)
!panicAny active stateTrigger emergency stop

Referee Commands

CommandEffect on State Machine
>startIdleBanPhaseStart
>stopAny → Idle
>panic_overMatchOnHoldWaitingForStart
>timeoutCurrent → OnTimeout
>firstpick [team]Sets firstPick (required before start)
>firstban [team]Sets firstBan (required before start)
>setmap [slot]Requires Idle state, doesn’t change state
>mapsNo state change (informational)
>inviteNo state change (sends invites)
>finishCloses lobby (should use /endref instead)

Win Condition Logic

int pointsToWin = (currentMatch.Round.BestOf - 1) / 2 + 1;
bool redWin = matchScore[0] == pointsToWin;
bool blueWin = matchScore[1] == pointsToWin;
Examples:
  • Best of 7: First to 4 points wins
  • Best of 11: First to 6 points wins
  • Best of 13: First to 7 points wins

Tiebreaker Trigger

if (pickedMaps.Count == currentMatch.Round.BestOf - 1)
{
    // Both teams are at match point
    await PreparePick("TB1");
}
Example (Best of 7):
  • Score reaches 3-3 after 6 maps
  • Tiebreaker is automatically picked
  • No player input required

Double Ban Round Logic

if (currentMatch.Round.BanRounds == 2 && pickedMaps.Count == 4)
{
    currentState = MatchState.SecondBanPhaseStart;
}
Flow:
  1. Initial bans: 2 per team (4 maps banned)
  2. First picks: 4 maps played
  3. Second ban phase triggers
  4. Second bans: 2 per team (8 maps total banned)
  5. Continue picks until win condition

Stolen Pick Mechanism

If a team doesn’t pick within 90 seconds:
if (content.Contains("Countdown finished") && sender == "BanchoBot")
{
    // Switch to opponent with 60-second timer
    currentState = MatchState.WaitingForPickBlue; // or Red
    isStolenPick = true;
    await SendMessageBothWays("!mp timer 60");
}
Effect on Pick Order:
  • lastPick is set to the team that should have picked
  • This ensures pick order alternates correctly on next map

Qualifiers Stage State Machine

Visual Diagram

Qualifiers State Machine

State Transition Table

Current StateEvent/TriggerConditionNext StateAction
Idle>startNot previously stoppedIdleReset map index, load first map
Idle>startPreviously stoppedPrevious stateResume from previous state
IdleAuto (from flow)Maps remainingWaitingForStartLoad next map, start 120s timer
IdleAuto (from flow)All maps playedMatchFinishedAnnounce completion
WaitingForStartBanchoBot”All players are ready”PlayingStart match
WaitingForStartBanchoBot”Countdown finished”PlayingStart match
PlayingBanchoBot”Match finished”IdleIncrement map index, 10s cooldown
Any active state!panicAnyoneMatchOnHoldAbort timers, ping referees
MatchOnHold>panic_overReferee onlyWaitingForStartResume with 10s timer
Any non-Idle state>stopReferee onlyIdlePause automation

Event Triggers

BanchoBot Events

Message PatternEffect
Created the tournament matchExtract MP link ID, join lobby
The match has finishedIncrement map index, trigger cooldown
All players are readyStart match from WaitingForStart
Countdown finishedStart match from WaitingForStart
Changed beatmapConfirmation (awaited by commands)

Player Events

InputState RequiredEffect
!panicAny active stateTrigger emergency stop

Referee Commands

CommandEffect on State Machine
>startIdleIdle (triggers flow)
>stopAny → Idle
>panic_overMatchOnHoldWaitingForStart
>setmap [slot]Requires Idle state, doesn’t change state
>inviteNo state change (sends invites)
>finishCloses lobby (should use /endref instead)

Map Progression Flow

┌─────────────────────────────────────────┐
│ >start command                          │
│ - Set currentMapIndex = 0               │
│ - Set currentState = Idle               │
│ - Call PrepareNextQualifierMap()        │
└─────────────────────────────────────────┘
                  |
                  v
┌─────────────────────────────────────────┐
│ PrepareNextQualifierMap()               │
│ - Check if maps remaining               │
│ - Load map at currentMapIndex           │
│ - Apply mods (e.g., NM, HD, HR + NF)    │
│ - Start 120s timer                      │
│ - Set state to WaitingForStart          │
└─────────────────────────────────────────┘
                  |
                  v
┌─────────────────────────────────────────┐
│ WaitingForStart                         │
│ - Wait for players to ready up          │
│ - OR wait for timer to expire           │
└─────────────────────────────────────────┘
                  |
                  v
┌─────────────────────────────────────────┐
│ Playing                                 │
│ - Match is in progress                  │
│ - Wait for "Match finished" message     │
└─────────────────────────────────────────┘
                  |
                  v
┌─────────────────────────────────────────┐
│ Match Finished Event                    │
│ - Increment currentMapIndex++           │
│ - Set state to Idle                     │
│ - Start 10-second cooldown              │
└─────────────────────────────────────────┘
                  |
                  v
┌─────────────────────────────────────────┐
│ Cooldown Complete                       │
│ - Call PrepareNextQualifierMap()        │
│ - Loop back to top                      │
└─────────────────────────────────────────┘

Map Index Progression

Example Pool: 7 maps (NM1, NM2, HD1, HD2, HR1, HR2, DT1)
EventcurrentMapIndexMap LoadedState
>start0NM1WaitingForStart
Match finishes1NM2WaitingForStart
Match finishes2HD1WaitingForStart
Match finishes3HD2WaitingForStart
Match finishes4HR1WaitingForStart
Match finishes5HR2WaitingForStart
Match finishes6DT1WaitingForStart
Match finishes7(none)MatchFinished

Exit Condition

if (currentMapIndex >= currentMatch.Round.MapPool.Count)
{
    await SendMessageBothWays(Strings.QualifiersOver);
    currentState = MatchState.MatchFinished;
    return;
}
When this triggers:
  • All maps have been played
  • currentMapIndex exceeds pool size
  • Automation announces completion
  • State set to MatchFinished (terminal state)

Common State Machine Patterns

Panic System

Both implementations share identical panic logic: Activation:
if (content.Contains("!panic"))
{
    currentState = MatchState.MatchOnHold;
    await SendMessageBothWays("!mp aborttimer");
    await SendMessageBothWays($"<@&{REFEREE_ROLE_ID}> PANIC");
}
Recovery:
if (content.Contains(">panic_over") && senderNick == referee)
{
    currentState = MatchState.WaitingForStart;
    await SendMessageBothWays("!mp timer 10");
}
Key Properties:
  • Can be triggered by anyone in the lobby
  • Can only be cleared by the assigned referee
  • Immediately aborts all active timers
  • Pings Discord referee role for visibility
  • Always recovers to WaitingForStart with a 10-second timer

Timer System

ContextDurationCommand
Elimination: Map Ready90 seconds!mp timer 90
Elimination: Timeout120 seconds!mp timer 120
Qualifiers: Map Ready120 seconds!mp timer 120
Panic Recovery10 seconds!mp timer 10
Match Start Delay10 seconds!mp start 10
Stolen Pick Window60 seconds!mp timer 60

State Persistence

Both implementations support stop/resume:
private bool stoppedPreviously;
private MatchState previousState;

case "stop":
    previousState = currentState;
    currentState = MatchState.Idle;
    stoppedPreviously = true;
    break;

case "start":
    if (stoppedPreviously)
    {
        currentState = previousState;
        stoppedPreviously = false;
    }
    break;
Use Case:
  • Referee needs to manually intervene mid-automation
  • >stop pauses without losing state
  • >start resumes exactly where it left off

State Machine Comparison

AspectEliminationQualifiers
Total States135
Player InputRequired (picks/bans)Not required
Deterministic FlowNo (player-driven)Yes (linear)
Win ConditionFirst to N pointsComplete all maps
Timeout SupportYes (per-team)No
Pick Timer90 secondsN/A
Map Timer90 seconds120 seconds
CooldownNone10 seconds
Map SelectionPlayer choiceSequential
ComplexityHighLow

Debugging State Machines

Logging Current State

Both classes expose currentState as internal, allowing test inspection:
Console.WriteLine($"Current State: {autoRef.currentState}");

Forcing State Transitions

For testing, states can be manually set:
autoRef.currentState = MatchState.WaitingForStart;
await autoRef.HandleIrcMessage(mockMessage);

Common Stuck States

StateLikely CauseFix
WaitingForPickRed/BluePlayer offline or confusedReferee types >stop, manually picks, >start
WaitingForStartTimer expired but no reactionCheck panic mode; >panic_over if stuck
MatchOnHoldPanic never clearedReferee types >panic_over
OnTimeoutTimeout countdown stuckWait for BanchoBot “Countdown finished”
IdleAutomation never startedCheck if firstPick/firstBan are set (elimination)

Build docs developers (and LLMs) love