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
+23 -6
View File
@@ -9,7 +9,7 @@ import (
)
func TestInitialConnectSQLPreservesAtomicNoShowReconciliation(t *testing.T) {
if !contains(initialConnectCandidatesSQL, "ASSIGNMENT_READY") || !contains(initialConnectCandidatesSQL, "LIMIT $1") {
if !contains(initialConnectCandidatesSQL, "ASSIGNMENT_READY") || !contains(initialConnectCandidatesSQL, "initial_connect_ready_at") || !contains(initialConnectCandidatesSQL, "LIMIT $1") {
t.Fatal("initial-connect sweep is not bounded to pre-live matches")
}
for query, fragments := range map[string][]string{
@@ -31,28 +31,45 @@ func TestInitialConnectSQLPreservesAtomicNoShowReconciliation(t *testing.T) {
func TestInitialConnectPlanValidationRejectsIncompleteOrForgedPlans(t *testing.T) {
participants := []initialConnectParticipant{
{PlayerID: "p0", TicketID: "t0", Team: 0, ConnectedAt: validTime(100), Active: true},
{PlayerID: "p1", TicketID: "t1", Team: 1, Active: true},
{PlayerID: "p0", TicketID: "t0", Team: 0, Slot: 0, ConnectedAt: validTime(100), Active: true},
{PlayerID: "p1", TicketID: "t1", Team: 1, Slot: 3, Active: true},
}
plan := domain.InitialConnectPlan{
Action: domain.InitialConnectStartWithBot, MatchState: domain.Live,
Connected: []string{"p0"},
NoShows: []domain.Abandonment{{PlayerID: "p1", Cooldown: time.Minute, AbandonedAt: time.Unix(100, 0)}},
CasualLineup: []domain.CasualSlot{
{Slot: 0, Team: 0, PlayerID: "p0"}, {Slot: 1, Team: 1, PlayerID: "bot-1", IsBot: true},
{Slot: 0, Team: 0, PlayerID: "p0"}, {Slot: 1, Team: 0, PlayerID: "bot-1", IsBot: true},
{Slot: 2, Team: 0, PlayerID: "bot-2", IsBot: true}, {Slot: 3, Team: 1, PlayerID: "bot-3", IsBot: true},
{Slot: 4, Team: 0, PlayerID: "bot-4", IsBot: true}, {Slot: 5, Team: 1, PlayerID: "bot-5", IsBot: true},
{Slot: 4, Team: 1, PlayerID: "bot-4", IsBot: true}, {Slot: 5, Team: 1, PlayerID: "bot-5", IsBot: true},
},
}
if err := validateInitialConnectPlan(plan, participants, domain.Casual); err != nil {
t.Fatalf("valid plan rejected: %v", err)
}
plan.CasualLineup[1].Team = 0
plan.CasualLineup[1].Team = 1
if err := validateInitialConnectPlan(plan, participants, domain.Casual); err == nil {
t.Fatal("team-swapped lineup accepted")
}
}
func TestInitialConnectPlanValidationRequiresCompleteRosterToStart(t *testing.T) {
participants := []initialConnectParticipant{
{PlayerID: "p0", TicketID: "t0", Team: 0, Slot: 0, ConnectedAt: validTime(100), Active: true},
{PlayerID: "p1", TicketID: "t1", Team: 1, Slot: 3, ConnectedAt: validTime(100), Active: true},
}
plan := domain.InitialConnectPlan{
Action: domain.InitialConnectStart, MatchState: domain.Live, Connected: []string{"p0", "p1"},
}
if err := validateInitialConnectPlan(plan, participants, domain.Ranked); err != nil {
t.Fatalf("valid complete start rejected: %v", err)
}
participants[1].ConnectedAt = sql.NullTime{}
if err := validateInitialConnectPlan(plan, participants, domain.Ranked); err == nil {
t.Fatal("start with disconnected participant accepted")
}
}
func validTime(unix int64) (result sql.NullTime) {
result.Time = time.Unix(unix, 0)
result.Valid = true