fix(multiplayer): reconcile authoritative initial connections

This commit is contained in:
Josh Creek
2026-09-03 00:02:04 +01:00
parent 781cbc35aa
commit aa446cfbfe
40 changed files with 809 additions and 87 deletions
+22 -6
View File
@@ -15,6 +15,7 @@ const (
type ConnectParticipant struct {
PlayerID string
Team int
Slot int
Connected bool
}
@@ -22,6 +23,7 @@ type InitialConnectAction string
const (
InitialConnectWait InitialConnectAction = "WAIT"
InitialConnectStart InitialConnectAction = "START"
InitialConnectCancel InitialConnectAction = "CANCEL"
InitialConnectStartWithBot InitialConnectAction = "START_WITH_BOTS"
)
@@ -57,6 +59,9 @@ func PlanInitialConnect(playlist Playlist, readyAt, now time.Time, participants
switch decision.Action {
case InitialConnectWait:
return plan, nil
case InitialConnectStart:
plan.MatchState = Live
return plan, nil
case InitialConnectCancel:
plan.MatchState = Cancelled
return plan, nil
@@ -86,16 +91,18 @@ func EvaluateInitialConnect(playlist Playlist, readyAt, now time.Time, participa
if playlist != Ranked && playlist != Casual || readyAt.IsZero() || len(participants) == 0 {
return InitialConnectDecision{}, fmt.Errorf("invalid initial-connect policy input")
}
if now.Before(readyAt.Add(InitialConnectWindow)) {
return InitialConnectDecision{Action: InitialConnectWait}, nil
if playlist == Ranked && len(participants) != 6 || playlist == Casual && (len(participants) < 2 || len(participants) > 6) {
return InitialConnectDecision{}, fmt.Errorf("invalid initial-connect roster size")
}
missing := make([]ConnectParticipant, 0)
connected := make([]string, 0)
teamConnected := map[int]bool{}
seen := make(map[string]bool, len(participants))
for _, participant := range participants {
if participant.PlayerID == "" || participant.Team < 0 {
if participant.PlayerID == "" || participant.Team < 0 || participant.Team > 1 || participant.Slot < 0 || participant.Slot > 5 || participant.Slot/3 != participant.Team || seen[participant.PlayerID] {
return InitialConnectDecision{}, fmt.Errorf("invalid participant")
}
seen[participant.PlayerID] = true
if participant.Connected {
connected = append(connected, participant.PlayerID)
teamConnected[participant.Team] = true
@@ -103,10 +110,19 @@ func EvaluateInitialConnect(playlist Playlist, readyAt, now time.Time, participa
missing = append(missing, participant)
}
}
if playlist == Ranked {
if len(participants) != 6 {
return InitialConnectDecision{}, fmt.Errorf("ranked requires six participants")
if len(missing) == 0 {
if playlist == Casual && len(participants) < 6 {
if !teamConnected[0] || !teamConnected[1] {
return InitialConnectDecision{}, fmt.Errorf("casual bot roster requires a human on each team")
}
return InitialConnectDecision{Action: InitialConnectStartWithBot, Innocent: sortedIDs(connected)}, nil
}
return InitialConnectDecision{Action: InitialConnectStart, Innocent: sortedIDs(connected)}, nil
}
if now.Before(readyAt.Add(InitialConnectWindow)) {
return InitialConnectDecision{Action: InitialConnectWait}, nil
}
if playlist == Ranked {
return InitialConnectDecision{Action: InitialConnectCancel, NoShows: rankedNoShows(missing, now, priorAbandons), Innocent: sortedIDs(connected)}, nil
}
if now.Before(readyAt.Add(CasualBotStartAfter)) {